스프링 JdbcTemplate
- 순수 Jdbc 설정과 같다.
- SQL Mapper 중 하나이다
- Jdbc API에서 본 반복 코드 대부분을 제거한다.
- 커넥션 획득/종료, 커넥션 동기화, 스프링 예외 변환기 실행 등 반복 작업을 대신 처리해 준다.
- DataSource 를 주입받아 JdbcTemplate를 생성하여 사용한다.
- SQL 문을 직접 작성해야 한다.
- 동적 SQL 을 해결하기 어렵다.
> 참고<
- Insert 문은 데이터 추가라는 단순한 작업이므로, 테이블 이름과 칼럼-값 정보로 SQL을 생성하기 쉽다.
따라서 Spring 이 자동화하기 위해 SimpleJdbcInsert 같은 유틸리티를 제공한다.
그렇지만 Select 는 조건, 그룹화, 정렬, 조인 등 형태가 다양하므로 자동화하기 어려워서 직접 작성한다.
Insert 문도 직접 작성해도 된다.
초기 설정
- build.gradle 파일에 jdbc, h2 데이터베이스 관련 라이브러리 추가
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
JdbcTemplate
순서 기반 파라미터 바인딩을 지원한다.
이때, 파라미터 순서가 맞지 않으면 문제가 발생한다.
- 사용 방법
`queryForObject()` : 단건 조회
`query()` : 목록 조회 (리스트 반환)
`update()` : 데이터 변경
단건 조회, 단순 데이터 조회 경우
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");
단건 조회, 객체 조회 경우
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;
});
데이터 변경 경우
jdbcTemplate.update(
"update t_actor set last_name = ? where id = ?",
"Banjo", 5276L);
- JdbcTemplate 예시 코드
@Repository
public class JdbcTemplateItemRepositoryV1 implements ItemRepository {
private final JdbcTemplate template;
public JdbcTemplateItemRepositoryV1(DataSource dataSource) {
this.template = new JdbcTemplate(dataSource);
}
@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;
}
@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;
};
}
}
NamedParameterJdbcTemplate
이름 기반 파라미터 바인딩을 지원한다.
JdbcTemplate 대신 NamedParameterJdbcTemplate 을 사용하도록 권장한다.
- 사용 방법
SQL 에서 `?` 대신 `:파라미터이름` 을 받아서 사용한다.
insert into item (item_name, price, quantity) values (:itemName, :price, :quantity)"
- 파라미터 종류
`Map`
> Key : `:파라미터이름`, Value : `파라미터값` 의 Map 의 데이터 구조
Map<String, Object> param = Map.of("id", id);
Item item = template.queryForObject(sql, param, itemRowMapper());
`SqlParameterSource`
> SqlParameterSource 인터페이스의 구현체
> 메서드 체인을 통해 편리한 사용법
SqlParameterSource param = new MapSqlParameterSource()
.addValue("itemName", updateParam.getItemName())
.addValue("price", updateParam.getPrice())
.addValue("quantity", updateParam.getQuantity())
.addValue("id", itemId); //이 부분이 별도로 필요하다.
template.update(sql, param);
`BeanPropertySqlParameterSource`
> SqlParameterSource 인터페이스의 구현체
> 자바빈 프로퍼티 규약을 통해 자동으로 파라미터 객체 생성 (getter/setter 통해서)
> 따라서 객체 필드를 통해 항상 바인딩할 수 없을 때는 사용할 수 없다.
(이럴 때는 MapSqlParameterSource 를 통해 하나씩 추가해서 사용해야 한다.)
SqlParameterSource param = new BeanPropertySqlParameterSource(item);
KeyHolder keyHolder = new GeneratedKeyHolder();
template.update(sql, param, keyHolder);
- BeanPropertyRowMapper
ResultSet 의 결과를 받아 자바빈 프로퍼티 규약에 맞추어 데이터를 변환한다.
일반적으로 데이터베이스는 `snake_case` 표기법을 사용하고, 자바 객체는 `camelCase` 표기법을 사용한다.
따라서 언더스코어 표기법을 카멜 표기법으로 자동 변환해서, 객체를 생성하여 setXXX 를 호출하여 변환해 준다.
(`item.setItemName(rs.getString("item_name"))` 이렇게 자동으로 호출하여 변환해 준다.)
private RowMapper<Item> itemRowMapper() {
return BeanPropertyRowMapper.newInstance(Item.class); //camel 변환 지원
}
- NamedParameterJdbcTemplate 예시 코드
@Repository
public class JdbcTemplateItemRepositoryV2 implements ItemRepository {
private final NamedParameterJdbcTemplate template;
public JdbcTemplateItemRepositoryV2(DataSource dataSource) {
this.template = new NamedParameterJdbcTemplate(dataSource);
}
@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;
}
@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);
}
@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());
return Optional.of(item);
} catch (EmptyResultDataAccessException e) {
return Optional.empty();
}
}
private RowMapper<Item> itemRowMapper() {
return BeanPropertyRowMapper.newInstance(Item.class); //camel 변환 지원
}
}
SimpleJdbcInsert
Insert 문의 SQL 을 직접 작성하지 않고, 편리하게 사용할 수 있다.
DataSource 를 주입받아 SimpleJdbcInsert 를 생성하여 사용한다.
생성 시점에 데이터베이스 테이블의 메타 데이터 생성
`withTableName` : 데이터를 저장할 테이블 명을 지정
`usingGeneratedKeyColumns` : key 를 생성하는 PK 칼럼 명을 지정
`usingColumns` : Insert SQL 에 사용할 칼럼을 지정 (생략 가능, 특정 값만 저장하고 싶을 때 사용)
Insert SQL 을 실행하는 메서드
`executeAndReturnKey` : Insert SQL 실행하고, 생성된 키 값 조회
- SimpleJdbcInsert 예시 코드
@Repository
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"); //생략 가능
}
@Override
public Item save(Item item) {
SqlParameterSource param = new BeanPropertySqlParameterSource(item);
Number key = jdbcInsert.executeAndReturnKey(param);
item.setId(key.longValue());
return item;
}
...
}
SimpleJdbcCall
스토어드 프로시저를 편리하게 호출할 수 있다.
출처 | 스프링 DB 2(김영한) - 인프런
'💠프로그래밍 언어 > Java' 카테고리의 다른 글
[Spring] MyBatis 사용법 익히기 ! (1) | 2025.05.01 |
---|---|
[Spring] 데이터베이스 연동하여 테스트하기 ! (별도 DB, 임베디드 모드) (0) | 2025.05.01 |
[Spring] 에러 코드로 예외 처리하기, 스프링 예외 추상화 사용하기 (0) | 2025.04.23 |
[Spring] 스프링 트랜잭션의 추상화, 템플릿, AOP 그리고 자동 등록까지 (0) | 2025.04.23 |
[Spring] JDBC 와 트랜잭션, 커넥션 풀 (0) | 2025.04.22 |