본문 바로가기
웹개발/스프링

스프링 4 DAO 구현

by 인생여희 2020. 11. 8.

 

데이터 엑세스 층의 주요역할은 데이터 액세스 처리를 비즈니스 로직층에서 분리하는 것이다. 비즈니스의 핵심 로직과 데이터에 액세스하는 로직이 함께 작성되어 있으면 나중에 유지보수하거나 수정할때 더 힘들어진다. 그래서 DAO 패턴을 사용한다. DAO 패턴은 데이터의 접근과 변경, 취득 등 데이터 액세스 처리를 DAO라고 하는 오브젝트로 분리해서 처리하는 패턴이다. 이렇게 하면 데이터 엑세스와 관련된 로직을 비즈니스 로직과 분리 할 수 있고, 데이터 액세스 방식이 변경되어도 비즈니스 로직에 영향을 주지 않는다.

 

DAO 클래스는 데이터베이스의 테이블별로 만들어지는 것이 보통이다.

 

자바의 데이터 엑세스 기술

자바의 다양한 데이터 액세스 기술

자바가 데이터 엑세스 처리를 구현할때 사용하는 기술에는 여러가지가 존재한다. 예를들어 JDBC, 하이버 네이트, MyBatis, JPA등 다양한 데이터 엑세스 기술이 존재한다.

 

 

스프링이 제공하는 데이터 액세스 기술

스프링이 제공하는 데이터 엑세스 기술

스프링은 새로운 데이터 액세스 기술을 제공하는 것이 아니라 기존의 데이터 액세스 기술을 간단하고 쉽게 사용할 수 있는 연계기능을 제공하고 있다. 예를 들면 JDBC, 하이버네이트, JPA에 관해서는 스프링에서 직접 연계기능을 제공하고 있으며, MyBatis는 MyBatis에서 연계기능을 제공하고 있다. 스프링이 제공하는 데이터 액세스 기술을 사용하면 다음과 같은 여러가지 장점을 얻을 수 있다.

 

1. 데이터 엑세스 처리를 간결하게 할 수 있다.

2.스프링이 제공하는 범용적이고 체계적인 데이터 액세스 예외를 이용할 수 있다.

3.스프링의 트랜잭션을 이용할 수 있다.

 

범용데이터 베이스 예외

 

스프링이 제공하는 범용적이고 체계적인 데이터 액세스 예외

스프링이 제공하는 범용 데이터 액세스 예외는 데이터 액세스 기술에 의존하지 않는 범용적인 예외 클래스집합이다. 데이터 액세스를 할때 발생하는 에러 원인을 체계적으로 정리하여 에러의 원인 별로 클래스를 만들어 놓았다. 그래서 데이터 엑세스 기술마다 존재하는 특정 예외를 스프링만의 범용예외로 변환하기 때문에 예외를 핸들링하는 클래스가 데이터 엑세스 기술에 의존하지 않아도 된다. 범용 데이터 엑세스 예외의 모든 클래스는 DataAccessException이라는 예외 클래스가 슈퍼 클래스다. 범용 데이터 액세스 예외는 실행할 때의 예외이므로 예외를 핸들링하는 쪽에서 catch구문이 필수가 아니고, 처리 가능한 예외만 처리 가능한 장소에서 catch 하면 된다.

 

 

범용데이터 액세스 예외는 발생한 곳에서 가까운 데이터 액세스 층의 DAO에서 catch하는 것이 되지만, DAO에 대응해야 하는 예외는 기본적으로 정의하지 않았으므로 통과시키고, 서비스 및 컨트롤러에 대응해야 하는 예외만을 catch 해서 처리한다. 

 

예를들어 취득되어야할 데이터가 취득이 되지 않아 EmptyResultDataAccessException이 발생했다면, 업무적사양에서 다른 테이블에서 데이터를 취득하는 것일 경우 서비스의 예외를 catch 해서 다른 테이블의 데이터를 취득해준다.

 

두번째 예로 교착상태에 빠졌을경우(DeadLockLoserDataAccessException) 컨트롤러가 catch해서 브라우저에 "다시 시도해 주세요"라는 메시지를 표시하는것으로 대응할 수도 있다. 

 

