김영한 님의 스프링 DB 2편 - 데이터 접근 활용 기술 강의를 보고 작성한 내용입니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard
JdbcTemplate 은 스프링으로 jdbc 를 사용할 때 기본으로 사용되는 spring-jdbc
라이브러리에 포함되어 있는데, 별도의 설정 없이 사용할 수 있다
템플릿 콜백 패턴을 사용해서 JDBC를 직접 사용할 때의 반복 작업을 대신 처리해준다
커넥션 획득 / Statement 준비 및 실행 / 결과를 반복하도록 루프 실행 / 커넥션, Statement, ResultSet 종료
트랜잭션을 위한 커넥션 동기화, 예외 발생 시 스프링 예외 변환기 실행
개발자는 SQL을 작성하고, 전달할 파라미터를 정의, 응답 값을 매핑하기만 하면 된다
public class JdbcTemplateItemRepositoryV1 implements ItemRepository {
private final JdbcTemplate template;
public JdbcTemplateItemRepositoryV1(DataSource dataSource) {
this.template = new JdbcTemplate(dataSource);
}
...
}
JdbcTemplate
을 사용하기 위해 필드에 선언이 필요
JdbcTemplate
를 생성할 때 DataSource
를 받아야 한다
JdbcTemplateItemRepositoryV1
의 생성자에서 dataSource
를 외부에서 주입 받고 생성자 내부에서 JdbcTemplate
을 생성
spring.datasource.url=jdbc:h2:tcp://localhost/~/test spring.datasource.username=sa spring.datasource.password=
application.properties에 위처럼 설정하면 스프링부트가 해당 설정을 사용해서 커넥션 풀과 DataSource , 트랜잭션 매니저를 스프링 빈으로 자동 등록
자동 등록된 DataSource
가 JdbcTemplateItemRepositoryV1
의 생성자에 주입된다
public void update(Long itemId, ItemUpdateDto updateParam) {
String sql = "update item set item_name = ?, price=?, quantity=? where id=?";
template.update(sql,
updateParam.getItemName(),
updateParam.getPrice(),
updateParam.getQuantity(),
itemId);
}
update()
: 데이터를 변경할 때 사용 ( insert, update, delete )
sql의 ?
에 바인딩할 파라미터를 update()
에 순차적으로 적어준다
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());
...
return ps;
}, keyHolder);
long key = keyHolder.getKey().longValue();
item.setId(key);
return item;
}
➜ id 값 없이 Item 객체 생성 후 DB에서 생성된 값으로 id 값 설정이 필요
DB에 item을 저장한 후 생성된 id 값을 가져오기 위해 KeyHolder
를 사용
KeyHolder
와 connection.prepareStatement(sql, new String[]{"id"})
를 사용해서 id 를 지정해주면 INSERT 쿼리 실행 이후에 DB에서 생성된 ID 값을 조회할 수 있다
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();
}
}
private RowMapper<Item> itemRowMapper() {
return( (rs, rowNum) -> {
Item item = new Item();
item.setId(rs.getLong("id"));
...
return item;
});
}
queryForObject()
를 통해 객체를 하나 가져올 수 있다
RowMapper
메서드가 필요결과가 없으면 EmptyResultDataAccessException
예외가 발생
결과가 둘 이상이면 IncorrectResultSizeDataAccessException
예외가 발생
itemRowMapper()
: 결과인 ResultSet 을 Item 객체로 바꾸는 메서드
데이터베이스의 조회 결과인 ResultSet
을 객체로 변환할 때 RowMapper
를 사용
JdbcTemplate 이 자동으로 ResultSet을 반복하면서 rowMapper 를 실행
but> 결과를 객체로 매핑하는 코드는 직접 구현해야한다
public List<Item> findAll(ItemSearchCond cond) {
// 동적 쿼리 코드 생략
...
return template.query(sql, itemRowMapper(), param.toArray());
}
query()
: 결과가 하나 이상일 때 사용
결과가 없으면 빈 컬렉션을 반환한다
findAll()
은 사용자가 검색하는 값에 따라서 실행하는 SQL이 동적으로 달려져야한다
위의 상황을 고려하여 where 절을 넣는 경우, and 를 사용하는 경우 모두 계산해야하고 상황에 맞는 파라미터도 생성해야한다
이처럼 고려해야될 사항이 너무 많아 JdbcTemplate 에서 동적 쿼리를 작성하기 어렵다
Mybatis
의 경우 동적 쿼리를 쉽게 작성할 수 있다2-3 번의 update()
를 보면 파라미터가 순서대로 sql의 ?
에 바인딩된다
but> 파라미터가 많아지면 순서를 잘못 넣어 심각한 문제가 발생할 수도 있다
➜ NamedParameterJdbcTemplate
라는 이름을 지정해서 파라미터를 바인딩하는 기능 제공
public class JdbcTemplateItemRepositoryV2 implements ItemRepository {
private final NamedParameterJdbcTemplate template;
public JdbcTemplateItemRepositoryV2(DataSource dataSource) {
this.template = new NamedParameterJdbcTemplate(dataSource);
}
}
// 변경 전 SQL
String sql = "update item set item_name = ?, price=?, quantity=? where id=?";
// 변경 후 SQL
String sql = "update item set item_name = :itemName, price=:price, quantity=:quantity where id=:id";
// 변경 전 호출
template.update(sql, updateParam.getItemName(), updateParam.getPrice(), updateParam.getQuantity(), itemId);
// 변경 후 호출
template.update(sql, param);
SQL의 ?
가 :파라미터이름
형태로 변경
메서드를 호출할 때 값을 직접 전달하지 않고 값을 가진 파라미터를 전달
파라미터를 전달하려면 Map 처럼 key, value 데이터 구조를 만들어서 전달해야한다
key는 :파라미터이름
으로 지정한 파라미터 이름, value는 해당 파라미터의 값
이름 지정 바인딩에서 사용하는 파라미터
Map
객체를 직접 사용
SqlParameterSource
: 인터페이스
MapSqlParameterSource
BeanPropertySqlParameterSource
Map<String, Object> param = Map.of("id", id); Item item = template.queryForObject(sql, param, itemRowMapper());
SqlParameterSource param = new MapSqlParameterSource() .addValue("itemName", updateParam.getItemName()) ... .addValue("id", itemId); template.update(sql, param);
SqlParameterSource
의 구현체
Map과 유사한데 SQL 타입을 지정할 수 있는 등 SQL에 특화
메서드 체인을 통해 편리하게 사용할 수 있다
SqlParameterSource param = new BeanPropertySqlParameterSource(item); template.update(sql, param);
SqlParameterSource
의 구현체
자바 빈 프로퍼티 규약을 통해 자동으로 파라미터 객체를 생성
ex> getItemName()
➜ ItemName
ItemName
을 key로, getItemName()
실행 결과를 value로 가짐private RowMapper<Item> itemRowMapper() { return BeanPropertyRowMapper.newInstance(Item.class); // camel 변환 지원 }
ResultSet
의 결과를 받아서 자바 빈 규약에 맞춰 데이터를 변환해준다
ex> select id, price from item
setId()
, setPrice()
처럼 자바 빈 프로퍼티 규약에 맞는 메서드를 호출DB의 칼럼 이름과 객체의 필드 이름이 다른 경우 별칭 as
를 활용한다
ex> select item_name
인 경우 setItem_name()
메서드가 없는 문제
select item_name as itemName
처럼 별칭을 활용하면 된다
camel 변환 지원
자바는 camelCase 표기법을 사용, 관계형 DB는 snake_case 를 사용
BeanPropertyRowMapper
는 snake_case 표기법을 camelCase 로 자동으로 변환해준다
따라서 item_name
으로 조회해도 자동으로 setItemName()
에 값이 들어간다
public class JdbcTemplateItemRepositoryV3 implements ItemRepository {
private final NamedParameterJdbcTemplate template;
private final SimpleJdbcInsert jdbcInsert;
public JdbcTemplateItemRepositoryV3(DataSource dataSource) {
this.template = new NamedParameterJdbcTemplate(dataSource);
this.jdbcInsert = new SimpleJdbcInsert(dataSource)
.withTableName("item")
.usingGeneratedKeyColumns("id");
// .usingColumns("item_name", "price", "quantity"); // 생략 가능
}
}
insert 쿼리를 직접 작성하지 않도록 SimpleJdbcInsert
라는 기능을 제공
withTableName()
: 테이블명
usingGeneratedKeyColumns()
: 자동으로 생성되는 PK 가 있는 경우 추가
usingColumns()
: 어떤 칼럼을 사용하는지
SimpleJdbcInsert
는 생성 시점에 dataSource
를 통해서 테이블명을 가지고 DB에서 메타데이터를 읽기 때문에 어떤 칼럼이 있는지 알 수 있어서 usingColumns()
은 생략 가능하다
public Item save(Item item) {
SqlParameterSource param = new BeanPropertySqlParameterSource(item);
Number key = jdbcInsert.executeAndReturnKey(param);
item.setId(key.longValue());
return item;
}
execute()
: DDL 과 같은 임의의 SQL을 실행할 때 사용
update("call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",Long.valueOf(unionId));
: 스토어드 프로시저 호출
SimpleJdbcCall
: 스토어드 프로시저를 편리하게 호출할 수 있다