먼저 ItemRepository를 상속받아 구현하겠다.
public class JdbcTemplateItemRepositoryV1 implements ItemRepository {
public JdbcTemplateItemRepositoryV1(DataSource dataSource) {
this.template = new JdbcTemplate(dataSource); //커넥션을 얻어오고 그래야 되기 때문에 필요. 어떤 DB인지도 알아야 하고.
}
private final JdbcTemplate template;
@Override
public Item save(Item item) {
return null;
}
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
}
@Override
public Optional<Item> findById(Long id) {
return Optional.empty();
}
@Override
public List<Item> findAll(ItemSearchCond cond) {
return null;
}
}
jdbc는 필드로 했다. 다 사용할거기 때문에..
jdbcTemplate 생성할 때 dataSource도 필요한데, dataSource는 커넥션 얻어오는 것에 특화된 인터페이스이기 때문에,
jdbcTemplate가 커넥션 얻어오는 것에 도움이 되도록 넣어주었다. 거기다 dataSource는 db가 어떤 db인지도 아니까.
일단은, save부터 구현했다.
@Override
public Item save(Item item) {
String sql = "insert into item(item_name, price, quantity) values(?,?,?)";
template.update(sql, item.getItemName(), item.getPrice(), item.getQuantity);
return item;
}
이렇게 깔끔할 줄 알았다...
근데 id를 얻어와서 set 해줘야 한다. 그래야 비로소 완전한 Item이 된다.
근데..
@Override
public Item save(Item item) {
String sql = "insert into item(item_name, price, quantity) values(?,?,?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
template.update(con->{
PreparedStatement ps = con.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;
}
이거 보자마자 JdbcTemplate에서는 키를 얻어오는 것을 지원 안해줬나? 이게 키 얻어오는 것이라면 이거 잘못 만든건데?
아니 그 많은 template.update()의 오버로딩이 있으면서 키홀더 넣은 오버로딩이 없다고?
이거는 분명히 잘못 만든거다. 이건 뭐랄까 진짜.. 개발자로서 화나게 만든 거다.
선생님도 이거는 깊이 있게 알지 않아도 된다고 했다..
그 다음 update()
@Override
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);
}
깔끔하다.
그냥 template.update(sql, 파라미터들);
그 다음 findById()
@Override
public Optional<Item> findById(Long id) {
String sql = "select * from item 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, row)->{
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;
};
}
일단은 id로 조회 해봤는데 아무래도 없을 수도 있으니까..
Optional.of(item)은 null을 허용하지 않는다는 거다.
Optional.ofNullable()은 null을 허용한다.
여튼 of로 했기에, null을 허용하지 않으니 만약 null이 나오면 EmptyResultDataAccessException을 던진다.
그럼 Optional인데 빈걸로 반환한다.
근데 또 결과가 둘 이상이면 IncorrectResultSizeDataAccessException 예외가 발생함. 직역 잘못된 결과 크기 데이터 접근 예외
query는 여러 개(리스트로 받음),
queryForObject는 단건 조회이다. 원시타입 반환받고 싶을 경우 Integer.class 이런 식으로 하면 된다.
이거는 쿼리의 결과를 오브젝트로 반환받는 건데, 적절하게 맵핑을 해 줘야되서 ItemRowMapper()를 써야 한다.
우리가 이거 콜백함수로 정의를 해 줘야 한다.
이게 처음에 뭔지 좀 까다로웠는데,
그러니까 이렇게 콜백함수로 정의해 둔 다음에 return 시켜두면 그걸 JdbcTemplate가 어딘가에 필드로 저장시켜 놓는다.
찾아보니 JdbcTemplate에 ResultSetSpliterator라는 inner class가 있는데 거기에 필드로 rowMapper를 가지고 있다.
뭐 솔직히 뭔지 잘은 모르겠는데, mapRow()가 실질적으로 인자로 줬던 콜백함수를 실행시키는 함수 같다.
여튼 뭐랄까, DB에 데이터가 오기까지 외부에서 가져오는거라 시간이 걸릴 수 있기에,
콜백함수로써 저장해 뒀다가, 제대로 rs가 오면 저 콜백함수를 실행하게끔 하는 거 같다.
그러니까, 결국은 우리가 함수를 정의한거다. 왜 함수를 정의할 때 인자로 뭘 받을 지는 사실 상 제약이 없으므로,
(rs, row) ->
이렇게 하는 게 외부에서 뭔가 들어와서 썼다기 보다는, 우리가 그냥 인자 두개 받는데, 이런거다. 이름도 상관없다.
그냥 함수 정의하는 거랑 똑같다.
호출 할 때 인자를 ResultSet, rowNum으로 넘겨주니 그렇게 들어가는 거지.
결국 콜백함수를 정의 해 놓으면, 저렇게 rowMapper를 필드로 저장해 놓았다가,
뭔가 rs다 도착하고 그러면 mapRow해서 인자로 그 도착한 rs와 rowNum을 순서대로 인자로 잘 넣어주고 그 콜백함수를 실행하는 듯 하다. mapRow가 저장된 콜백함수 실행하는 거 인듯?
RowMapper는 인터페이스이므로, JdbcTemplate가 따로 구현을 한 듯 하다.
여튼 인자로 넘겼다가 rs가 도착한 타이밍 등 특정 타이밍에 mapRow하면 실행되게끔 JdbcTemplate가 구현을 한 듯 하다.
그 다음이 정말 문제인데..
findAll
이건 동적 SQL 라고 한다.
그러니까 조건에 따라 SQL이 달라지는..
@Override
public List<Item> findAll(ItemSearchCond cond) {
Integer maxPrice = cond.getMaxPrice();
String itemName = cond.getItemName();
String sql = "select * 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());
}
사실 복잡하긴 해도 못쓸것은 아니지만,
무조건 에러날 거라며 넘어가셨다.
나는 정말 간단하게
if(maxPrice == null) maxPrice = 9999999;
if(itemName == null) itemName = '%';
이런 식으로 하면 되지 않을까 했다. (쿼리 날려봐도 %는 된다.)
SELECT * FROM item WHERE item_name LIKE concat('%', '%', '%');
이거 쿼리 날려봤더니 다 찾아진다.
근데 일부로 동적쿼리 설명하려고 저러신 것 같다..
여튼 뭐 경우에 따라 쿼리를 추가하는거다.
그리고 바인딩 인자들 저렇게 배열로 넣어도 된다. (순서 잘 지켜서)
'스프링 > 6. 스프링 DB-2' 카테고리의 다른 글
9. 이름지정 파라미터 (0) | 2023.10.08 |
---|---|
8. 구현한 Jdbc로 갈아 끼우기. (0) | 2023.10.08 |
6. Jdbc 템플릿 (0) | 2023.10.08 |
5. DB생성 (0) | 2023.10.08 |
4. 테스트 환경 (0) | 2023.10.08 |