JDBC API를 간편하게 → JDBC Template
📚 Spring JDBC : 간편한 JDBC API 사용을 위한 JDBC Template 제공
Spring JDBC는 반복적인 JDBC API 코드를 간소화하여 효율적인 데이터베이스 작업을 지원한다.
JDBC API를 사용할 때 발생하는 구조적 반복 문제와 예외 처리 문제를 해결하기 위해 Spring이 JdbcTemplate을 제공한다.
1️⃣ Spring JDBC가 해결하는 문제
1-1. 반복되는 코드 제거
JDBC API를 사용하면 다음과 같은 반복 작업이 발생한다.
- Connection 객체 생성 및 반환
- Statement 생성 및 실행
- ResultSet 처리
- 예외처리와 자원 정리
JdbcTemplate은 이 모든 과정을 내부적으로 처리하여 간단하고 깔끔한 코드를 작성할 수 있도록 도와준다.
1-2. 예외 처리 간소화
- JDBC API는 SQLException과 같은 예외를 try-catch-finally 블록으로 처리해야 한다.
- JdbcTemplate은 예외를 Spring의 DataAccessException으로 변환하여 처리 과정을 단순화시킨다.
1-3. 간단한 트랜잭션 처리
- JDBC API에서 다수의 쿼리를 하나의 트랜잭션으로 묶으려면 수동으로 Connection 객체를 관리해야 했다.
- Spring에서는 트랜잭션 관리자를 사용해 간편하게 트랜잭션을 관리한다.
2️⃣ Spring JDBC: JdbcTemplate
기존의 코드인 JDBC API(https://1000sang-dev.tistory.com/79)를 JdbcTemplate으로 Repository 구현.
※ Service 로직도 JdbcTemplate에 맞게 수정해 준다. (코드는 따로 안올림)
2-1. JdbcTemplate으로 단일 SELECT 수행
JdbcTemplate는 JDBC API의 주요 구성 요소를 간소화하여 제공한다.
@Repository
@RequiredArgsConstructor
public class UserJdbcTemplateDao {
private final JdbcTemplate jdbcTemplate;
public User findById(int userId) {
String getUserQuery = "SELECT * FROM \"user\" WHERE id = ?";
int getUserParams = userId;
return this.jdbcTemplate.queryForObject(
getUserQuery,
(resultSet, rowNum) -> new User(
resultSet.getInt("id"),
resultSet.getString("name"),
resultSet.getInt("age"),
resultSet.getString("job"),
resultSet.getString("specialty"),
resultSet.getTimestamp("created_at")
.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime()
),
getUserParams
);
}
}
- queryForObject 메서드
- SQL 쿼리
- RowMapper: 결과를 반환할 람다 함수 (혹은 RowMapper 구현체)
- 쿼리 파라미터
2-2. JdbcTemplate으로 다수 SELECT 수행
@Repository
@RequiredArgsConstructor
public class UserJdbcTemplateDao {
private final JdbcTemplate jdbcTemplate;
public List<User> findAll() {
String getUserQuery = "SELECT * FROM \"user\"";
return this.jdbcTemplate.queryForStream(
getUserQuery,
(resultSet, rowNum) -> new User(
resultSet.getInt("id"),
resultSet.getString("name"),
resultSet.getInt("age"),
resultSet.getString("job"),
resultSet.getString("specialty"),
resultSet.getTimestamp("created_at")
.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime()
)
).toList();
}
}
- queryForStream
- SQL 쿼리의 결과를 Java 8+의 Stream 형태로 반환해준다.
2-3. JdbcTemplate으로 INSERT 수행
public User save(String name, Integer age, String job, String specialty) {
// (A) INSERT USER
String createUserQuery = "INSERT INTO \"user\" (name, age, job, specialty, created_at) VALUES (?, ?, ?, ?, ?)";
Object createUserParams = new Object[]{
name,
age,
job,
specialty,
LocalDateTime.now()
};
this.jdbcTemplate.update(
createUserQuery,
createUserParams,
int.class
);
// (B) SELECT id - MySQL:last_insert_id()->id / PostgresQL:currval()->lastval/lastval()->lastval
String lastInsertIdQuery = "SELECT lastval()";
int createdUserId = this.jdbcTemplate.queryForObject(
lastInsertIdQuery,
int.class
);
// (C) SELECT USER
String getUserQuery = "SELECT * FROM \"user\" WHERE id = ?";
int getUserParams = createdUserId;
return this.jdbcTemplate.queryForObject(
getUserQuery,
(resultSet, rowNum) -> new User(
resultSet.getInt("id"),
resultSet.getString("name"),
resultSet.getInt("age"),
resultSet.getString("job"),
resultSet.getString("specialty"),
resultSet.getTimestamp("created_at")
.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime()
),
getUserParams
);
}
- update
- 단일 데이터 삽입 및 조작
2-4. JdbcTemplate으로 UPDATE 수행
public User update(int id, String name, Integer age, String job, String specialty) {
// (A) UPDATE USER
String updateUserQuery = "UPDATE \"user\" SET name = ?, age = ?, job = ?, specialty = ? WHERE id = ?";
Object[] updateUserParams = new Object[]{
name,
age,
specialty,
id,
};
int rowsAffected = this.jdbcTemplate.update(
updateUserQuery,
updateUserParams
);
// (B) SELECT USER
String getUserQuery = "SELECT * FROM \"user\" WHERE id = ?";
int getUserParams = id;
return this.jdbcTemplate.queryForObject(
getUserQuery,
(resultSet, rowNum) -> new User(
resultSet.getInt("id"),
resultSet.getString("name"),
resultSet.getInt("age"),
resultSet.getString("job"),
resultSet.getString("specialty"),
resultSet.getTimestamp("created_at")
.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime()
),
getUserParams
);
}
}
2-5. JdbcTemplate으로 DELETE 수행
public void delete(int userId) {
String deleteUserQuery = "DELETE FROM \"user\" WHERE id = ?";
Object[] deleteUserParams = new Object[]{
userId
};
this.jdbcTemplate.update(
deleteUserQuery,
deleteUserParams
);
}
3️⃣ JdbcTemplate 장점
1. 코드 간소화
- 복잡한 JDBC API 코드를 제거하여 간결한 데이터베이스 작업 코드 작성 가능.
2. 예외 처리 통일
- Spring의 DataAccessException으로 예외를 통합 관리.
3. 트랜잭션 관리 통합
- Spring의 트랜잭션 관리자를 활용해 손쉽게 트랜잭션 처리 가능.
4. 반복 작업 제거
- Connection 생성, 자원 반환, SQLException 처리 등 반복 작업이 필요 없음.
4️⃣ 트랜잭션 처리
Spring에서는 PlatformTransactionManager와 @Transactional을 사용하여 트랜잭션을 쉽게 처리할 수 있다
트랜잭션 주요 개념
- 트랜잭션 생성: Connection 생성
- 트랜잭션 참여: 동일 Connection에서 작업 수행
- 트랜잭션 처리: 정상 종료 시 Commit, 예외 발생 시 RollBack
5️⃣ JdbcTemplate 사용 시 주의 사항
1. DataSource 설정
- JdbcTemplate는 내부적으로 DataSource를 사용하므로, 적절히 설정된 DataSource가 필요하다.
2. 트랜잭션 컨텍스트
- @Transactional 사용 시 모든 쿼리가 동일 Connection에서 실행되도록 보장된다.
3. Connection 관리
- Spring은 Connection을 자동 관리하지만, 수동 관리가 필요한 경우 DataSourceUtils를 사용할 수 있다.
6️⃣ 결론
Spring JDBC의 JdbcTemplate은 간단하고 효율적인 데이터베이스 작업을 가능하게 한다. 기존 JDBC API의 복잡성과 반복성을 제거하고, 예외 처리와 트랜잭션 관리를 통합하여 생산성을 높인다.
JdbcTemplate은 더 나아가 Spring Data JPA 및 ORM 기술로 확장 가능한 기초를 제공한다.🎯
다음 포스팅은 이어서 실습코드를 활용(PlatformTransactionManager 사용)하여 Spring Transaction에 대해 좀 더 깊게 들어가볼까 한다.
ℹ️ 참고
[ASAC 6기 강의자료]
https://jiwondev.tistory.com/154
'💻DEV-STUDY > Spring' 카테고리의 다른 글
[Spring Boot, DB] DB와 Spring Boot 연동 (1) | 2024.11.29 |
---|---|
[Spring] 실무에서의 Best Practices #2 (0) | 2024.10.07 |
[Spring] 실무에서의 Best Practices #1 (0) | 2024.10.07 |
[Spring] Spring Boot 장점 및 동작 (0) | 2024.10.06 |
[Spring] Spring 요청값 처리 방식 (0) | 2024.10.06 |