본문 바로가기

Spring

Spring JDBC

1. JDBC와 SPRING JDBC API
JDBC             SPRING JDBC
DriverManager / DataSource        DataSource
Statement / PreparedStatement / CallableStatement  JdbcTemplate / SQLObject
ResultSet / RowSet          POJO List OR Map/SQLRowSet


2. MyJdbcDaoSupport 개발
Spring 프레임워크는 모든 DAO 클래스에서 공통적으로 필요한 속성과 기능들(EX : connection 등)을 포함하고 있는 Base 클래스로
org.springframewrok.jdbc.core.support.JdbcDaoSupport를 지원한다. 이 클래스는 DataSource를 관리 할 뿐 아니라 JdbcTemplate 클래스에
접근 가능하도록 지원하기도 한다.

이 책에서는 예제 쿼리를 MessageSource를 이용하여 구현하고 있고, 따라서 모든 DAO클래스가 이 MessageSource에 모두 접근해야 하므로
이처럼 공통적으로 필요로 하는 속성들을 가지는 Base클래스를 위 클래스를 상속하여 만든다.

package net.javajigi.common.dao;

import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;

public class MyJdbcDaoSupport extends JdbcDaoSupport {
    private MessageSourceAccessor messageSourceAccessor;

    private DataFieldMaxValueIncrementer incrementer;

    public void setIncrementer(DataFieldMaxValueIncrementer incrementer) {
        this.incrementer = incrementer;
    }
   
    protected final DataFieldMaxValueIncrementer getIncrementer() {
        return incrementer;       
    }

    public void setMessageSourceAccessor(
            MessageSourceAccessor newMessageSourceAccessor) {
        this.messageSourceAccessor = newMessageSourceAccessor;
    }
   
    protected final MessageSourceAccessor getMessageSourceAccessor() {
        return messageSourceAccessor;
    }
}

- 메시지소스 접근 위한 메소드와 시퀀스를 사용하기 위한 메소드를 추가 구현


3. BoardDAO, BoardFileDAO 클래스
- JdbcTemplate나 SQLObjects 둘 중 하나를 이용하여 개발.

 1) JdbcTemplate 기반
  - 모든 쿼리를 JdbcTempate 클래스가 가지고 있는 메소드를 이용하여 실행

