본문 바로가기
데이터 접근 기술/JdbcTemplate

JdbcTemplate 소개

by oncerun 2022. 10. 13.
반응형

JdbcTemplate?

JdbcTemplate대신 많은 신입 개발자들은 순수 JDBC의 고충을 공부한 이후,

실제 사용되는 JPA 구현 기술, MyBatis 등으로 넘어가기에 JdbcTemplate에 대한 개념 자체를 모른다. 

따라서 이번 기회에 JdbcTemplate의 간단한 소개와 설정, 장점과 단점을 통해 데이터 접근 기술 선택에 대한 좀 더 넓은 지식을 가지게 되었으면 좋겠다.

 

또한 스프링에서 데이터 접근 방식에 대한 doc를 읽어보는 것도 좋겠다.

https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#jdbc-choose-style

 

Data Access

The Data Access Object (DAO) support in Spring is aimed at making it easy to work with data access technologies (such as JDBC, Hibernate, or JPA) in a consistent way. This lets you switch between the aforementioned persistence technologies fairly easily, a

docs.spring.io

 

JdbcTemplate의 소개

 

JdbcTemplate는 SQL Mapper의 종류로 스프링에서 SQL을 직접 사용하는 경우에 매우 좋은 선택지가 될 수 있다.

이는 spring-jdbc 라이브러리에 포함되어 있기 때문에 별도의 설정 없이 스프링으로 jdbc를 사용할 때 기본으로 사용되는 라이브러리이다.

 

기존 순수하게 jdbc 다루는 코드에는 수많은 반복 문제가 발생하는데 jdbcTemplate은 이를 템플릿 콜백 패턴을 통해 해결한다. 

 

순수 JDBC를 사용함에 따라 문제의 여지가 될 수 있는 부분들은 다음과 같다.

 

  • 커넥션을 획득하는 부분이 코드에 중복된다.
  • 사용한 리소스를 반환하기 위한 코드가 중복된다.
  • 트랜잭션 시작을 위한 코드가 반복된다.
  • 트랜잭션 시 커넥션을 동기화하기 위한 코드가 필요하다.
  • 특정 구현체에 종속된 예외를 발생시킨다.

다만 JdbcTemplate은 동적 SQL을 해결하기 어렵다. 

 

주요 기능

  • JdbcTemplate
    순서 기반 파라미터 바인딩 지원
  • NamedParameterJdbcTemplate
    이름 기반 파라미터 바인딩 지원
  • SimpleJdbcInsert
    Insert sql에 대한 편리한 기능 제공
  • SimpleJdbCall
    스토어드 프로시저를 편리하게 호출 가능

 

JdbcTemplate의 설정

 

spring-jdbc 패키지 내부에 존재하기 때문에 관련 라이브러리를 추가해야 한다.

 

JdbcTemplate은 커넥션 관련 부분을 해결하기 때문에 DataSource의 주입이 필요한데, 이는 실제 인스턴스에 직접 주입하여 사용할 수도 있지만 Spring IoC를 사용해 템플릿으로 주입받아 사용할 수도 있다.

 

 

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

 

Retrieving Auto-generated Keys

만약 JdbcTemplate을 사용하여 insert를 진행할 때 데이터베이스에게 pk생성을 위임하여 그 값을 반환받아야 할 때가 있다. 

이 경우 JdbcTemplate을 KeyHolder를 사용한 방법으로 해결방안을 제시해 준다.

final String INSERT_SQL = "insert into my_test (name) values(?)";
final String name = "Rob";

KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
    PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] { "id" });
    ps.setString(1, name);
    return ps;
}, keyHolder);


// 데이터베이스에서 생성해준 Primary key를 얻었다.
long key = keyHolder.getkey().longValue();

 

** jdbcTemplate. update의 인수

@FunctionalInterface
public interface PreparedStatementCreator {
   PreparedStatement createPreparedStatement(Connection con) throws SQLException;
}

Querying 

querying을 사용하기 위해선 jdbcTemplate의 "queryForObject()" 메서드를  주로 사용한다. 

 

queryForObject()를 통해 관계된 행의 수, 바인드 변수, 정의된 객체 타입에 매핑할 수 있습니다. 

 

아마 자주 사용될 것으로 예상되는 도메인 객체에 바인드 하는 예시는 다음과 같습니다.

private final RowMapper<Actor> actorRowMapper = (resultSet, rowNum) -> {
    Actor actor = new Actor();
    actor.setFirstName(resultSet.getString("first_name"));
    actor.setLastName(resultSet.getString("last_name"));
    return actor;
};

public List<Actor> findAllActors() {
    return this.jdbcTemplate.query("select first_name, last_name from t_actor", actorRowMapper);
}

해당 queryForOject의 인자를 살펴보면 다음과 같습니다.

  • 첫 번째 인자로는 작성된 SQL문
  • 두 번째 인자로는 ResultSet, int타입의 두 개의 매개 변수를 받아 <T> 타입의 반환 값을 가지는 함수형 인터페이스인 RowMapper가 들어갑니다. 
  • 세 번째 인자는 "?"에 매핑될 인자가 들어갑니다.
    * query()는 결과가 하나 이상일 때, queryForObject는 관련 로우가 하나일 때 사용