마지막으로 데이터 베이스에 접속되지 않는경우 발생하는 DataAccessResourceFailureException 등에 대해서는 서비스 및 컨트롤러에서 대응할 수 있는 것이 아니므로 공통기능에서 한번에 처리하는 것이 좋다. AOP를 이용해서 시스템 관리자에게 문의하라는 등 내용을 알려주는 기능을 추가해 줄 수도 있다.

 

 

데이터 소스

커넥션 풀에 대응하는 데이터 소스

데이터 액세스 기술의 종류와 상관없이 데이터베이스 접속은 필요하고, 데이터베이스에 접속할 경우, 데이터베이스 접속을 관리해주는 데이터 소스를 준비할 필요가 있다.

데이터 소스는 데이터 베이스와 접속 오브젝트인 Connection 오브젝트의 팩토리라고 할 수 있다. Connection 오브젝트의 생명 주기는 데이터 소스가 맡고, 보통 업무용 앱에서는 커넥션 풀에 의해 Connection 오브젝트를 재사용 한다. 무한히 Connection 오브젝트를 작성해서 데이터 베이스의 리소스가 고갈 되는 것을 방지하거나 Connection 오브젝트가 생성하고 소멸할 때 부하를 줄이는 것이 주된 목적이다.

 

JDBC의 API로서 DataSource라는 인터페이스가 제공되고 다양한 구현 모델이 존재한다.

 

스프링에서 데이터 소스는 데이터 소스를 bean 정의 파일이나 javaConfig로 정의한다음 개발자가 작성한 Bean등에 인젝션 해서 사용한다. 인잭션 할때는 어노테이션으로 해도되고, bean정의 파일이나 javaconfig로 해도 상관없다.

 

데이터 소스의 이용

참고로, 데이터 소스를 정의할때 지정하는 jdbc 드라이버의 이름과 url 접속정보는 쉽게 변경할 수 있도록 별도의 프로퍼티 파일에 작성하는 것이 좋다. context 스키마의 property-placeholder 태그를 이용하거나, javaconfig의 @propertySource 어노테이션을 이용하면 프로퍼티 파일에 작성한 문자열을 Bean 정의로 이용할 수 있다.

 

프로퍼티 파일의 이용 방법과 같이 데이터 소스 Bean 정의 방법 확인해보자.

개발자가 적절한 데이터 소스의 구현체를 선택해서 결정한다. 구현의 종류는 아래와 같다.

 

-서드파티가 제공하는 데이터 소스

-애플리케이션 서버가 제공하는 데이터 소스

-임베디드 데이터 베이스의 데이터 소스

 

 

서드파티가 제공하는 데이터 소스

데이터 소스의 구현 제품은 여러 서드 파티에서 제공하고 있다. 대표적으로 Apache Commons DBCP가 있다. 이 DBCP는 오픈 소스 제품이고, 무상으로 이용할 수 있으며 커넥션 풀도 대응하고 있다. 아래 내용은 Bean 정의 파일에 DBCP의 데이터 소스를 정의한 예이다.

spring-db.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xmlns:jdbc="http://www.springframework.org/schema/jdbc" 
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:property-placeholder location="jdbc.properties"/>
 

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxActive" value="${jdbc.maxPoolSize}" />
    </bean>
    

</beans>

 

데이터 베이스의 접속 정보로 사용자명 및 패스워드 등의 기본 정보 및 풀링하는 커넥션의 최대 수 지정 등 상세 설정을 할 수 있다. 직접 파일에 적을 수 있지만 나중에 변경하기 편하도록 ${} 를 이용해서 중괄호 안에 프로퍼티 파일의 값을 적어준다. 프로퍼티 파일을 읽어 들이기 위해서는 bean 정의 파일에 context 스키마를 정의 하고 <context:property-placeholder location="jdbc.properties"/> 구문을 이용해서 읽어 들인다. 아래는 읽어 들일 프로퍼티 파일(jdbc.properties)이다.

 

jdbc.properties

