JdbcTemplate 소개
SQL을 직접 사용하는 경우를 위해 스프링이 제공하는 라이브러리
장점
- 설정의 편리함
JdbcTemplate
은spring-jdbc
라이브러리에 포함되어 있다.spring-jdbc
라이브러리는 스프링으로 JDBC를 사용할 때 기본으로 사용되는 라이브러리다.- 그래서 별도의 복잡한 설정 없이 바로 사용할 수 있다.
- 반복 문제 해결
JdbcTemplate
은 JDBC를 직접 사용할 때 발생하는 대부분의 반복 작업을 대신 처리해준다.JdbcTemplate
이 템플릿 콜백 패턴을 사용하기 때문에 가능하다.
- 개발자는 SQL을 작성하고, 전달할 파리미터를 정의하고, 응답 값을 매핑하기만 하면 된다.
- 처리해주는 반복 작업
- 커넥션 획득
- statement를 준비하고 실행
- 결과를 반복하도록 루프를 실행
- 커넥션 종료
- statement 종료
- resultset 종료
- 트랜잭션 다루기 위한 커넥션 동기화
- 예외 발생시 스프링 예외 변환기 실행
단점
- 동적 SQL을 해결하기 어렵다.
JdbcTemplate 설정
build.gradle
에 아래와 같이 라이브러리를 추가하자.
//JdbcTemplate 추가
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
추가로 JDBC를 사용하려면 특정 DB에 대한 드라이버가 필요하니,
이번엔 H2 DB에 대한 드라이버를 추가하자.
//H2 데이터베이스 추가
runtimeOnly 'com.h2database:h2'
JdbcTemplate 적용1 - 기본
매퍼
JdbcTemplate
은 매퍼라는 것이 필요하다.
RowMapper
라는 클래스를 통해 생성한다.
제네릭으로 내가 데이터를 설정할 클래스를 설정해주고,
rs
에서 컬럼명을 지칭해서 set + 자료형
으로 이루어진 메소드명으로 값을 가져온다.
이 때 rs
는 ResultSet
을 의미한다.
private RowMapper<Item> itemRowMapper() {
return (rs, rowNum) -> {
Item item = new Item();
item.setId(rs.getLong("id"));
item.setItemName(rs.getString("item_name"));
item.setPrice(rs.getInt("price"));
item.setQuantity(rs.getInt("quantity"));
return item;
};
}
단일 조회
JdbcTemplate
의 queryForObject
메소드를 사용하자.
파라미터로는 쿼리, 매퍼, 인자들을 지정하면 된다.
파라미터로 넘길 변수가 a, b, c가 있다면 아래와 같이 된다.
template.queryForObject(sql, 매퍼, a, b, c)
목록 조회
JdbcTemplate
의 query
메소드를 사용하자.
파라미터로는 쿼리, 매퍼, 인자들을 지정하면 된다.
파라미터로 넘길 변수가 a, b, c가 있다면 아래와 같이 된다.
template.query(sql, 매퍼, a, b, c)
등록, 수정, 삭제
JdbcTemplate
의 update
메소드를 사용하자.
파라미터로는 쿼리, 매퍼, 인자들을 지정하면 된다.
파라미터로 넘길 변수가 a, b, c가 있다면 아래와 같이 된다.
template.update(sql, 매퍼, a, b, c)
등록할 때 PK 값 가져오기
데이터를 저장할 때 PK 값을 가져오고 싶을 때가 있을 것이다.
그럴 때는 다른 update
메소드를 사용하자.
GeneratedKeyHolder
를 사용해서 PK 값을 가져오는 메소드가 따로 있다.
우선 update
를 실행할 때 람다를 통해서 아래와 같이 데이터를 등록한다.
KeyHolder keyHolder = new GeneratedKeyHolder();
template.update(connection -> {
//자동 증가 키
PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"});
ps.setString(1, item.getItemName());
ps.setInt(2, item.getPrice());
ps.setInt(3, item.getQuantity());
return ps;
}, keyHolder);
그러면 데이터가 저장됬을 때 keyHolder
에 PK 값이 저장된다.
long key = keyHolder.getKey().longValue();
JdbcTemplate 적용2 - 동적 쿼리 문제
실무에서 개발하다 보면 검색할 때 조건이 매우 다양한 것을 알 수 있다.
특히 관리자용 페이지를 개발하다 보면 해당 페이지가 관리하는 서비스의 규모가 커질수록
관리하는 데이터가 많아지기 때문에 검색 조건도 다양해진다.
그런데 검색 조건이 다양하다고 그걸 또 다 쓰는 것은 아니다.
만약에 검색 조건이 5개가 있다고 치면
1,3,5
를 쓰는 경우도 있을 것이고, 1,2,4
를 쓰는 경우도 있을 것이고, 안 쓰는 경우도 있을 것이다.
이런 경우에 문제점은 조건절을 만드는 것이다.
어느 순간에 WHERE
를 넣을지 AND
를 넣을지 개발자가 완전히 수동으로 확인해줘야 한다.
어찌보면 JdbcTemplate
사용 시 제일 귀찮은 점으로 볼 수 있다.
JdbcTemplate 적용3 - 구성과 실행
@Configuration
을 통해서 환경설정을 하는 클래스를 만들어주자.
해당 클래스 내부에서 서비스나 리포지토리를 빈으로 등록하고,
메인 애플리케이션 클래스에서 @Import
로 해당 환경설정을 활성화 시켜주자.
JdbcTemplate - 이름 지정 파라미터 1
JdbcTemplate
이 파리미터를 바인딩할 때는 순서 지정 바인딩
과 이름 지정 바인딩
이 있다.
순서 지정 바인딩
순서대로 자동으로 파라미터가 바인딩되는 방식이다.
만약에 update item set item_name=?, price=?, quantity=? where id=?
라는 쿼리가 있다고 가정해보자.
그러면 해당 쿼리를 실행하려면 다음과 같이 값을 설정할 것이다.
template.update(sql, itemName, price, quantity, itemId);
그런데 만약에 여기서 price와 quantity의 순서가 바뀌었다고 가정해보자.
template.update(sql, itemName, quantity, price, itemId);
만약 실제로 이런 상황이 발생한다면 개발 시점에 알 수 있을까?
대비를 미리 잘 해둔다면 찾기 쉽겠지만 영어가 가득한 상황에서 찾기는 쉽지 않을 것이다.
정말 심각할 때는 실제로 운영에 반영되고 찾게 되는 상황이 발생할 수도 있다.
그러면 코드만 고치는 게 아니라 DB도 복구해야 하는 등 일이 엄청 커질 수 있다.
이름 지정 바인딩
순서 지정 바인딩의 문제를 고치기 위해 이름 지정 바인딩 방식이 등장했다.
NamedParameterJdbcTemplate
라는 클래스를 사용하면 이름 지정 바인딩 방식을 사용할 수 있다.
JdbcTemplate - 이름 지정 파라미터 2
NamedParameterJdbcTemplate
라는 클래스를 통해서 JdbcTemplate
에서 이름 지정 바인딩을 하는 방식을 알아보자.
매퍼
우선 매퍼를 사용하는 방식부터 차이가 크다.
기본적으로 RowMapper
를 사용하는 것은 동일하다.
다만 기존에는 ResultSet
을 통해서 클래스에 값을 설정하는 방식이었다.
하지만 이름 지정 바인딩 방식에서는 BeanPropertyRowMapper
를 통해서 클래스 자체를 전달한다.
BeanPropertyRowMapper
를 사용하면 item_id
를 itemId
로 변환하는 camel-case
변환도 지원해준다.
private RowMapper<Item> itemRowMapper() {
return BeanPropertyRowMapper.newInstance(Item.class); //camel-case 변환 지원
}
이름 지정 바인딩에서의 파라미터
이름 지정 바인딩 방식에서는
Map
과 SqlParameterSource
라는 인터페이스가 적극적으로 사용된다.
Map
은 단순히 키와 값을 설정하면 되니 넘어가자.
SqlParameterSource
인터페이스는 값을 Map
처럼 설정하는 방식과 자바빈 프로퍼티 규약
을 기준으로 설정하는 방식이 있다.
Map
처럼 설정하는 방식은 MapSqlParameterSource
라는 클래스를 사용한다.
해당 클래스는 빌더 패턴을 사용한다는 것이 중요하다.
그래서 아래와 같이 생성과 동시에 값을 설정한다.
SqlParameterSource param = new MapSqlParameterSource()
.addValue("itemName", itemName)
.addValue("price", price)
.addValue("quantity", quantity)
.addValue("id", itemId);
그런 다음에 값을 넘기면 된다.
template.update(sql, param);
자바빈 프로퍼티 규약
의 경우에는 값을 넘기기 위해 클래스를 만들어서 getter 메소드를 통해 값을 설정하는 방식이다.
해당 방식은 BeanPropertySqlParameterSource
라는 클래스를 사용한다.
만약에 Item
이라는 클래스가 있고 price
라는 필드가 있다고 가정해보자.
이 때 getPrice
라는 메소드가 존재한다면 price
를 사용하는 곳에 자동으로 값을 설정해준다.
값은 컨트롤러나 서비스에서 설정해주고 리포지토리에서는 그저 사용하기만 하면 된다.
SqlParameterSource param = new BeanPropertySqlParameterSource(item);
쿼리 작성 시 차이점
순서 지정 바인딩 방식에서는 where id = ?
처럼 물음표로 값을 설정할 위치를 지정해줬다.
하지만 이름 지정 바인딩 방식에서는 where id = :id
처럼 :
와 필드명
으로 매핑할 이름을 직접 명시해준다.
등록할 때 PK 값 가져오기
이름 지정 바인딩 방식을 사용할 때도 동일하게 GeneratedKeyHolder
를 사용하면 된다.
template.update(sql, param, keyHolder);
처럼 쿼리를 실행하고,
Long key = keyHolder.getKey().longValue();
처럼 값을 가져오면 된다.
주의할 점
순서 지정 바인딩을 사용할 때는 JdbcTemplate
에 JdbcTemplate
를 그대로 주입했다.
이름 지정 바인딩을 사용할 때는 JdbcTemplate
에 NamedParameterJdbcTemplate
를 주입하는 것을 주의하자.
아니면 처음부터 NamedParameterJdbcTemplate
를 선언하면 된다.
Jdbc Template - SimpleJdbclnsert
JdbcTemplate
은 INSERT SQL를 직접 작성하지 않아도 되도록 SimpleJdbcInsert
라는 편리한 기능을 제공한다.
SimpleJdbcInsert
라는 클래스를 사용하면 된다.
참고로 스프링에서는 JdbcTemplate
를 사용할 때 데이터를 저장할 때는 관례 상 이 방법을 많이 사용한다.
생성자
생각보다 단순한데 테이블명과 등록 시 반환할 PK의 컬럼명을 명시하면 된다. 아래는 그 예시다.
this.jdbcInsert =
new SimpleJdbcInsert(dataSource)
.withTableName("item") //테이블명 지정
.usingGeneratedKeyColumns("id"); //PK가 되는 컬럼명 지정
그리고 SimpleJdbcInsert
는 생성 시점에 DB 테이블의 메타 데이터를 조회할 수 있다.
그래서 usingColumns
라는 메소드를 통해서 INSERT
가 실행될 때 저장되는 컬럼들을 지정할 수 있다.
전체가 아닌 일부 컬럼만 저장하고 싶을 때 사용하며, 생략 가능하다.
데이터 등록하기
단순히 executeAndReturnKey
메소드를 실행하면 된다.
파라미터를 지정하는 것은 BeanPropertySqlParameterSource
로 하면 된다.
실행하면 아래와 같은 모습이 된다.
Number key = jdbcInsert.executeAndReturnKey(param);
메소드명을 보면 알 수 있듯이 PK 값을 반환한다.
해당 값은 Number
클래스로 받아서
해당 클래스에서 제공하는 longValue()
나 intValue()
같은 메소드로 형변환해서 사용하면 된다.
JdbcTemplate 주요 기능
JdbcTemplate
- 순서 기반 파라미터 바인딩을 지원
NamedParameterJdbcTemplate
- 이름 기반 파라미터 바인딩을 지원
- 가장 권장되는 방식
SimpleJdbcInsert
- INSERT SQL을 편리하게 사용 가능
SimpleJdbcCall
- 스토어드 프로시저를 편리하게 호출 가능
- 공식 문서
JdbcTemplate 정리
실무에서 쿼리를 직접 실행할 때는 JdbcTemplate
를 사용하자.
실제로 JPA
같은 ORM 기술을 사용할 때도 종종 쿼리를 직접 사용해야 할 때가 있는데,
그럴 때 JdbcTemplate
이 많이 쓰인다.
다만 JdbcTemplate
의 최대 단점이 문제인데 바로 동적 쿼리다.
만약 JPA
같은 ORM 기술을 쓰는 게 아니라면 이럴 때는 MyBatis
라는 기술을 사용하자.
JdbcTemplate
의 최대 단점인 동적 쿼리 문제를 해결해주면서 쿼리도 편하게 작성할 수 있게 도와준다.