@FunctionalInterface
public interface RowMapper<T> {

   @Nullable
   T mapRow(ResultSet rs, int rowNum) throws SQLException;

}

 

만약 queryForObject를 통해 진행 값을 Optional을 통해 반환하기 위해 래핑 한다면 Optional.of()를 사용하는 것을 추천한다. 

 

@Override
public Optional<Item> findById(Long id) {
    String sql = "select id, item_name, price, quantity where id = ?"; 
    try {
        Item item = template.queryForObject(sql, itemRowMapper(), id);
        return Optional.of(item);
    } catch (EmptyResultDataAccessException e) {
        return Optional.empty();
    }
}

 

이는 queryForObject에서 반환 객체를 찾지 못하면 언체크 예외를 던지기 때문이다. 

 

Dynamic Query

이제 단점인 동적 쿼리에 대해 알아보자. 

 

동적 쿼리는 대 부분 조건에 따라 sql문이 변하는 것을 말한다.  그리고 대부분 동적 쿼리를 많이 사용한다.

 

String itemName = cond.getItemName();
Integer maxPrice = cond.getMaxPrice();
String sql = "select id, item_name, price, quantity from item";

if (StringUtils.hasText(itemName) || maxPrice != null) {
    sql += " where";
}
boolean andFlag = false;
List<Object> param = new ArrayList<>();
if (StringUtils.hasText(itemName)) {
    sql += " item_name like concat('%',?,'%')";
    param.add(itemName);
    andFlag = true;
}
if (maxPrice != null) {
    if (andFlag) {
        sql += " and";
    }
    sql += " price <= ?";
    param.add(maxPrice);
}
log.info("sql={}", sql);
return template.query(sql, itemRowMapper(), param.toArray());

 

이를 보면 JdbcTemplate에서는 동적 쿼리를 위해선 조건에 따라 sql문을 변형하는 방식을 취한다. 이는 조건의 개수가 많아 짐에 따라 더욱 거대해질 여지가 남아있다. 

 

코드의 맥락이 커진다는 것은 개발자의 실수를 발생시킬 수 있기에 이에 대한 해결방안이 필요하다.

 

 

 

 

추가적인 부분

JdbcTemplate의 확장된 부분으로 NamedParameterJdbcTemplate이 존재한다.

이 해당 클래스는 jdbc의 '?'로 sql문에 주입될 파라미터 방식 대신 '?'를 변수명을 통해 치환하여 사용할 수 있도록 도와준다.

 

// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
    this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int countOfActorsByFirstName(String firstName) {

    String sql = "select count(*) from T_ACTOR where first_name = :first_name";

    SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);

    return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}

 

기본 JdbcTemplate에 대한 사용방법은 매우 복잡하다. 이를 개선하자고 나온 것이 SimpleJdbc 클래스이다. 

https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#jdbc-simple-jdbc

 

Data Access

The Data Access Object (DAO) support in Spring is aimed at making it easy to work with data access technologies (such as JDBC, Hibernate, or JPA) in a consistent way. This lets you switch between the aforementioned persistence technologies fairly easily, a

docs.spring.io

 

 

 

또한 insert 하는 부분에 대해 매우 간편하게 진행할 수 있는 클래스를 제공한다. 

 

바로 SimpleJdbcInsert이다. 

 

public class JdbcActorDao implements ActorDao {

    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.insertActor = new SimpleJdbcInsert(dataSource)
                .withTableName("t_actor")
                .usingColumns("first_name", "last_name")
                .usingGeneratedKeyColumns("id");
    }

    public void add(Actor actor) {
        Map<String, Object> parameters = new HashMap<String, Object>(2);
        parameters.put("first_name", actor.getFirstName());
        parameters.put("last_name", actor.getLastName());
        Number newId = insertActor.executeAndReturnKey(parameters);
        actor.setId(newId.longValue());
    }

    // ... additional methods
}

 

이는 dataSource를 통해 해당 테이블을 조회하고 메타데이터를 조회해 더욱 편리하게 조작할 수 있는 방법이다. 

 

체인 메서드를 통해 사용하는 옵션들이 존재한다. 

https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#jdbc-simple-jdbc

 

Data Access

The Data Access Object (DAO) support in Spring is aimed at making it easy to work with data access technologies (such as JDBC, Hibernate, or JPA) in a consistent way. This lets you switch between the aforementioned persistence technologies fairly easily, a

docs.spring.io

 

정리

JdbcTemplate은 매우 간단하고 실용적으로 SQL을 사용하여 값을 반환받을 수 있다. 

ORM 기술을 사용하면서 직접 SQL을 작성해야 할 때 함께 사용해도 좋다고 한다. 

다만 동적 쿼리 문제에 대해 실질적인 해결 방안이 없다.  또한 Sql을 자바 코드로 작성하기에 실수가 발생할 여지도 존재한다.

 

동적 쿼리 문제를 해결하면서 SQL도 편리하게 작성할 수 있도록 하는 기술이 바로 Mybatis이다. 

 

반응형

'데이터 접근 기술 > JdbcTemplate' 카테고리의 다른 글

NamedParameterJdbcTemplate 활용  (0) 2022.10.13

댓글