jdbc.driverClassName=org.hsqldb.jdbc.JDBCDriver
jdbc.url=jdbc:hsqldb:mem:sample
jdbc.username=sa
jdbc.password=
jdbc.maxPoolSize=20

 

 

애플리케이션 서버가 제공하는 데이터 소스

JNDI를 이용한 데이터 소스 취득

일반적으로 애플리케이션 서버 제품은 데이터 소스의 오브젝트를 생성, 관리해주는 기능을 가지고 있다. 애플리케이션이 관리하는 데이터 소스 오브젝트는 대부분 애플리케이션 서버에 내장되고, 네이밍 서비스로 관리된다. 애플리케이션은 네이밍 서비스가 관리하는 오브젝트에 액세스 하기 위한 표준 API인 JNDI를 이용해서 데이터 소스 오브젝트를 취득하고 있다.

JNDI 경유로 데이터 소스를 취득하는 대표적인 방법은 Bean 파일의 경우 jee 스키마의 jndi-lookup 태그를 이용해서 아래와 같이 작성 한다.(bean 정의 파일에 jee 스키마를 정의해야한다.)

 

spring-jndi.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <context:property-placeholder location="jndi.properties"/>


    <jee:jndi-lookup id="dataSource"  jndi-name="${jndi.datasource}" />


</beans>

 

애플리케이션 서버가 관리하는 데이터 소스의 오브젝터가 dataSource라는 ID의 Bean으로 관리된다.

 

jndi.properties

jndi.datasource=jdbc/MyDataSource

 

참고  : 데이터 소스에 sql 스크립트 파일 실행하기

일반 데이터 소스에서 sql 스크립트 파일 실행할때 <jdbc:initialize-database data-source="dataSource"> 태그를 이용해서 스크립트 실행 대상의 데이터 소스의 Bean 명을 data-soure 속성에 지정한다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xmlns:jdbc="http://www.springframework.org/schema/jdbc" 
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 서드파티가 제공하는 데이터 소스 정보를 프로퍼티 파일에서 읽어오기 -->
    <context:property-placeholder location="jdbc.properties"/>
 

<!-- 서드파티가 제공하는 데이터 소스 - Apache Commons DBCP -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxActive" value="${jdbc.maxPoolSize}" />
    </bean>
    
    
<!--     데이터 소스에 sql 스크립트 파일 실행하기 -->
    <jdbc:initialize-database data-source="dataSource">
        <jdbc:script location="script/table.sql"/>
        <jdbc:script location="script/data.sql"/>
        <jdbc:script location="script/proc.sql" separator="/"/>
    </jdbc:initialize-database>


</beans>

 

 

일반 JDBC와 스프링 JDBC

일반 JDBC를 이용했을때

스프링 JDBC는 JDBC를 래핑한 API를 제공하고 JDBC 보다 간결하게 사용할 수 있는 스프링 기능이다.

JDBC를 직접 사용한 경우 여러가지 문제점이 있는데 그 중하나가 select문 하나 작성하는데도 대량의 소스 코드를 기술해야 된다는 것이다. PreparedStatement를 취득했을 때 반드시 클로즈도 해야 한다. 또한 데이터 엑세스시 발생한 에러의 원인도 SQLException에서 에러 코드를 취득한 후 값을 조사해야 한다. 에러 코드는 데이터베이스 제품마다 다르므로 데이터베이스가 바뀌면 소스도 변경해야 할 필요가 있다. SQLException은 컴파일 시 예외 처리 유무를 검사하므로 소스 코드에서 반드시 catch 구문을 기술 해야 컴파일 할 수 있다. throws를 선언하는 방법도 있지만 결국에는 호출한 메서드 안에서 catch를 기술해야 하는 단점이 있다.

 

스프링 JDBC를 이용했을때

스프링 JDBC는 JDBC를 래핑한 API를 제공해, JDBC를 직접이용할 때 발생하는 긴 코드들을 사용하지 않게 해준다. 스프링 JDBC를 사용하면 소스코드가 짧아지고, 간단해지며, SQLException의 원인을 특정짓는 처리도 필요 없어진다.

 

Template 클래스

