1. JdbcTemplate이란?
JdbcTemplate은 Spring Framework에서 제공하는 클래스로, JDBC를 사용하여 데이터베이스에 접근할 때 발생하는 반복적인 코드를 간소화하고 개발 생산성을 향상시키기 위한 도구입니다. JDBC의 저수준 API를 추상화하여 간결하고 효율적인 데이터베이스 연동을 가능하게 합니다.
• JDBC의 한계점 보완: 연결 관리, 예외 처리, 자원 해제 등의 반복적인 작업을 자동화하여 개발자의 부담을 줄여줍니다.
• 편리한 API 제공: 데이터베이스 CRUD 작업을 위한 다양한 메서드를 제공합니다.
2. JdbcTemplate의 주요 기능
1. 데이터베이스 접근의 간소화: JDBC의 복잡한 코드를 간결하게 만들어줍니다.
2. 반복적인 코드 제거: 연결 설정, SQL 실행, 결과 매핑, 예외 처리 등의 반복적인 코드를 제거합니다.
3. 예외 처리 통합: SQLException을 DataAccessException으로 변환하여 일관된 예외 처리가 가능합니다.
4. 편리한 결과 매핑: RowMapper 등을 사용하여 결과를 객체로 손쉽게 매핑할 수 있습니다.
5. 트랜잭션 관리 연동: Spring의 트랜잭션 관리 기능과 통합하여 사용 가능합니다.
6. 바인딩 변수 지원: SQL 인젝션을 방지하기 위한 바인딩 변수를 쉽게 사용할 수 있습니다.
3. JdbcTemplate 설정 및 사용 방법
JdbcTemplate을 사용하기 위해서는 몇 가지 설정이 필요합니다. 이 섹션에서는 JdbcTemplate을 설정하고 사용하는 방법에 대해 자세히 설명하겠습니다.
3.1 의존성 추가
JdbcTemplate을 사용하려면 프로젝트에 필요한 의존성을 추가해야 합니다. Spring Boot를 사용하신다면, spring-boot-starter-jdbc 의존성을 추가하면 됩니다.
implementation 'org.springframework.boot:spring-boot-starter-jdbc:버전에 맞게 설정'
이 의존성에는 JdbcTemplate 클래스와 함께 데이터베이스 연결을 위한 기본 설정이 포함되어 있습니다.
3.2 DataSource 설정
DataSource는 데이터베이스 연결을 관리하는 인터페이스이며, JdbcTemplate은 이를 통해 데이터베이스에 접근합니다.
3.2.1 Spring Boot 자동 설정 사용
Spring Boot를 사용한다면, application.properties 또는 application.yml 파일에서 데이터베이스 연결 정보를 설정하면 자동으로 DataSource가 빈(bean)으로 등록됩니다.
예시 (application.properties):
spring.datasource.url=jdbc:postgresql://localhost:5432/your_database
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.driver-class-name=org.postgresql.Driver
주의사항:
• HikariCP가 기본 커넥션 풀로 사용되며, 별도의 설정 없이도 효율적인 커넥션 풀이 적용됩니다.
• 특별한 커스텀 설정이 필요하지 않다면, 이 방법으로 간단하게 DataSource를 설정할 수 있습니다.
4. JdbcTemplate 예제 코드
package com.example.demo.service;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
@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
);
}
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();
}
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
);
// (B) SELECT last inserted id
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 및 delete 메서드도 유사한 방식으로 구현
}
4.1 코드 설명
• 의존성 주입: @RequiredArgsConstructor를 사용하여 JdbcTemplate을 생성자 주입 받습니다.
• findById 메서드:
• SQL 쿼리를 작성하고, jdbcTemplate.queryForObject() 메서드를 사용하여 단일 결과를 User 객체로 매핑합니다.
• findAll 메서드:
• 모든 사용자를 조회하며, jdbcTemplate.queryForStream()을 사용하여 결과를 스트림으로 받아 리스트로 변환합니다.
• save 메서드:
• 새로운 사용자를 데이터베이스에 삽입하고, 마지막으로 생성된 ID를 조회하여 해당 사용자를 반환합니다.
• 바인딩 변수 사용:
• SQL 쿼리에서 ?를 사용하여 파라미터를 바인딩함으로써 SQL 인젝션을 방지합니다.
• 람다 표현식:
• RowMapper를 람다로 표현하여 코드의 간결성을 높였습니다.
5. JdbcTemplate의 장점과 단점
5.1 장점
1. 코드 간결성: JDBC에서 반복적으로 작성해야 하는 연결 관리, 자원 해제, 예외 처리 등의 코드를 자동화하여 코드가 간결해집니다.
2. 생산성 향상: 개발자는 비즈니스 로직에 집중할 수 있어 개발 효율이 높아집니다.
3. 예외 처리의 단순화: SQLException을 DataAccessException으로 변환하여 일관된 예외 처리가 가능합니다.
4. 자원 관리 자동화: 내부적으로 자원 해제를 처리하여 개발자가 수동으로 자원을 해제할 필요가 없습니다.
5. SQL 인젝션 방지: 바인딩 변수를 사용하여 안전한 SQL 실행이 가능합니다.
5.2 단점
1. 추상화의 한계: JDBC를 기반으로 하기 때문에 ORM처럼 객체와 데이터베이스 간의 완전한 매핑을 제공하지 않습니다.
2. SQL 의존성: SQL 쿼리를 직접 작성해야 하므로 데이터베이스에 종속적인 코드를 작성하게 될 수 있습니다.
3. 복잡한 쿼리 관리 어려움: 복잡한 쿼리나 대규모 프로젝트에서 SQL 관리가 어려울 수 있습니다.
5.3 JDBC와의 차이점 및 개선점
• 반복 코드 제거: JDBC에서는 Connection, PreparedStatement, ResultSet 등의 객체를 생성하고 수동으로 해제해야 하지만, JdbcTemplate은 이러한 작업을 내부적으로 처리합니다.
• 예외 처리 간소화: JDBC에서는 SQLException을 매번 처리해야 하지만, JdbcTemplate은 이를 런타임 예외로 변환하여 예외 처리를 단순화합니다.
• 코드 가독성 향상: 메서드 체이닝과 람다 표현식을 활용하여 코드가 더 읽기 쉬워집니다.
• 생산성 향상: 개발자는 비즈니스 로직에 집중할 수 있으며, 데이터베이스 작업을 빠르게 구현할 수 있습니다.
6. 트랜잭션 관리
JdbcTemplate은 Spring의 트랜잭션 관리 기능과 쉽게 통합할 수 있습니다.
6.1 @Transactional 어노테이션 사용
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserJdbcTemplateDao userDao;
@Transactional
public User createUser(String name, Integer age, String job, String specialty) {
return userDao.save(name, age, job, specialty);
}
}
• **@Transactional**을 메서드나 클래스 레벨에 적용하여 해당 범위 내에서 트랜잭션이 관리됩니다.
• 트랜잭션은 기본적으로 런타임 예외가 발생하면 롤백됩니다.
6.2 프로그래밍 방식의 트랜잭션 관리
• 필요에 따라 TransactionTemplate을 사용하여 프로그래밍 방식으로 트랜잭션을 관리할 수 있습니다.
7. JdbcTemplate 사용 시 고려사항
7.1 SQL 인젝션 방지
• 바인딩 변수 사용: 쿼리에서 ?를 사용하고 파라미터를 별도로 전달하여 SQL 인젝션 공격을 방지합니다.
7.2 예외 처리
• 일관된 예외 처리: DataAccessException과 그 하위 클래스를 통해 데이터 접근 예외를 일관되게 처리합니다.
7.3 성능 최적화
• 배치 업데이트 사용: 대량의 데이터를 처리할 때는 jdbcTemplate.batchUpdate()를 사용하여 성능을 향상시킵니다.
• 커넥션 풀 설정 조정: DataSource의 커넥션 풀 크기와 같은 설정을 조정하여 성능을 최적화합니다.
7.4 NamedParameterJdbcTemplate 사용
• 가독성 향상: 이름 기반 파라미터를 지원하는 NamedParameterJdbcTemplate을 사용하여 코드의 가독성을 높일 수 있습니다.
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
@RequiredArgsConstructor
public class UserNamedJdbcTemplateDao {
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public User findById(int userId) {
String sql = "SELECT * FROM \"user\" WHERE id = :id";
Map<String, Object> params = Collections.singletonMap("id", userId);
return namedParameterJdbcTemplate.queryForObject(
sql,
params,
(resultSet, rowNum) -> new User(
// 필드 매핑
)
);
}
}
8. 마무리
JdbcTemplate은 JDBC의 복잡한 부분을 추상화하여 개발자가 데이터베이스 작업을 더욱 효율적으로 수행할 수 있게 해주는 강력한 도구입니다. 제공해주신 UserJdbcTemplateDao 예제는 JdbcTemplate의 장점을 잘 활용하여 코드의 간결성과 가독성을 높였습니다.
하지만 JdbcTemplate은 SQL을 직접 작성해야 하므로 데이터베이스 종속적인 코드가 발생할 수 있습니다. 이러한 경우에는 ORM 프레임워크인 JPA/Hibernate를 사용하는 것도 고려해볼 수 있습니다.
프로젝트의 규모와 요구사항에 따라 JDBC, JdbcTemplate, JPA 등을 적절히 선택하여 사용하는 것이 중요합니다.
'spring' 카테고리의 다른 글
[Spring] @Transaction (1) | 2024.11.28 |
---|---|
[Spring] Spring의 트랜잭션 동기화(Transaction Synchronization) (0) | 2024.11.28 |
[Spring] JDBC (0) | 2024.11.22 |
[Spring] Spring Boot를 통한 JDBC, JdbcTemplate, JPA의 이해와 비교 (1) | 2024.11.22 |
[Spring] JdbcTemplate과 일반 JDBC 코드의 차이점 (1) | 2024.11.19 |