데이터 접근 기술 (2) - JdbcTemplate
개요
이번 글은 데이터 접근 기술 시리즈의 두 번째로, JdbcTemplate에 대한 글이다.
김영한 님의 스프링 DB 강의와 개인 공부를 통해 학습한 내용을 정리하고자 한다.
Spring Boot 3.2.2, JDK 17 버전이며 H2를 데이터베이스로 사용하니 참고하자.
- 데이터 접근 기술 (1) - 데이터 접근 기술의 종류
- 데이터 접근 기술 (2) - JdbcTemplate
- 데이터 접근 기술 (3) - MyBatis
- 데이터 접근 기술 (4) - JPA
- 데이터 접근 기술 (5) - Spring Data JPA
- 데이터 접근 기술 (6) - Querydsl
JdbcTemplate이란?
스프링 프레임워크에서 제공하는 JDBC 기반의 데이터베이스 접근을 돕는 클래스로, JDBC의 반복적인 코드를 최소화하고 예외 처리와 연결 관리 등을 간편하게 할 수 있도록 도와준다.
JDBC를 직접 사용할 때 발생하는 반복 작업을 대신 처리해 주는데, 아래의 내용을 모두 자동으로 처리한다.
- Connection 획득
- Statement 준비 및 실행
- Connection, Statement, ResultSet 종료
- Transaction 처리를 위한 Connection 동기화
- 예외 발생 시 스프링 예외 변환기 실행
자바 코드로 sql을 작성하기 때문에 줄바꿈을 하며 작성할 때 스페이스를 신경 써서 해주어야 하며, 문자열 사이에 +를 해야 하는 번거로움이 있다. 아래 코드는 JdbcTemplate으로 sql을 작성하는 예시이다.
public void update(Long itemId, ItemUpdateDto updateParam) {
String sql = "update item " +
"set item_name = ?, price = ?, quantity = ? " +
"where id = ?";
// update logic 생략
}
또한 검색 조건이 있는 데이터를 조회하는 경우 다양한 조건에 맞게 if문과 sql 내용을 작성하는 등 동적 쿼리 작성에 어려움이 있다.
간단하고 실용적인 방법으로 sql을 사용하려면 JdbcTemplate을 사용하면 될 것이다.
JdbcTemplate이 제공하는 주요 기능은 다음과 같다.
- `JdbcTemplate` : 순서 기반 파라미터 바인딩 지원
- `NamedParameterJdbcTemplate` : 이름 기반 파라미터 바인딩 지원
- `SimpleJdbcInsert` : insert sql을 편리하게 사용 가능
- `SimpleJdbcCall` : 스토어드 프로시저 편리하게 호출 가능
기본 설정
1. build.gradle
dependencies {
// JdbcTemplate 사용
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
// H2 데이터베이스 사용
runtimeOnly 'com.h2database:h2'
}
2. application.properties
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
각 설정을 적용하고 사용할 테이블을 생성한다. 내가 작성한 테이블은 아래와 같다.
create table user (
id bigint generated by default as identity,
user_name varchar(10),
phone_num varchar(25),
primary key (id)
);
📌 참고
아래의 포스트에 H2 데이터베이스 설치 및 실행 방법을 작성해놓았으니 필요하면 참고하자.
https://hyunrian.tistory.com/88
Mac에서 H2 설치 및 실행하기
H2 Database는 간단한 개발이나 테스트 용도로 사용하기 좋은 DB이다. 이번 글에서는 맥에서 H2를 설치하고 실행하는 과정을 진행해보려 한다. H2 설치하기 아래의 링크로 들어가면 최신 버전을 OS에
hyunrian.tistory.com
코드 작성
간단한 CRUD 구현을 위해 작성한 클래스는 도메인 객체 User, 인터페이스인 UserRepository와 구현체 JdbcUserRepository로 총 3개이다.
1. `User`
@Data
public class User {
private Long id;
private String userName;
private String phoneNum;
public User(String userName, String phoneNum) {
this.userName = userName;
this.phoneNum = phoneNum;
}
}
2. `UserRepository`
public interface UserRepository {
//회원 저장
User save(User user);
//회원 수정
void update(User user);
//아이디로 회원 조회
Optional<User> findById(Long id);
//회원 전체 조회
List<User> findAll();
//회원 삭제
void delete(Long id);
}
3. `JdbcUserRepository`
@Repository
public class JdbcUserRepository implements UserRepository {
private final NamedParameterJdbcTemplate template;
private final SimpleJdbcInsert jdbcInsert;
public JdbcUserRepository(DataSource dataSource) {
this.template = new NamedParameterJdbcTemplate(dataSource);
this.jdbcInsert = new SimpleJdbcInsert(dataSource)
.withTableName("user") //테이블명
.usingGeneratedKeyColumns("id"); //pk 컬럼명
}
@Override
public User save(User user) {
SqlParameterSource param = new BeanPropertySqlParameterSource(user);
Number key = jdbcInsert.executeAndReturnKey(param);
user.setId(key.longValue());
return user;
}
@Override
public void update(User user) {
String sql = "update user " +
"set user_name = :userName, " +
"phone_num = :phoneNum " +
"where id = :id";
// SqlParameterSource param = new BeanPropertySqlParameterSource(user);
//위의 방식과 아래 방식 중 적합한 것으로 사용하면 됨
SqlParameterSource param = new MapSqlParameterSource()
.addValue("userName", user.getUserName())
.addValue("phoneNum", user.getPhoneNum())
.addValue("id", user.getId());
template.update(sql, param);
}
@Override
public Optional<User> findById(Long id) {
String sql = "select user_name, phone_num from user where id = :id";
try {
Map<String, Long> param = Map.of("id", id);
RowMapper<User> rowMapper = BeanPropertyRowMapper.newInstance(User.class);
User user = template.queryForObject(sql, param, rowMapper); //결과가 없으면 예외 터짐
return Optional.of(user);
} catch (EmptyResultDataAccessException e) {
return Optional.empty();
}
}
@Override
public List<User> findAll() {
String sql = "select user_name, phone_num from user";
RowMapper<User> rowMapper = BeanPropertyRowMapper.newInstance(User.class);
return template.query(sql, rowMapper);
}
@Override
public void delete(Long id) {
String sql = "delete from user where id = :id";
Map<String, Long> param = Map.of("id", id);
template.update(sql, param);
}
}
- JdbcUserRepository의 생성자는 DataSource를 주입 받고 내부에서 `NamedParameterJdbcTemplate`과 `SimpleJdbcInsert`를 생성한다.
- `NamedParameterJdbcTemplate`를 사용하기 때문에 파라미터 바인딩을 `?` 대신 `:parameterName`과 같이 사용할 수 있다.
- 파라미터를 전달하려면 key - value의 데이터 구조로 전달해야 하며 다음의 방식을 주로 사용한다.
- `Map`
- `SqlParameterSource` (interface)
- `MapSqlParameterSource`
- `BeanPropertySqlParameterSource`
- 데이터 저장 시 id 값은 DB가 자동으로 생성하도록 설정했기 때문에 id 값은 insert가 완료된 이후에 확인할 수 있다. 이로 인해 작성해야 할 코드가 길어지는데, `SimpleJdbcInsert`를 사용함으로써 간단한 작성이 가능하다. `SimpleJdbcInsert`를 사용하지 않는다면 아래와 같이 작성해야 한다.
public User save(User user) {
String sql = "insert into user(user_name, phone_num) values(:userName, :phoneNum)";
SqlParameterSource param = new BeanPropertySqlParameterSource(user);
KeyHolder keyHolder = new GeneratedKeyHolder();
template.update(sql, param, keyHolder);
user.setId(keyHolder.getKey().longValue());
return user;
}
- `BeanPropertyRowMapper`는 `ResultSet`의 결과를 받아 자바빈 규약에 맞추어 데이터를 변환한다.
- DB에 `snake_case`로 작성된 컬럼명은 `camelCase`로 자동 변환된다. (user_name 👉🏻 userName)
- 컬럼명과 객체 이름이 완전히 다른 경우라면 sql에서 별칭을 사용하도록 한다. (select user_name as username)
- 조회 결과를 객체로 매핑하기 위해 `RowMapper`를 사용한다.
- 하나의 row를 조회할 때는 `template.queryForObject()`, 여러 개의 row를 조회할 때는 `template.query()`를 사용한다.
- insert, update, delete로 데이터를 변경할 때는 `template.update()`를 사용한다.
- 그 외 테이블 생성 등 임의의 sql을 실행할 때는 `execute()`를 사용한다.
테스트 코드를 작성하여 확인해보면 모두 정상적으로 작동됨을 확인할 수 있다.
JdbcTemplate의 더욱 자세한 사용 방법은 스프링 공식 매뉴얼을 참고하도록 하자.
https://docs.spring.io/spring-framework/reference/data-access/jdbc/core.html#jdbc-JdbcTemplate
Using the JDBC Core Classes to Control Basic JDBC Processing and Error Handling :: Spring Framework
Some query methods return a single value. To retrieve a count or a specific value from one row, use queryForObject(..). The latter converts the returned JDBC Type to the Java class that is passed in as an argument. If the type conversion is invalid, an Inv
docs.spring.io
참고
- 스프링 DB 2편 - 데이터 접근 활용 기술 (김영한)
'Database' 카테고리의 다른 글
데이터 접근 기술 (6) - Querydsl (0) | 2024.01.04 |
---|---|
데이터 접근 기술 (5) - Spring Data JPA (0) | 2024.01.01 |
데이터 접근 기술 (4) - JPA (0) | 2023.12.18 |
데이터 접근 기술 (3) - MyBatis (0) | 2023.12.09 |
데이터 접근 기술 (1) - 데이터 접근 기술의 종류 (0) | 2023.11.29 |
Liked this Posting!