스프링 jdbc에는 중요한 클래스 2개가 있다. JdbcTemplate, NamedParameterJdbcTemplate 클래스다. 

jdbc를 직접 사용했을때 생기는 장황한 처리 부분을 지정된 형틀로 구현해서 간단하게 처리할 수 있도록 해준다. 

탬플릿 클래스의 빈을 등록한다음 DAO에 인젝션 한다. 템플릿 클래스의 오브젝트를 저장할 필드를 준비해서 @autowired를 지정하면 템플릿 클래스의 bean이 생성된다.(bean정의 파일 안에서 인젝션 해도된다.) 

 

template.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xmlns:jdbc="http://www.springframework.org/schema/jdbc" 
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 서드파티가 제공하는 데이터 소스 정보를 프로퍼티 파일에서 읽어오기 -->
    <context:property-placeholder location="jdbc.properties"/>


	<!-- 스프링 jdbc는 jdbc를 래핑한 api를 제공해, jdbc를 직접 사용할때 발생하는 상황한 코드를 숨겨준다. 스프링 
		jdbc를 이용하면 소스코드가 아주 간단해지고 sqleception의 원인을 특정하는 처리도 필요 없어진다. 스프링 jdbc에는 중요한 
		클래스 2개가 있다. JdbcTemplate, NamedParameterJdbcTemplate 클래스다. jdbc를 직접 사용했을때 
		생기는 장황한 처리 부분을 지정된 형틀로 구현해서 간단하게 처리할 수 있도록 해준다. -->


	<!-- 탬플릿 클래스의 빈을 등록한다음 DAO에 인젝션 한다. 템플릿 클래스의 오브젝트를 저장할 필드를 준비해서 @autowired를 
		지정하면 템플릿 클래스의 bean이 생성된다. (bean정의 파일 안에서 인젝션 해도된다.) -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg ref="dataSource" />
    </bean>
    <bean
        class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
        <constructor-arg ref="dataSource" />
    </bean>



<!-- 서드파티가 제공하는 데이터 소스 - Apache Commons DBCP -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxActive" value="${jdbc.maxPoolSize}" />
    </bean>
    
    
<!--     데이터 소스에 sql 스크립트 파일 실행하기 -->
    <jdbc:initialize-database data-source="dataSource">
        <jdbc:script location="script/table.sql"/>
        <jdbc:script location="script/data.sql"/>
        <jdbc:script location="script/proc.sql" separator="/"/>
    </jdbc:initialize-database>


</beans>

 

JdbcTemplate, NamedParameterJdbcTemplate의 Bean등록이 끝났으면 각각 생성자에서 데이터 소스를 인젝션 한다. 

<constructor-arg ref="dataSource" />

 

템플릿 클래스의 Bean을 등록한다음 DAO에 인젝션한다. 템플릿 클래스의 오브젝트를 저장할 필드를 준비해서 @Autowired를 지정하면 템플릿 클래스의 Bean이 인젝션 된다.(Bean 정의 파일을 이용해서 인젝션 해도 된다.)

 

템플릿 클래스의 Bean을 DAO에 인젝션

@Repository
public class SpringJDBCAccoundDao implements AccountDao{
	
    //주입
	@Autowired
	private JdbcTemplate jdbTemplate;
	
	@Autowired
	private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
	
	
}

 

위의 여러 JDBC를 이용해서 SQL문 작성 예 1 

package sample;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
import org.springframework.jdbc.core.simple.SimpleJdbcCall;

import sample.config.DataSourceConfig;
import sample.config.TemplateConfig;
import sample.springjdbc.business.domain.Owner;
import sample.springjdbc.business.domain.Pet;

public class ExecuteSqlMain {

    public static void main(String[] args) {
    	
    	// Spring 컨터이너 생성        
    	//XML로 Bean을 정의한 경우
    	ApplicationContext ctx = new ClassPathXmlApplicationContext("sample/config/spring-db.xml");

    	//JavaConfig로 Bean을 정의한 경우
    	//ApplicationContext ctx = new AnnotationConfigApplicationContext(
        //        TemplateConfig.class, DataSourceConfig.class);
        
        // JdbcTemplate 와 NamedParameterJdbcTemplate 오브젝트를 취득
        JdbcTemplate jdbcTemplate = ctx.getBean(JdbcTemplate.class);
        NamedParameterJdbcTemplate namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class);