public class SpringJDBCWithTemplateBoardDAO extends MyJdbcDaoSupport implements
  BoardDAO {

 public Board insert(Board board) throws DataAccessException {
  String sql = getMessageSourceAccessor().getMessage("board.sql.insert");
  int boardNo = getIncrementer().nextIntValue();
  board.setBoardNo(boardNo);
  Object[] values = { new Integer(boardNo), board.getTitle(),
    board.getName(), board.getEmail(), board.getPassword(),
    board.getCreateDate(), board.getContent() };

  getJdbcTemplate().update(sql, values);

  return board;
 }
 
 ....
 
  public Board findBoard(int boardNo) throws DataAccessException {
  String sql = getMessageSourceAccessor().getMessage(
    "board.sql.select.byboardno");

  RowMapper rowMapper = new RowMapper() {
   public Object mapRow(ResultSet rs, int rownum) throws SQLException {
    Board board = new Board();
    board.setTitle(rs.getString("title"));
    board.setName(rs.getString("name"));
    board.setEmail(rs.getString("email"));
    board.setPassword(rs.getString("password"));
    board.setCreateDate(rs.getString("createdate"));
    board.setContent(rs.getString("content"));
    board.setHitCount(rs.getInt("hitCount"));

    return board;
   }
  };

  Board board = (Board) getJdbcTemplate().queryForObject(sql,
    new Object[] { new Integer(boardNo) }, rowMapper);
  board.setBoardNo(boardNo);

  return board;
 }
 
 ....
 
 public List findBoardList(int currentPage, int countPerPage)
   throws DataAccessException {
  int start = (currentPage - 1) * countPerPage;

  String sql = getMessageSourceAccessor().getMessage(
    "board.sql.select.list");

  RowMapper rowMapper = new RowMapper() {
   public Object mapRow(ResultSet rs, int rownum) throws SQLException {
    Board board = new Board();
    board.setBoardNo(rs.getInt("boardNo"));
    board.setTitle(rs.getString("title"));
    board.setName(rs.getString("name"));
    board.setEmail(rs.getString("email"));
    board.setCreateDate(rs.getString("createdate"));
    board.setHitCount(rs.getInt("hitCount"));

    return board;
   }
  };

  List list = getJdbcTemplate().query(sql,
    new Object[] { new Integer(start), new Integer(countPerPage) },
    rowMapper);

  return list;
 }
}

 2) Insert, Update, Delete
  - int update(String sql)
  - int update(String sql, Object[] args) -- 매핑 데이터
  - int update(String sql, Object[] args, int[] argTypes)
 
 3) Select
  - int queryForInt(String sql)
  - int queryForObject(String sql, Object[] args, RowMapper rowMapper) -- RowMapper innerClass에서 각 컬럼과 pojo를 매핑하여 놓고
  이를 이 메소드에 넘겨서 board pojo클래스에 매핑한다. Object에는 게시판번호를 넘겨준다.
  ... 외 다수
  - List queryForList(String sql, Object[] args, RowMapper rowMapper) -- object에 start row, countPerpage를 주면
  그 만큼만 넘겨준다. 물론 rowMapper에 맞춰서
 
 4) 콜백메소드 ?? (위 rowMapper 메소드..) - 그냥 뭔가 실행되고 호출되는 메소드
 
 5) SQLObjects를 이용하여 구현
  - 별도의 클래스를 이용하는 방법으로 하나의 쿼리를 실행하기 위하여 Spring JDBC에서 지원하는 클래스를 상속하는
    새로운 클래스를 생성하는 방법 (지원하는 클래스 정보는 : org.springframework.jdbc.object)
   
   
 /**
 * 이 소스는 Spring 프레임워크 워크북에서 사용한 예제 소스입니다.
 * 이 소스는 모든 개발자들이 자유롭게 수정 및 배포할 수 있습니다.
 * 단, 이 소스를 기반으로 새로운 애플리케이션을 개발할 경우 출처를 명시해 주시면 됩니다.
 */
package net.javajigi.board.dao;


