1. JdbcTemplate이란?


Spring Framework로 앱을 제작할 때 Data Access Layer에서 사용하는 기술은 크게 3가지입니다.

  1. 1. JPA (구현체로 Hibernate를 많이 사용합니다.)
  2. 2. MyBatis (구 iBatis)
  3. 3. JdbcTemplate

요즘은 JPA가 많이 사용된다고 알고 있습니다. 뭐.. 그래도.. 지금 보고있는 Spring Framework 입문 책에서 JdbcTemplate 객체를 가지고 Data Access를 처리하고, 또 Spring Framework 초창기부터 존재해왔다니 나름의 역사도 있는 JdbcTemplate 클래스를 공부할 필요는 있는 것 같습니다. 먼저 Java로 가장 Low하게 Data Access를 처리하기 위해선 java.sql.*에 있는 API를 사용합니다.

public Member selectByEmail(Connection conn, String email) throws SQLException {
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    Member = member = null;
    try {
        pstmt = conn.prepareStatement("select * from member where email=?");
        pstmt.setString(1, email);
        rs = pstmt.executeQuery();
        if (rs.next()) {
            member = toMember(rs);
        }
        return member;
    } finally {
        JdbcUtil.close(pstmt);
        JdbcUtil.close(rs);
    }
}

위 코드는 java api로 DB에 쿼리를 날리는 통용된 코드입니다.

  1. Connection 객체로부터 PreparedStatement 객체를 가져옵니다.
  2. PreparedStatement 객체에 query를 세팅하고, 넘길 파라미터 값들을 setXX() 메서드로 세팅해줍니다.
  3. 세팅한 query를 DB로 날려서, 결과값을 ResultSet 객체로 담아옵니다.

그런데 위와 같은 jdbc 코드를 사용해보신 분들은 아시겠지만, 중복되는 코드가 아주 많이 발생합니다.

insert, update, delete, select 메서드를 작성해놓고 보면 정작 중복되는 코드가 80~90퍼센트입니다.. 지루하죠.. 😒

이러한 문제를 해결하기 위해 Spring Framework는 초창기 시절부터 JdbcTemplate 클래스를 지원하고 있습니다.

 

JdbcTemplate 객체는 query문을 날리기 위해 아래의 메서드를 지원합니다. (물론 더 있지만, 대부분 사용되는건 이 2개입니다.)

    1. query() 메서드: select 쿼리를 지원한다.

    2. update() 메서드: insert, update, delete 쿼리를 지원한다.

 

update() 메서드를 이용해 update 쿼리문을 날리는 코드는 다음과 같이 작성합니다.

public void update(Member member) {
    jdbcTemplate.update(
        "update MEMBER set NAME = ?, PASSWORD = ? where EMAIL = ?",
        member.getName(), member.getPassword(), member.getEmail());
}

query() 메서드를 이용해 select 쿼리문을 날리는 코드는 다음과 같이 작성합니다.

public Member selectByEmail(String email) {
    List results = jdbcTemplate.query(
            "select * from MEMBER where EMAIL = ?",
            new RowMapper() {
                @Override
                public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
                    Member member = new Member(
                            rs.getString("EMAIL"),
                            rs.getString("PASSWORD"),
                            rs.getString("NAME"),
                            rs.getTimestamp("REGDATE").toLocalDateTime());
                    member.setId(rs.getLong("ID"));
                    return member;
                }
            }, 
            email); // 3번째 매개변수 email은 인덱스 파라미터(두 번째 매개변수에서 "select * ~ EMAIL = ?" 에서 ?가 인덱스 파라미터다.
    return results.isEmpty() ? null : results.get(0);
}

2. javax.sql.DataSource 인터페이스


Java에서는 커넥션 풀(Connection Pool)을 지원하기 위해 javax.sql.DataSource 인터페이스를 지원합니다. 커넥션 풀을 사용하고 싶다면 해당 인터페이스를 구현한 객체를 이용해 사용하면 됩니다. 직접 DataSource 구현 클래스를 만들어 사용하는 것도 좋지만, 이미 구현해놓은 여러 구현체(모듈)이 있습니다. 

 

DB 커넥션 풀 기능을 제공하는 모듈(javax.sql.DataSource 인터페이스의 구현체)

    1. Tomcat JDBC

    2. HikariCP

    3. DBCP

    4. c3p0

 

어떤 모듈이든 상관없이 JDBC API는 DriverManager 외에 DataSource를 이용해서 DB 연결을 구하는 방법을 정의하고 있습니다. 

Connection conn = null;
try {
    // dataSource는 생성자나 설정 메서드를 이용해서 주입받는다.
    conn = dataSource.getConnection();
    ...

그런데 스프링을 사용하면 DataSource나 Connection, Statement, ResultSet을 직접 사용하지 않고 JdbcTemplate을 이용해서 간단히 쿼리를 실행할 수 있다. 즉, DataSource 구현체를 이용해 DB 설정과 관련된 코드를 담은 DataSource를 빈으로 등록하고, 해당 빈을 JdbcTemplate 객체에 주입해서 사용한다.

@Bean(destroyMethod = "close")
public DataSource dataSource() {
    DataSource ds = new DataSource();
    ds.setDriverClassName("com.mysql.jdbc.Driver");
    ds.setUrl("jdbc:mysql://localhost/spring5fs?characterEncoding=utf8");
    ds.setUsername("spring5");
    ds.setPassword("spring5");
    ds.setInitialSize(2);
    ds.setMaxActive(10);
    ds.setTestWhileIdle(true); // 유휴 커넥션 검사
    ds.setMinEvictableIdleTimeMillis(1000 * 60 * 3); // 최소 유휴 시간 3분
    ds.setTimeBetweenEvictionRunsMillis(1000 * 10); // 10초 주기
    return ds;
}
위의 코드처럼 DB 관련 세팅을 한 후 아래처럼 JdbcTemplate 객체를 생성할 때 주입해서 사용하면 된다.
import javax.sql.DataSource;
 
import org.springframework.jdbc.core.JdbcTemplate;
 
public class MemberDao {
    private JdbcTemplate jdbcTemplate;
 
    public MemberDao(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
}

3. RowMapper<T>


RowMapper는 ResultSet에서 데이터를 읽어와 T 객체로 변환해주는 기능을 제공합니다.