        // SELECT 문 ~ 도메인으로 변환안하는 경우
        // queryForInt 메서드
        int count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM PET", Integer.class);
        System.out.println(count);

        
        String ownerName = "홍길동";
        count = jdbcTemplate.queryForObject(
                "SELECT COUNT(*) FROM PET WHERE OWNER_NAME=?", Integer.class, ownerName);
        System.out.println(count);
        
        // queryForObject 메서드
        int id = 1;
        String petName = jdbcTemplate.queryForObject(
                "SELECT PET_NAME FROM PET WHERE PET_ID=?", String.class, id);
        System.out.println(petName);
        
        Date birthDate = jdbcTemplate.queryForObject(
                "SELECT BIRTH_DATE FROM PET WHERE PET_ID=?", Date.class, id);
        System.out.println(birthDate);
        
        
        // queryForMap 메서드
        Map<String, Object> map = jdbcTemplate.queryForMap(
                "SELECT * FROM PET WHERE PET_ID=?", id);
        System.out.println(map.get("PET_NAME"));
        System.out.println(map.get("OWNER_NAME"));
        
        // queryForList 메서드
        List<Map<String, Object>> mapList = jdbcTemplate.queryForList(
                " SELECT * FROM PET WHERE OWNER_NAME=?", ownerName);
        System.out.println(mapList.get(0).get("PET_NAME"));
        System.out.println(mapList.get(0).get("OWNER_NAME"));
        
        
        List<Integer> priceList = jdbcTemplate.queryForList(
                "SELECT PRICE FROM PET WHERE OWNER_NAME=?", Integer.class, ownerName);
        
        System.out.println(priceList.get(0));
        
        
        // SELECT 문 ~ 도메인으로 변환하는 경우
        // queryForObject 메서드
        // RowMapper을 익명클라스로하는 경우
        Pet pet = jdbcTemplate.queryForObject(
                "SELECT * FROM PET WHERE PET_ID=?"
                , new RowMapper<Pet>() {
                    public Pet mapRow(ResultSet rs, int rowNum) throws SQLException {
                        Pet p = new Pet();
                        p.setPetId((Integer)rs.getObject("PET_ID"));
                        p.setPetName(rs.getString("PET_NAME"));
                        p.setOwnerName(rs.getString("OWNER_NAME"));
                        p.setPrice((Integer)rs.getObject("PRICE"));
                        p.setBirthDate(rs.getDate("BIRTH_DATE"));
                        return p;
                        }}
                , id); 
        System.out.println(pet.getPetName());
        System.out.println(pet.getOwnerName());
        
        // RowMapper을 익명클래스로 하지 않는 경우
        class MyRowMapper implements RowMapper<Pet> {
            public Pet mapRow(ResultSet rs, int rowNum) throws SQLException {
                Pet p = new Pet();
                p.setPetId(rs.getInt("PET_ID"));
                p.setPetName(rs.getString("PET_NAME"));
                p.setOwnerName(rs.getString("OWNER_NAME"));
                p.setPrice(rs.getInt("PRICE"));
                p.setBirthDate(rs.getDate("BIRTH_DATE"));
                return p;
            }
        }
        pet = jdbcTemplate.queryForObject(
                " SELECT * FROM PET WHERE PET_ID=?"
                ,new MyRowMapper()
                ,id);
        System.out.println(pet.getPetName());
        System.out.println(pet.getOwnerName());
        
        // query 메서드
        List<Pet> petList = jdbcTemplate.query(
                " SELECT * FROM PET WHERE OWNER_NAME=?"
                , new RowMapper<Pet>() {
                public Pet mapRow(ResultSet rs, int rowNum) throws SQLException {
                    Pet p = new Pet();
                    p.setPetId(rs.getInt("PET_ID"));
                    p.setPetName(rs.getString("PET_NAME"));
                    p.setOwnerName(rs.getString("OWNER_NAME"));
                    p.setPrice(rs.getInt("PRICE"));
                p.setBirthDate(rs.getDate("BIRTH_DATE"));
                    return p;
                }}
            , ownerName);
        System.out.println(petList.get(0).getPetName());
        System.out.println(petList.get(0).getOwnerName());