public class SpringJDBCWithMSBoardDAO extends MyJdbcDaoSupport implements
        BoardDAO {
    private Insert insert; // 내부 클래스

    private Update update; // 내부 클래스

    private Delete delete; // 내부 클래스

    private UpdateHitCount updateHitCount; // 내부 클래스

    private SelectByBoardNo selectByBoardNo; // 내부 클래스

    private SelectList selectList; // 내부 클래스

    protected void initDao() throws Exception {
        super.initDao();

        insert = new Insert(getDataSource());
        update = new Update(getDataSource());
        updateHitCount = new UpdateHitCount(getDataSource());
        delete = new Delete(getDataSource());
        selectByBoardNo = new SelectByBoardNo(getDataSource());
        selectList = new SelectList(getDataSource());
    } //내부 클래스 초기화

    public int delete(int boardNo) throws DataAccessException {
        return delete.update(boardNo);
    }

    public Board findBoard(int boardNo) throws DataAccessException {
        Board board = (Board) selectByBoardNo.findObject(boardNo);
        board.setBoardNo(boardNo);

        return board;
    }

    public List findBoardList(int currentPage, int countPerPage)
            throws DataAccessException {
        int start = (currentPage - 1) * countPerPage;

        return selectList.execute(start, countPerPage);
    }

    public int getTotalNo() throws DataAccessException {
        String sql = getMessageSourceAccessor().getMessage(
                "board.sql.totalcount");
        return getJdbcTemplate().queryForInt(sql);
    }

    public Board insert(Board board) throws DataAccessException {
        int boardNo = getIncrementer().nextIntValue();
        board.setBoardNo(boardNo);

        Object[] values = { new Integer(boardNo), board.getTitle(),
                board.getName(), board.getEmail(), board.getPassword(),
                board.getCreateDate(), board.getContent() };
        insert.update(values);

        return board;
    }

    public Board update(Board board) throws DataAccessException {
        Object[] values = { board.getTitle(), board.getName(),
                board.getEmail(), board.getContent(),
                new Integer(board.getBoardNo()) };
        update.update(values);

        return board;
    }

    public int updateHitCount(int boardNo) throws DataAccessException {
        return updateHitCount.update(boardNo);
    }

    protected class SelectList extends MappingSqlQuery { //리스트를 가져오는 쿼리는 MappingSqlQuery를 상속
        protected SelectList(DataSource ds) { // 위에서 넘겨준 datasource
            super(ds, getMessageSourceAccessor().getMessage(
                    "board.sql.select.list"));
            declareParameter(new SqlParameter(Types.INTEGER));
            declareParameter(new SqlParameter(Types.INTEGER));
        }

        protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
            Board board = new Board();
            board.setBoardNo(rs.getInt("boardNo"));
            board.setTitle(rs.getString("title"));
            board.setName(rs.getString("name"));
            board.setEmail(rs.getString("email"));
            board.setCreateDate(rs.getString("createdate"));
            board.setHitCount(rs.getInt("hitCount"));

            return board;
        }
    }

    protected class SelectByBoardNo extends MappingSqlQuery {
        protected SelectByBoardNo(DataSource ds) {
            super(ds, getMessageSourceAccessor().getMessage(
                    "board.sql.select.byboardno"));
            declareParameter(new SqlParameter(Types.INTEGER));
        }

        protected Object mapRow(ResultSet rs, int rownum) throws SQLException {
            Board board = new Board();
            board.setTitle(rs.getString("title"));
            board.setName(rs.getString("name"));
            board.setEmail(rs.getString("email"));
            board.setPassword(rs.getString("password"));
            board.setCreateDate(rs.getString("createdate"));
            board.setContent(rs.getString("content"));
            board.setHitCount(rs.getInt("hitCount"));

            return board;
        }
    }

    class Insert extends SqlUpdate { // 트랜잭션이 있는 쿼리 실행은 SqlUpdate를 상속 update(Object) 메서드는 parent것.
        public Insert(DataSource dataSource) { //여기서는 생성자를 통해 쿼리와 타입만 재설정 해준다.
            super(dataSource, getMessageSourceAccessor().getMessage(
                    "board.sql.insert"));

            declareParameter(new SqlParameter(Types.INTEGER));
            declareParameter(new SqlParameter(Types.VARCHAR));
            declareParameter(new SqlParameter(Types.VARCHAR));
            declareParameter(new SqlParameter(Types.VARCHAR));
            declareParameter(new SqlParameter(Types.VARCHAR));
            declareParameter(new SqlParameter(Types.VARCHAR));
            declareParameter(new SqlParameter(Types.VARCHAR));
        }
    }
 
    class Update extends SqlUpdate { // 트랜잭션이 있는 쿼리 실행은 SqlUpdate를 상속 update(Object) 메서드는 parent것.
        public Update(DataSource dataSource) { //여기서는 생성자를 통해 쿼리와 타입만 재설정 해준다.
            super(dataSource, getMessageSourceAccessor().getMessage(
                    "board.sql.update"));

            declareParameter(new SqlParameter(Types.VARCHAR));
            declareParameter(new SqlParameter(Types.VARCHAR));
            declareParameter(new SqlParameter(Types.VARCHAR));
            declareParameter(new SqlParameter(Types.VARCHAR));
            declareParameter(new SqlParameter(Types.INTEGER));
        }
    }

    class UpdateHitCount extends SqlUpdate { // 트랜잭션이 있는 쿼리 실행은 SqlUpdate를 상속 update(Object) 메서드는 parent것.
        public UpdateHitCount(DataSource dataSource) { //여기서는 생성자를 통해 쿼리와 타입만 재설정 해준다.
            super(dataSource, getMessageSourceAccessor().getMessage(
                    "board.sql.update.hitcount"));

            declareParameter(new SqlParameter(Types.INTEGER));
        }
    }

    class Delete extends SqlUpdate { // 트랜잭션이 있는 쿼리 실행은 SqlUpdate를 상속
        public Delete(DataSource dataSource) {
            super(dataSource, getMessageSourceAccessor().getMessage(
                    "board.sql.delete"));

            declareParameter(new SqlParameter(Types.INTEGER));
        }
    }
}

