ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링3.0 - Database 연동하기
    웹프로그래밍/spring 2018. 7. 3. 23:07

    JDBC API를 이용해서 Database에 접근할 수 있으며, iBatis나 Hibernate, JPA와 같은 ORM Framework를 이용해서 Database를 연동할 수도 있다. SPRING은 JDBC를 비롯하여 ORM Framework를 직접적으로 지원하고 있기 때문에 약간의 노력만 들이면 JDBC뿐만 아니라 다른 ORM Framework를 SPRING과 간단하게 연동할 수 있다.



    : SPRING은 JDBC, Hibernate, iBatis 등의 다양한 기술을 이용해서 손쉽게 DAO Class를 구현할 수 있도록 지원하고 있는데, 지원하는 내용은 다음과 같다.

         - 템플릿 Class를 통한 데이터 접근 지원

         - 의미 있는 예외 Class 제공

         - Transaction 처리



    예를 들어, JDBC를 사용할 경우 특정 테이블에서 데이터를 로딩하는 Code는 다음과 같은 형식을 취하게 된다.

    Connection을 생성하고, PreparedStatement, ResultSet, 그리고 Connection 등의 Resource을 반환하는 Code는 거의 모든 JDBC Code에서 중복되는 Code이다. 뿐만 아니라, PreparedStatement를 구하고 ResultSet으로 부터 데이터를 읽어와 Java 빈 객체에 저장하는 Code 역시 동일한 형식을 취한다.


     SPRING은 Database 연동을 위한 템플릿 Class를 제공함으로써, 개발자가 중복된 Code를 입력해야하는 성가신 작업을 줄일 수 있도록 돕고 있다. JDBC뿐만 아니라 iBatis, JMS와 같은 다양한 기술에 대해 템플릿 Class를 제공하고 있다.







    -SPRING의 예외 Class 지원


    JdbcTemplate Class는 처리과정에서 SQLException이 발생하면 SPRING이 제공하는 예외 Class 중 알맞은 예외 Class로 변환해서 발생시킨다. 예를 들어, 아래 Code와 같이 올바르지 않은 SQL 쿼리를 실행하는 경우 JdbcTemplate은 BadSqlGrammerException 예외를 발생시킨다.


    SPRING이 제공하는 Database  관련 예외 Class들은 모두 DataAccessException Class를 상속받고 있는데, DataAccessException은 RuntimeException이다.

    예외 처리가 필수적이지 않다. 따라서, 필요한 경우에만 try~catch 블럭을 이용해서 예외를 처리하면 된다.

    SPRING이 제공하는 템플릿 Class를 사용하면 Database 연동을 위해 사용하는 기술에 상관 없이 동일한 방식으로 예외처리를 할 수 있게 된다. 








    -DataSource 설정 (DB 커넥션)


    DataSupport Class나 템플릿 Class 그리고 Hibernate나 iBatis와 같은 Framework와의 연동을 위해 제공되는 Class를 사용할 경우 SPRING은 DataSource를 통해서 Connection을 제공할 수 있다.



    1. 커넥션 풀을 이용한 DataSource 설정

    DBCP(Jakarta Commons Database Connection Pool) API와 같이 커넥션 풀 Library를 이용해서 커넥션 풀 기반의 DataSource를 설정할 수 있다. 아래 Code는 DBCP가 제공하는 BasicDataSource Class를 이용해서 DataSource를 설정하는 예이다.

    BasicDataSource Class는 커넥션 풀을 관리하기 위한 다양한 프로퍼티를 제공한다.


    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"

            p:driverClassName="oracle.jdbc.driver.OracleDriver"

            p:url="jdbc:oracle:thin:@192.168.107.100:1521:ORCL"

            p:username="spring"

            p:password="spring"/>




    DBCP와 함께 널리 사용되는 커넥션 풀 API인 c3p0 Library를 사용해서 DataSource를 설정할 수도 있다. 아래 Code는 c3p0가 제공하는

    ComboPooledDataSource를 이용해서 DataSource를 설정하는 예제 Code이다.

    ComboPooledDataSource Class 역시 커넥션 풀을 관리하기 위한 다양한 프로퍼티를 제공한다.



          <bean id="dataSource" class="com.mchange.v2.c3p0.comboPooledDataSource" destroy-method="close"

            p:driverClassName="oracle.jdbc.driver.OracleDriver"

            p:url="jdbc:oracle:thin:@localhost:1521:ORCL"

            p:username="nals"

            p:password="dkagh1234."/>

            

    2. JNDI를 이용한 DataSource 설정

    Weblogic이나 JBoss와 같은 JEE Application 서버를 이용할 경우, JNDI를 이용해서 DataSource를 구하곤 한다. 심지어 톰캣이나 Resin등의 

    WEB Container를 사용하는 경우에도 JNDI로부터 DataSource를 구하도록 설정이 가능하다.



    JNDI로부터 DataSource를 가져오고 싶다면, 다음과 같이 <jee:jndi-lookup> 태그를 이용해서 JNDI에 등록된 객체의 이름을 명시하면 된다.

    <jee:jndi-lookup> 태그는 SPRING 2.0버전부터 지원하며, 이 태그를 사용하기 위해서는 jee 네임스페이스 및 관련 XML 스키마로 등록해 주어야 한다.


      <?xml version="1.0" encoding="UTF-8"?>

    <beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:jee="http://www.springframework.org/schema/jee" 

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    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">

           

    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/guestbook" resource-ref="true"/>

    <bean id="messageDao" class="kame.spring.guestbook.dao.jdbc.JdbcMessageDao" p:dataSource-ref="dataSource"/>

    </beans>



    <jee:jndi-lookup> 태그를 사용하지 않고 다음과 같이 JndiObjectFactoryBean Class를 이용해서 JNDI로 부터 DataSource를 구하도록 설정할 수 있다.


    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">

    <property name="jndiName" value="jdbc/guestbook"/>

    <property name="resourceRef" value="true"/>

    </bean>



    3.  DriverManager를 이용한 DataSource 설정


    커넥션 풀이나 JNDI를 사용할 수 없는 경우에는 DriverManager를 이용해서 커넥션을 제공하는 DriverManagerDataSource Class를 사용할 수 있다.

    설정 방법은 다음과 같다.

           

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"

    p:driverClassName="oracle.jdbc.driver.OracleDriver"

            p:url="jdbc:oracle:thin:@localhost:1521:ORCL"

            p:username="nals"        

            p:password="dkagh1234." />






    -DataSource로부터 Connection 구하기


    SPRING은 JDBC를 위한 템플릿 Class와 DAO 지원 Class를 제공하고 있기 때문에, SPRING을 사용하면서 직접적으로 Connection을 이용해서 JDBC

    프로그래밍을 해야하는 경우가 많지는 않다. 하지만, 외부에서 제공한 Module이 Connection을 파라미터로 전달받는다면, 어쩔 수 없이 DataSource로부터 Connection을 구해서 해당 Module에 제공해야 할 것이다.

    DataSource로부터 Connection을 직접 구해야 하는 경우, 다음과 같이 앞서 설정한 DataSource의 getConnection() 메서드를 사용해서 Connection을 

    구할 수 있을 것이다.


    public class JdbcMessageDao{

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource){

    this.dataSource=dataSource;

    }

    @Override

    public int selectCount(){

    Connection conn = null;

    ...

    try{

    conn = dataSource.getConnection();

    ...

    }finally{

    JdbcUtils.closeConnection(conn);

    }

    }

    }

    하지만, DataSource에서 직접 Connection을 구하게 되면, SPRING이 제공하는 Transaction 관리 기능을 완전히 활용할 수 없게 된다. 만약, SPRING이 제공하는 Transaction 기능을 올바르게 적용하고 싶다면 SPRING이 제공하는 DataSourceUtils Class를 이용해서 Connection을 구하고 반환해야 한다.

    try{

    conn = DataSourceUtils.getConnection(dataSource);

    ...

    }finally{

    DataSourceUtils.releaseConnection(conn,dataSource);

    }







    -SPRING JDBC 지원 


    JDBC를 이용해서 프로그래밍을 할때 성가신 작업중의 하나는 늘 동일한 형태의 try~catch~finally 블록을 사용해야 한다는 점이다.

    Connection을 구하고, try~catch~finally로 Resource을 관리하는 등의 중복된 Code를 매번 입력하는 것은 꽤 성가신 일이다. SPRING은 이런 중복되는 

    Code를 제거할 수 있도록 해 주는 템플릿 Class를 제공한다.


    - JdbcTemplate : 기본적인 JDBC 템플릿 Class로서 JDBC를 이용해서 데이터에 대한 접근을 제공한다.


    - NamedParameterJdbcTemplate : PreparedStatement에서 인덱스 기반의 파라미터가 아닌 이름을 가진 파라미터를 사용할 수 있도록 지원하는 템플릿 Class.

    - SimpleJdbcTemplate : Java5의 가변 인자를 이용해서 쿼리를 실행할 때 사용되는 데이터를 전달할 수 있는 템플릿 Class.


    - SimpleJdbcInsert : 데이터 삽입을 위한 Interface를 제공해주는 Class. 


    - SimpleJdbcCall : 프로시저 호출을 위한 Interface를 제공해 주는 Class.





    -JdbcTemplate Class를 이용한 JDBC 프로그래밍


    JdbcTemplate Class는 SQL 실행을 위한 메서드를 제공하고 있다. 이들 메서드를 사용하면 데이터 조회, 삽입, 수정, 삭제를 위한 SQL 쿼리를 실행할 수 있다.


    JdbcTemplate Class를 사용하려면 다음과 같이 JdbcTemplate 객체를 생성할 때 DataSource를 전달해주면 된다.


            public class JdbcTemplateGuestMessageDao implements GuestMessageDao {

    private JdbcTemplate jdbcTemplate;

    public JdbcTemplateGuestMessageDao(DataSource dataSource) {

    jdbcTemplate = new JdbcTemplate(dataSource);

    }

    ...

    }

    위 Class에 대한 SPRING 설정 파일은 다음과 같을 것이다.


    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" 

    p:driverClassName="oracle.jdbc.driver.OracleDriver"

            p:url="jdbc:oracle:thin:@localhost:1521:ORCL"

            p:username="nals"

            p:password="dkagh1234." />

    <bean id="guestMessageDao" class="madvirus.spring.chap08.dao.JdbcTemplateGuestMessageDao">

    <constructor-arg>

    <ref bean="dataSource" />

    </constructor-arg>

    </bean>



    다음과 같이 JdbcTemplate Class를 전달받도록 구현할 수도 있다.



    public class JdbcTemplateGuestMessageDao implements GuestMessageDao {

    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate){

    this.jdbcTemplate = jdbcTemplate;

    }

    ...

    }

    DAO Class에서 JdbcTemplate을 프로퍼티나 생성자에서 전달받을 경우, SPRING 설정파일에서는 다음과 같이 설정해주면 된다.


    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" .../>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource"/>

          <bean id="guestMessageDao" class="madvirus.spring.chap08.dao.JdbcTemplateGuestMessageDao" p:jdbcTemplate-ref="jdbcTemplate"/>






    (1) 조회를 위한 메서드

    query(), queryForList(), queryForObject(), queryForInt(), queryForLong()


      쿼리 실행 결과를 객체 목록으로 가져올 때에는 RowMapper를 이용하는 query() 메서드를 이용하면 된다. JdbcTemplate Class는 다음과 같은 query()

      메서드를 제공하고 있다.


       - query(String sql, RowMapper<?> rowMapper)

       - List<T> query(String sql, Object[] args, RowMapper<T> rowMapper)

       - List<T> query(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper)

             

    위 Code에서 sql 파라미터는 실행할 쿼리를, RowMapper는 실행 결과를 Java 객체로 변환해주는 매퍼를, args 파라미터는 PreparedStatement를 실행할 때 사용할 파라미터 바인딩 값 목록을, argTypes는 파라미터 바인딩을 할 때 사용할 SQL 타입 목록을 의미한다. argTypes에 사용되는 값은 java.sql.Types Class에 정의된 값을 사용한다.


    (2) 삽입/수정/삭제를 위한 메서드 : 

    update()

    INSERT, UPDATE, DELETE 쿼리를 실행할 때에는 update() 메서드를 사용하면 된다. update() 메서드도 query() 메서드와 마찬가지로 인덱스 파라미터를 위한 값을 전달받는 메서드와 그렇지 않은 메서드로 구분된다.


             - int update(String sql)

             - int update(String sql, Object ... args)

             - int update(String sql, Object[] args, int[] argTypes)





    -NamedParameterJdbcTemplate Class를 이용한 JDBC 프로그래밍


    NamedParameterJdbcTemplate Class는 JdbcTemplate Class와 비슷한 기능을 제공하는데, 차이점이 있다면 인덱스 기반의 파라미터가 아니라 이름 기반의 파라미터를 설정할 수 있도록 해준다는 점이다. 예를 들어, 인덱스 기반의 파라미터를 전달받는 물음표를 사용하지 않고 다음과 같이 이름 기반의 파라미터를 쿼리에서 사용할 수 있도록 지원한다.


    select * from GUESTBOOK_MESSAGE order by MESSAGE_ID desc limit :startRow, :fetchSize



     NamedParameterJdbcTemplate Class는 다음과 같이 생성자를 이용해서 DataSource를 전달받는다.


            public class NamedParamGuestMessageDao implements GuestMessageDao {

    private NamedParameterJdbcTemplate template;

    public NamedParamGuestMessageDao(DataSource dataSource) {

    template = new NamedParameterJdbcTemplate(dataSource);

    }

    ...

    }



    (1) Map을 이용한 파라미터 값 설정 메서드


    Map 기반의 메서드는 Object 배열이 아닌 Map을 이용해서 이름을 가진 파라미터 값을 설정한다. 아래 Code는 Map 기반 메서드와 이름 기반의 파라미터를 갖는 SQL 쿼리의 사용 예이다.


                @Override

    public int delete(int id) {

    Map<String, Object> paramMap = new HashMap<String, Object>();

    paramMap.put("id", id);

    return template.update(

    "delete from GUESTBOOK_MESSAGE where MESSAGE_ID = :id",

    paramMap);

    }


    이름 기반의 파라미터를 갖지 않는 쿼리를 실행하는 경우에는 아무 값도 갖지 않는 Map 객체를 사용하면 된다.


    int id = tempate.queryForInt("select last_insert_id() ", Collections.<String, Object> emptyMap());


    NamedParameterJdbcTemplate Class가 제공하는 Map 기반 메서드는 다음과 같다.


    - List<T> query(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper)

    - List<T> queryForList(String sql, Map<String, ?> paramMap, Class<T> elementType)

    - T queryForObject(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper)

    - T queryForObject(String sql, Map<String, ?> paramMap, Class<T> requiredType)

    - int queryForInt(String sql, Map<String, ?> paramMap)

    - long queryForLong(String sql, Map<String, ?> paramMap)

    - int update(String sql, Map<String, ?> paramMap)





    (2) SqlParameterSource를 이용한 파라미터 값 설정 메서드


    SqlParameterSource는 Interface이기 때문에 실제로 사용할 때에는 SqlParameterSource Interface를 구현한 Class를 사용해서 파라미터 값을 전달해 주어야 한다. SPRING은 다음과 같은 두 개의 SqlParameterSource 구현 Class를 제공하고 있다.


           - org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource

           - org.springframework.jdbc.core.namedparam.MapSqlParameterSource

           

    BeanPropertySqlParameterSource Class는 동일한 이름을 갖는 Java 객체의 프로퍼티 값을 이용해서 파라미터 값을 설정한다. 아래 Code는 BeanPropertySqlParameterSource Class의 사용 예를 보여주고 있다.



            @Override

    public int insert(GuestMessage message) {

    BeanPropertySqlParameterSource paramSource = new BeanPropertySqlParameterSource(

    message);

    int insertedCount = template.update(

    "insert into GUESTBOOK_MESSAGE (GUEST_NAME, MESSAGE, REGISTRY_DATE) values "

    + "(:guestName, :message, :registryDate)", paramSource);

    ...

    return insertedCount;

    }


    위 Code에서 쿼리에 포함된 guestName, message, registryDate 파라미터는 각각 message 객체의 guestName 프로퍼티, message 프로퍼티, 그리고registryDate 프로퍼티 값을 이용해서 설정된다.


    다음 메서드는 SqlParameterSource를 인자로 전달받는 메서드의 목록이다.


               - List<T> query(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper)

       - List<T> queryForList(String sql, SqlParameterSource paramSource, Class<T> elementType)

       - T queryForObject(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper)

       - T queryForObject(String sql, SqlParameterSource paramSource, Class<T> requiredType)

       - int queryForInt(String sql, SqlParameterSource paramSource)

       - long queryForLong(String sql, SqlParameterSource paramSource)

       - int update(String sql, SqlParameterSource paramSource)