        // BeanPropertyRowMapper을 사용해서 도메인으로 변환을 자동화
        pet = jdbcTemplate.queryForObject(
                " SELECT * FROM PET WHERE PET_ID=?"
                , new BeanPropertyRowMapper<Pet>(Pet.class)
                , id);
        System.out.println(pet.getPetName());
        System.out.println(pet.getOwnerName());

        // ResultSetExtractor을 사용한 도메인 변환
        // 부모 도메인가 하나인 경우
        Owner owner = jdbcTemplate.query(
                " SELECT * FROM OWNER O INNER JOIN PET P ON O.OWNER_NAME=P.OWNER_NAME WHERE O.OWNER_NAME=?"
                , new ResultSetExtractor<Owner>() {
                    public Owner extractData(ResultSet rs) throws SQLException, DataAccessException {
                        if (!rs.next()) {
                            return null;
                        }
                        Owner owner = new Owner();
                        owner.setOwnerName(rs.getString("OWNER_NAME"));
                        do {
                            Pet pet = new Pet();
                            pet.setPetId(rs.getInt("PET_ID"));
                            pet.setPetName(rs.getString("PET_NAME"));
                            pet.setOwnerName(rs.getString("OWNER_NAME"));
                            pet.setPrice(rs.getInt("PRICE"));
                            pet.setBirthDate(rs.getDate("BIRTH_DATE"));
                            owner.getPetList().add(pet);
                        } while(rs.next());
                        return owner;
                    }}
                , ownerName);
        System.out.println(owner.getOwnerName());
        System.out.println(owner.getPetList().get(0).getPetName());
        System.out.println(owner.getPetList().get(0).getOwnerName());
        
        // 부모 도메인가 복수인 경우
        List<Owner> ownerList = jdbcTemplate.query(
                " SELECT * FROM OWNER O INNER JOIN PET P ON O.OWNER_NAME=P.OWNER_NAME ORDER BY OWNER_NAME"
                , new ResultSetExtractor<List<Owner>>() {
                    public List<Owner> extractData(ResultSet rs) throws SQLException, DataAccessException {
                        List<Owner> result = new ArrayList<Owner>();
                        Owner owner = null;
                        String currentPk = "";
                        while (rs.next()) {
                            String ownerName = rs.getString("OWNER_NAME");
                            if (!ownerName.equals(currentPk)) {
                                owner = new Owner();
                                owner.setOwnerName(rs.getString("OWNER_NAME"));
                                currentPk = ownerName;
                                result.add(owner);
                            }
                            Pet pet = new Pet();
                            pet.setPetId(rs.getInt("PET_ID"));
                            pet.setPetName(rs.getString("PET_NAME"));
                            pet.setOwnerName(rs.getString("OWNER_NAME"));
                            pet.setPrice(rs.getInt("PRICE"));
                            pet.setBirthDate(rs.getDate("BIRTH_DATE"));
                            owner.getPetList().add(pet);
                        }
                        return result;
                    }}
                );
        System.out.println(ownerList.get(0).getOwnerName());
        System.out.println(ownerList.get(0).getPetList().get(0).getPetName());
        System.out.println(ownerList.get(0).getPetList().get(0).getOwnerName());
        
        
        // INSERT/UPDATE/DELETE 문
        pet = new Pet();
        pet.setPetId(99);
        pet.setPetName("나비");
        pet.setOwnerName("홍길동");
        pet.setPrice(10000);
        pet.setBirthDate(new Date());        
        jdbcTemplate.update(
                "INSERT INTO PET (PET_ID, PET_NAME, OWNER_NAME, PRICE, BIRTH_DATE) VALUES (?, ?, ?, ?, ?)"
                , pet.getPetId(), pet.getPetName(), pet.getOwnerName(), pet.getPrice(), pet.getBirthDate());
        