//excute와 update 메소드는 상속되어진 메소드인듯함..

10.4.2. MappingSqlQuery
  private class CustomerMappingQuery extends MappingSqlQuery {
    public CustomerMappingQuery(DataSource ds) {
      super(ds, "SELECT id, name FROM customer WHERE id = ?");
      super.declareParameter(new SqlParameter("id", Types.INTEGER));
      compile();
    }
    public Object mapRow(ResultSet rs, int rowNumber) throws SQLException {
      Customer cust = new Customer();
      cust.setId((Integer) rs.getObject("id"));
      cust.setName(rs.getString("name"));
      return cust;
    }
  }
//모든 파라미터가 compile 메소드를 호출해서 정의
 
 public Customer getCustomer(Integer id) {
    CustomerMappingQuery custQry = new CustomerMappingQuery(dataSource);
    Object[] parms = new Object[1];
    parms[0] = id;
    List customers = custQry.execute(parms);
    if (customers.size() > 0)
        return (Customer) customers.get(0);
    else
        return null;
}

10.4.3. SqlUpdate
import java.sql.Types;

import javax.sql.DataSource;

import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlUpdate;

public class UpdateCreditRating  extends SqlUpdate {
    public UpdateCreditRating(DataSource ds) {
        setDataSource(ds);
        setSql("update customer set credit_rating = ? where id = ?");
        declareParameter(new SqlParameter(Types.NUMERIC));
        declareParameter(new SqlParameter(Types.NUMERIC));
        compile();
    }

    /**
     * @param id for the Customer to be updated
     * @param rating the new value for credit rating
     * @return number of rows updated
     */
    public int run(int id, int rating) {
        Object[] params =
            new Object[] {
                new Integer(rating),
                new Integer(id)};
        return update(params);
    }
}


4. 쿼리 관리
- applicationContext.xml
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
  <property name="basenames">
   <list>
    <value>DBQuery</value>
    <value>Messages</value>
   </list>
  </property>
 </bean>

 <bean id="messageSourceAccessor" class="org.springframework.context.support.MessageSourceAccessor">
  <constructor-arg>
   <ref local="messageSource" />
  </constructor-arg>
 </bean>

- applicationContext-jdbc.xml
<bean id="myJdbcDaoSupport" abstract="true"
  class="net.javajigi.common.dao.MyJdbcDaoSupport">
  <property name="dataSource">
   <ref local="dataSource"/>
  </property>
  <property name="messageSourceAccessor">
   <ref bean="messageSourceAccessor"/>
  </property>
 </bean>
 
- applicationContext-board.xml
 <bean id="boardFileDAOTarget" parent="myJdbcDaoSupport" class="net.javajigi.board.dao.SpringJDBCWithMSBoardFileDAO">
  <property name="incrementer">
   <ref local="boardFileIncrementer" />
  </property>
 </bean
 
 <bean id="boardDAOTarget" parent="myJdbcDaoSupport" class="net.javajigi.board.dao.SpringJDBCWithTemplateBoardDAO">
  <property name="incrementer">
   <ref local="boardIncrementer" />
  </property>
 </bean>

TIP ref local 과 local는 같은 xml에 존재하느냐 아니냐의 차이임


공통정보를 명시해준다.
의문은 class 구현시에 extends를 이미 시켜주고 있는데
이런 설정이 필요할까 하는것..

중복으로 써주는게 아닐까..



-- 출처 : 나