SQL을 직접 사용하는 경우에 스프링이 제공하는 JdbcTemplate는 JDBC를 매우 편리하게 사용할 수 있도록 도와주어 좋은 선택지다.
장점
- 설정의 편리함
- JdbcTemplate는 'spring-jdbc' 라이브러리에 포함되어 있는데 이 라이브러리는 스프링으로 JDBC를 사용할 때 기본적으로 사용되는 라이브러리라서 별도의 설정이 필요없다.
- 반복 문제 해결
- JdbcTemplate는 템플릿 콜백 패턴을 사용해서, JDBC를 직접 사용할 때 발생하는 반복되는 작업을 대신 처리해준다.
- 대신해주는 반복되는 작업들
- 커넥션 획득
- statement를 준비하고 실행
- 결과를 반복하도록 루프 실행
- 커넥션, statement, resultset 종료
- 트랜잭션을 위한 커넥션 동기화
- 예외 발생시 스프링 예외 변환기 실
- 대신해주는 반복되는 작업들
- 개발자는 SQL을 작성하고, 전달할 파라미터를 정의하고, 응답 값을 매핑하기만 하면 된다.
- JdbcTemplate는 템플릿 콜백 패턴을 사용해서, JDBC를 직접 사용할 때 발생하는 반복되는 작업을 대신 처리해준다.
단점
- 동적 SQL을 해결하기 어렵다.
//JdbcTemplate 추가
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
//H2 데이터베이스 추가
runtimeOnly 'com.h2database:h2'
이 코드를 'build.gradle'에 추가하면 사용 가능
JdbcTemplateItemRepositoryV1
/**
* JdbcTemplate 구현
*/
@Slf4j
public class JdbcTemplateItemRepositoryV1 implements ItemRepository {
private final JdbcTemplate template;
public JdbcTemplateItemRepositoryV1(DataSource dataSource) { //생성자에 dataSource가 필요
this.template = new JdbcTemplate(dataSource);
}
@Override
public Item save(Item item) {
String sql = "insert into item(item_name, price, quantity) values (?,?,?)";
//DB에서 생성해준 id값 가져오기
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);
//데이터베이스에 insert가 완료되어야 키 값을 확인할 수 있어서 insert후에 key값 가져오기.
long key = keyHolder.getKey().longValue();
item.setId(key);
return item;
}
@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);
}
@Override
public Optional<Item> findById(Long id) {
String sql = "select id, item_name, price, quantity 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, 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;
});
}
@Override
public List<Item> findAll(ItemSearchCond cond) {
String itemName = cond.getItemName();
Integer maxPrice = cond.getMaxPrice();
String sql = "select id, item_name, price, quantity 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()); //query는 리스트 반환, 쿼리문이 실패일 때만 DataAccessException을 던짐.
}
}
save()
- 데이터를 저장할 때 PK인 id 값은 데이터베이스 자체에서 auto increment 방식을 사용하기 때문에 데이터가 insert되어야 PK값을 알 수 있다. 그래서 KeyHolder와 'connection.prepareStatement(sql, new String[]{"id"})를 사용해서 id를 지정해주면 insert 이후에 데이터베이스에서 생성된 id 값을 알 수 있다.
findById()
- template.quertForObject()
- 결과 row가 하나일 때 사용
- RowMapper는 데이터베이스의 반환 결과인 ResultSet을 객체로 변환한다.
- 결과가 없으면, 그리고 결과가 둘 이상이면 예외가 발생한다.
findAll()
- template.query()
- 결과가 하나 이상일 때 사용한다. (하나 이상이니까 하나일 때도 사용됨)
- RowMapper는 데이터베이스의 반환 결과인 ResultSet을 객체로 변환한다. (RowMapping시에 필요한 resultset이 끝날 때까지 반복문을 돌리는 것은 JdbcTemplate에서 해준다.)
- 결과가 없으면 빈 컬렉션을 반환한다.
- 동적 쿼리 문제
- 필터링을 할 때 사용자가 넣은 값에 따라 동적으로 쿼리가 달라져야 한다. where를 넣고 어떤 상황에는 and를 넣어줘야 하고, 각 상황에 맞춰서 파라미터도 생성해야 한다.
- 이후에 배우는 Mybatis를 사용할 때의 큰 장점은 SQL을 직접 작성할 때 동적 쿼리를 쉽게 작성할 수 있다는 것이다.
JdbcTemplate 실행
@Configuration
@RequiredArgsConstructor
public class JdbcTemplateV1Config {
private final DataSource dataSource;
@Bean
public ItemService itemService() {
return new ItemServiceV1(itemRepository());
}
@Bean
public ItemRepository itemRepository() {
return new JdbcTemplateItemRepositoryV1(dataSource);
}
}
원래 데이터베이스가 아닌 메모리를 사용해서 MemoryConfig를 사용했지만, 지금은 JdbcTemplate를 이용하기 때문에 새로운 Config를 만든다.
@Import(JdbcTemplateV1Config.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")
public class ItemServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ItemServiceApplication.class, args);
}
@Bean
@Profile("local")
public TestDataInit testDataInit(ItemRepository itemRepository) {
return new TestDataInit(itemRepository);
}
}
그리고 main메서드가 있는 곳에도 @Import를 바꾼다.
결과
데이터베이스와 잘 연동된 것을 확인할 수 있다.
728x90
'Spring' 카테고리의 다른 글
[Spring DB2] 데이터 접근 기술 - 테스트 (3) | 2024.03.17 |
---|---|
[Spring DB2] 데이터 접근 기술 - 스프링 JdbcTemplate-2 (1) | 2024.03.14 |
[Spring DB2] 데이터 접근 기술 - 시작 (0) | 2024.03.11 |
[Spring DB 1편 듣고 복습, 토이 프로젝트 수정] 6. 스프링과 문제 해결 - 예외 처리, 반복 (2) | 2024.03.09 |
[Spring DB 1편 듣고 복습, 토이 프로젝트 수정] 5. 자바 예외 이해 (4) | 2024.03.02 |