        jdbcTemplate.update(
                "UPDATE PET SET PET_NAME=?, OWNER_NAME=?, PRICE=?, BIRTH_DATE=? WHERE PET_ID=?"
                , pet.getPetName(), pet.getOwnerName(), pet.getPrice(), pet.getBirthDate(), pet.getPetId());
        
        jdbcTemplate.update("DELETE FROM PET WHERE PET_ID=?", pet.getPetId());

        // NamedParameterJdbcTemplate를 사용
        // 메서드 체인에 기술하는 경우
        namedParameterJdbcTemplate.update(
                " INSERT INTO PET (PET_ID, PET_NAME, OWNER_NAME, PRICE, BIRTH_DATE)" +
                    " VALUES (:PET_ID, :PET_NAME, :OWNER_NAME, :PRICE, :BIRTH_DATE)"
                , new MapSqlParameterSource()
                .addValue("PET_ID", pet.getPetId())
                .addValue("PET_NAME", pet.getPetName())
                .addValue("OWNER_NAME", pet.getOwnerName())
                .addValue("PRICE", pet.getPrice())
                .addValue("BIRTH_DATE", pet.getBirthDate())
            );
        
        jdbcTemplate.update("DELETE FROM PET WHERE PET_ID=?", pet.getPetId());
        
        // 메서드 체인에 기술하지 않는 경우
        MapSqlParameterSource map2 = new MapSqlParameterSource();
        map2.addValue("PET_ID", pet.getPetId());
        map2.addValue("PET_NAME", pet.getPetName());
        map2.addValue("OWNER_NAME", pet.getOwnerName());
        map2.addValue("PRICE", pet.getPrice());
        map2.addValue("BIRTH_DATE", pet.getBirthDate());
        namedParameterJdbcTemplate.update(
            " INSERT INTO PET (PET_ID, PET_NAME, OWNER_NAME, PRICE, BIRTH_DATE)" +
                " VALUES (:PET_ID, :PET_NAME, :OWNER_NAME, :PRICE, :BIRTH_DATE)"
            ,map2
        );
        
        jdbcTemplate.update("DELETE FROM PET WHERE PET_ID=?", pet.getPetId());

        // BeanPropertySqlParameterSource를 사용해서 도메인에서 파라메터변화을 자동화
        BeanPropertySqlParameterSource beanProps = new BeanPropertySqlParameterSource(pet);
        namedParameterJdbcTemplate.update(
            " INSERT INTO PET (PET_ID, PET_NAME, OWNER_NAME, PRICE, BIRTH_DATE)" +
                " VALUES (:petId, :petName, :ownerName, :price, :birthDate)"
            ,beanProps
        );
        
        jdbcTemplate.update("DELETE FROM PET WHERE PET_ID=?", pet.getPetId());
        
        // 배치 업데이트, 프로시져 콜
        // batchUpdate 메서드를 사용한 배치 업데이트
        final ArrayList<Pet> pList = new ArrayList<Pet>();
        pet = new Pet();
        pet.setPetId(1);
        pet.setOwnerName("owner001");
        pList.add(pet);
        pet = new Pet();
        pet.setPetId(2);
        pet.setOwnerName("owner002");
        pList.add(pet);
        pet = new Pet();
        pet.setPetId(3);
        pet.setOwnerName("owner003");
        
        pList.add(pet);
                
        //IN구
        List<Integer> ids = new ArrayList<Integer>();
        ids.add(1);
        ids.add(2);
        ids.add(3);
        MapSqlParameterSource param = new MapSqlParameterSource();
        param.addValue("ids", ids);
        petList = namedParameterJdbcTemplate.query(
            "SELECT * FROM PET WHERE PET_ID IN (:ids)", 
            param, 
            new RowMapper<Pet>() {
                public Pet mapRow(ResultSet rs, int rowNum) throws SQLException {
                    Pet p = new Pet();
                    p.setPetId(rs.getInt("PET_ID"));
                    p.setPetName(rs.getString("PET_NAME"));
                    p.setOwnerName(rs.getString("OWNER_NAME"));
                    p.setPrice((Integer)rs.getObject("PRICE"));
                    p.setBirthDate(rs.getDate("BIRTH_DATE"));
                    return p;
                }
            }
        );
        
