Post

(Spring JDBC) JdbcTemplate

JdbcTemplate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// query 결과가 없는 경우 EmptyResultDataAccessException가 발생한다. DAO 내에서 try-catch해주자.

Product product = jdbcTemplate.queryForObject(
    query, 
    new Object[] { productCode }, 
    new BeanPropertyRowMapper<>(Product.class)
);

List<DisposalHistory> disposalHistoryList = jdbcTemplate.query(
    query, 
    new Object[] { dateDate }, 
    new BeanPropertyRowMapper<>(DisposalHistory.class)
);

/* new BeanPropertyRowMapper<>()를 사용해 자동으로 mapping 하는 경우
 * @AllArgsConstructor 뿐만 아니라
 * @NoArgsConstructor도 필요하다!
 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// queryForList는 컬럼 이름을 key로 해서 Map<String, Object>를 리턴한다.
[{id = 1, name="qer"} , {id = 2, name="asdf"}, ...]

// queryForMap은 Map<K,V>를 리턴하는데 queryForList의 원소에 해당하는게 V이고, 지정한 컬럼값이 K가 된다.
// id 컬럼을 K로 지정한 경우
{
    1 : {id = 1, name="qer"},
    2 : {id = 2, name="asdf"},
}

// 그래서 결과로 반환받은 행 중에 id = ?인 레코드의 존재를 판단하거나 접근할 때 유용.
result.get(1).get(name)    // qer을 반환.

// 자동 mapping(RowMapper)를 안쓸거면 그냥 query()를 사용해서 람다 등을 이용해 직접 1:1로 필드에 넣어준다.
jdbcTemplate.update()로 레코드를 삽입하는 동시에 해당 레코드의 Key값( sequence )을 반환받기

KeyHolder와 PreparedStatement를 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String query = "INSERT INTO SALES(sales\_id, customer\_id, branch\_id, sales\_time, receipt\_id, cancel\_check, amount) "
    + "VALUES(sales\_id\_seq.nextval, ?, ?, TO\_DATE(?,'YYYYMMDD HH24:MI:SS'), sales\_id\_seq.currval)";

  
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
    PreparedStatement ps = connection.prepareStatement(query, PreparedStatement.RETURN\_GENERATED\_KEYS);
    ps.setString(1, (sales.getCustomerId() != null) ? sales.getCustomerId().toString() : "NULL");
    ps.setString(2,Long.toString(sales.getBranchId()));
    ps.setString(3,sales.getSalesTime());
    return ps;
}, keyHolder);

return keyHolder.getKey().longValue();
update를 쓸 때는 주의할 것.

DB 툴과 같이 쓰는 경우 같은 테이블에 대해서 update를 툴에서도 수행하고 자바에서도 수행하게 되면 툴은 보통 Commit을 하기 전 까지 수정사항이 반영이 안되니까, update한 테이블에 대해서 Lock을 가지고 있는 경우가 있다. 그래서 자바에서 뭔가 Blocking이 걸리면서 안되는데?? 싶은 상황이 벌어질 수 있음.

RowMapper 종류
  • org.springframework.jdbc.core.BeanPropertyRowMapper
  • org.springframework.jdbc.core.DataClassRowMapper
    • BeanPropertyRowMapper의 자식 클래스로 java record나 kotlin data class라면 이를 사용하는게 더 나아보임.
    • 일반적으로 이걸 쓰면 된다.
  • org.springframework.data.jdbc.core.convert.EntityRowMapper
    • Spring Data JDBC 에서 제공
JPA entity와 RowMapper
  • BeanPropertyRowMapper에서 컬럼과 필드는 column_name as fieldName 형태로 SQL에서 매핑을 지정해주는 방식이다.
  • 컬럼명 변경 시 SQL 찾아다니면서 모두 바꿔주어야 한다. 필드명 변경도 마찬가지로 SQL 찾아다니면서 바꿔주어야 한다.
  • @Column 애너테이션이 붙어있다면, 애너테이션에 명시된 컬럼명으로 필드에 주입해주도록 하면 as 연결 안해도 되므로 필드명 변경이 SQL과 무관해져 유지보수하기 더 낫다.
  • 하지만 이 밖에도 @Converter 같은 여러 JPA 애너테이션이 존재하는데, 이들을 모두 처리해주는 RowMapper를 작성해야 의미가 있다.

여러모로 RowMapper를 사용하는 JDBC와의 궁합은 JPA 보다 Spring Data JDBC 쪽이 더 좋은 것 같다.

This post is licensed under CC BY 4.0 by the author.