JdbcTemplate
실무에서 가장 간단하고 실용적인 방법으로 SQL을 사용하려면 JdbcTemplate를 사용하면 된다.
기본 사용법
단건 조회(queryForObject)
int rowCount = jdbcTemplate.queryForObject("select count(*) from t_actor",
Integer.class);
단건 조회 - 숫자 조회, 파라미터 바인딩
int countOfActorsNamedJoe = jdbcTemplate.queryForObject(
"select count(*) from t_actor where first_name = ?", Integer.class,
"Joe");
단건 조회 - 문자 조회
String lastName = jdbcTemplate.queryForObject(
"select last_name from t_actor where id = ?",
String.class, 1212L);
단건 조회 - 객체 조회
Actor actor = jdbcTemplate.queryForObject(
"select first_name, last_name from t_actor where id = ?",
(resultSet, rowNum) -> {
Actor newActor = new Actor();
newActor.setFirstName(resultSet.getString("first_name"));
newActor.setLastName(resultSet.getString("last_name"));
return newActor;
},
1212L);
목록 조회 - 객체
List<Actor> actors = jdbcTemplate.query(
"select first_name, last_name from t_actor",
(resultSet, rowNum) -> {
Actor actor = new Actor();
actor.setFirstName(resultSet.getString("first_name"));
actor.setLastName(resultSet.getString("last_name"));
return actor;
});
목록 조회 - 객체
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);
}
INSERT
jdbcTemplate.update(
"insert into t_actor (first_name, last_name) values (?, ?)",
"Leonor", "Watling");
UPDATE
jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L);
DELETE
jdbcTemplate.update(
"delete from t_actor where id = ?",
Long.valueOf(actorId));
이름 지정 파라미터[NamedParameterJdbcTemplate]
JdbcTemplate을 기본으로 사용하면 파라미터를 sql에 순서대로 바인딩한다.
String sql = "update item set item_name=?, price=?, quantity=? where id=?";
template.update(sql,
itemName,
price,
quantity,
itemId);
파라미터가 적을때는 문제가 안되지만 파라미터가 많이 있을때는 문제가 발생할 수 있다.
이러한 문제를 해결하기 위해 Jdbc에서는 NamedParameterJdbcTemplate를 제공한다.
<JdbcTemplate 기본 사용 코드>
@Override
public Item save(Item item) {
String sql = "insert into item (item_name, price, quantity) values (?, ?, ?)";
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);
long key = keyHolder.getKey().longValue();
item.setId(key);
return item;
}
< NamedParameterJdbcTemplate 사용 코드(save)>
@Override
public Item save(Item item) {
String sql = "insert into item (item_name, price, quantity) " +
"values (:itemName, :price, :quantity)";
SqlParameterSource param = new BeanPropertySqlParameterSource(item);
KeyHolder keyHolder = new GeneratedKeyHolder();
template.update(sql,param,keyHolder);
long key = keyHolder.getKey().longValue();
item.setId(key);
return item;
}
< NamedParameterJdbcTemplate 사용 코드(update)>
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
String sql ="update item " +
"set item_name=:itemName, price=:price, quantity=:quantity " +
"where id=:id";
SqlParameterSource param = new MapSqlParameterSource()
.addValue("itemName",updateParam.getItemName())
.addValue("price",updateParam.getPrice())
.addValue("quantity",updateParam.getQuantity())
.addValue("id",itemId);
template.update(sql,param);
}
MapSqlParameterSource ==> Map과 유사하며 SQL타입을 지정할 수 있는 등 SQL에 특화된 기능을 제공
< NamedParameterJdbcTemplate 사용 코드(findById)>
@Override
public Optional<Item> findById(Long id) {
String sql = "select id, item_name, price, quantity from item where id = :id";
try {
Map<String, Object> param = Map.of("id",id);
Item item = template.queryForObject(sql, param, itemRowMapper()); //데이터베이스에서 조회한 결과를 Item 객체로 매핑.
return Optional.of(item);
}catch (EmptyResultDataAccessException e){
return Optional.empty(); //item이 NULL일 경우
}
}
private RowMapper<Item> itemRowMapper() { //데이터베이스에서 가져온 결과를 Item 클래스의 필드에 자동으로 매핑해준다.
return BeanPropertyRowMapper.newInstance(Item.class);
}
BeanPropertyRowMapper ==> ResultSet 의 결과를 받아서 자바빈 규약에 맞추어 데이터를 변환한다.
또한, BeanPropertyRowMapper는 camel 변환을 지원한다.
따라서 select item_name으로 조회해도 setItemName()에 문제없이 들어간다.
queryForObject==> 하나의 로우 조회
< NamedParameterJdbcTemplate 사용 코드(findAll)>
@Override
public List<Item> findAll(ItemSearchCond cond) {
String itemName = cond.getItemName();
Integer maxPrice = cond.getMaxPrice();
SqlParameterSource param = new BeanPropertySqlParameterSource(cond);
String sql = "select id, item_name, price, quantity from item";
//동적 쿼리
if (StringUtils.hasText(itemName) || maxPrice != null) {
sql += " where";
}
boolean andFlag = false;
if (StringUtils.hasText(itemName)) {
sql += " item_name like concat('%',itemName,'%')";
andFlag = true;
}
if (maxPrice != null) {
if (andFlag) {
sql += " and";
}
sql += " price <= maxPrice";
}
log.info("sql={}", sql);
return template.query(sql,param,itemRowMapper());
}
SimpleJdbcInsert
JdbcTemplate는 INSERT 쿼리를 직접 작성하지 않아도 되도록 SimpleJdbcInsert라는 기능을 제공한다.
< SimpleJdbcInsert 사용 코드>
@Slf4j
public class JdbcTemplateRepositoryV3 implements ItemRepository {
private final NamedParameterJdbcTemplate template;
private final SimpleJdbcInsert jdbcInsert;
public JdbcTemplateRepositoryV3(DataSource dataSource){
this.template = new NamedParameterJdbcTemplate(dataSource);
this.jdbcInsert = new SimpleJdbcInsert(dataSource)
.withTableName("item")
.usingGeneratedKeyColumns("id");
// .usingColums("item_name","price","quantity"); 생략 가능
}
@Override
public Item save(Item item) {
SqlParameterSource param = new BeanPropertySqlParameterSource(item);
Number key = jdbcInsert.executeAndReturnKey(param); // INSERT SQL을 실행
item.setId(key.longValue());
return item;
}
}
- withTableName : 데이터를 저장할 테이블 명 지정
- usingGeneratedKeyColumns : 테이블의 기본키 칼럼 명을 지정
로그를 확인하면 INSERT 쿼리를 자동으로 생성하는 것을 확인할 수 있다.
JdbcTemplate의 장단점
장점
- 별도의 복잡한 설정 없이 사용 가능
- JDBC를 직접 사용할때 발생하는 반복작업(커넥션 획득, statement 를 준비하고 실행, 커넥션 종료, statement, resultset종료, 예외발생시 스프링 예외 변환기 실행 ....)을 대신 처리해준다.
단점
- 동적 SQL을 해결하기 어렵다.