        System.out.println("IN구");
        for (Pet p : petList) {
            System.out.println(p.getPetId());
        }
        
        
        //JdbcTemplate의 batchUpdate
        int[] num = jdbcTemplate.batchUpdate("UPDATE PET SET OWNER_NAME=? WHERE PET_ID=?", new BatchPreparedStatementSetter() {            
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                ps.setString(1, pList.get(i).getOwnerName());
                ps.setInt(2, pList.get(i).getPetId());
            }
            @Override
            public int getBatchSize() {
                return pList.size();
            }
        });
        
        //NamedParameterJdbcTemplateのbatchUpdate
        SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(pList.toArray());
        num = namedParameterJdbcTemplate.batchUpdate(
                "UPDATE PET SET OWNER_NAME=:ownerName WHERE PET_ID=:petId", batch);        
        
        
        // SimpleJdbcCall을 사용한 프로시져 콜
        SimpleJdbcCall call = new SimpleJdbcCall(jdbcTemplate.getDataSource())
        .withProcedureName("CALC_PET_PRICE")
        .withoutProcedureColumnMetaDataAccess()
        .declareParameters(
            new SqlParameter("IN_PET_ID", Types.INTEGER),
            new SqlOutParameter("OUT_PRICE", Types.INTEGER)
        );        
        MapSqlParameterSource in = new MapSqlParameterSource()
                        .addValue("IN_PET_ID", id);
        Map<String, Object> out = call.execute(in);
        int price = (Integer)out.get("OUT_PRICE");
        System.out.println(price);
    }
    
}

 

위의 여러 JDBC를 이용해서 SQL문 작성 예 2

package sample;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;

import sample.config.JndiConfig;
import sample.config.TemplateConfig;


public class JndiMain {
    
    public static void main(String[] args) throws Exception {
    	// 데이터소스의 오브젝트를 생성
        BasicDataSource ds = new BasicDataSource();
        ds.setDriverClassName("org.hsqldb.jdbc.JDBCDriver");
        ds.setUrl("jdbc:hsqldb:mem:sample");
        ds.setUsername("sa");
        ds.setPassword("");
        datainitialize(ds);

        // Spring이 제공하는 JNDI구현(테스트 용)을 사용해서, 네이밍컨테키스를 생성
        SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
        builder.bind("jdbc/MyDataSource", ds);
        builder.activate();
        
        // Bean정의 파일을 사용해서, JNDI경우로 테이터 소스 취득
        //ApplicationContext ctx = new ClassPathXmlApplicationContext("sample/config/spring-jndi.xml");
        // JavaConfig를 사용해서, JNDI경우로 데이터 소스 취득
        ApplicationContext ctx = new AnnotationConfigApplicationContext(TemplateConfig.class, JndiConfig.class);
        
        JdbcTemplate jdbcTemplate = ctx.getBean(JdbcTemplate.class);
                
        int count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM PET", Integer.class);
        System.out.println(count);
        
        
    }
    
    private static void datainitialize(DataSource ds) {
        ResourceDatabasePopulator p = new ResourceDatabasePopulator();
        p.addScripts(
                new ClassPathResource("/script/table.sql"),
                new ClassPathResource("/script/data.sql")
                );
        p.execute(ds);
        p = new ResourceDatabasePopulator();
        p.addScript(
                new ClassPathResource("/script/proc.sql")
                );
        p.setSeparator("/");
        p.execute(ds);
        
    }
    
}

 

파일구조

예제파일

springjdbc.zip
0.06MB

 

 

 

 

참고: 스프링 4입문 (한빛미디어)

'웹개발 > 스프링' 카테고리의 다른 글

스프링 4 MVC - part 1  (0) 2020.11.10
스프링 4 비즈니스 로직 설계와 트랜잭션  (0) 2020.11.09
스프링 4 AOP  (0) 2020.11.08
스프링 4 Bean Config  (0) 2020.11.07
스프링 4 DI  (0) 2020.11.07