스프링은 JDBC를 어떻게 추상화했는가. JdbcTemplate 정의
- JDBC API 사용 시 ① 접속 → ② 쿼리 → ③ 결과 처리라는 반복되는 패턴이 존재한다. 스프링은 이 반복 작업(커넥션 획득/해제, 예외 처리 등)을 대신 처리해 주는 JdbcTemplate 클래스를 제공한다.
- 왜 스프링에서 이런 기능을 제공할까? 위에서 서술한 반복 패턴은 애플리케이션이 DB와 소통할 때마다 매번 발생하는 보일러플레이트다. 스프링은 개발자가 이 반복 작업에서 벗어나 SQL과 비즈니스 로직 작성에만 집중할 수 있도록 JdbcTemplate을 제공한다.
아래와 같이 전과 후 코드를 비교해 보자
// Before
public User findById(int userId) throws SQLException {
Connection connection = null; // 1.
PreparedStatement statement = null; // 2.
ResultSet resultSet = null; // 3.
try {
connection = dataSource.getConnection(); // 1. Hikari 커넥션 풀에서 Connection 객체 하나를 꺼내옴
statement = connection.prepareStatement("SELECT * FROM \"user\" WHERE id = ?"); // 2. SQL을 실행할 Statement 객체 생성
statement.setInt(1, userId); // 3. SQL 실행
resultSet = statement.executeQuery();// 3. 결과를 ResultSet에 저장
if (resultSet.next()) {
return 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()
);
}
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "유저 정보가 존재하지 않습니다 - id : " + userId);
} catch (SQLException e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "자원에 대한 접근에 문제가 있습니다.");
} finally {
// 자원반납
if (resultSet != null) resultSet.close(); // 1.
if (statement != null) statement.close(); // 2.
if (connection != null) connection.close(); // 3.
}
}
// After
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
);
}
- Before/After 이점 정리
- try-catch-finally 제거 → Connection/Statement/ResultSet 자원 반납 코드가 사라짐
- SQLException을 스프링의 DataAccessException 계열(런타임 예외)로 자동 변환 → checked exception 처리 부담 감소
- SQL과 RowMapper(결과→엔티티 변환 로직)만 작성하면 됨 → 개발자는 "무엇을 조회할지"에만 집중
- DataSource 주입 방식
- @Configuration 클래스에서 @Bean으로 DataSource(HikariCP) 등록
- Spring Boot의 auto-configuration이 이 DataSource를 기반으로 JdbcTemplate 빈을 자동 생성
- Repository에서는 @RequiredArgsConstructor 생성자 주입으로 JdbcTemplate을 그대로 받아서 사용 (직접 new JdbcTemplate(dataSource) 할 필요 없음)
JdbcTemplate 주요 메서드
- queryForObject : 결과가 정확히 1건 일 때 (0건 / 2건 이상이면 예외)
- query : 결과가 여러 건일 때 (List<T> 반환, 없으면 빈 리스트)
- queryForList : 엔티티 매핑 없이 단순 값 리스트 조회 (List <String>)
- queryForStream : query와 비슷하지만 Stream<T>로 반환, 대용량 데이터 처리에 적합
- update : INSERT/UPDATE/DELETE, 영향받은 row 수(int) 반환한다.
💡 checked exception 처리 부담 감소가 무엇일까?
Java의 예외는 크게 두 가지로 나뉜다.
하나는 checked exception인데, 컴파일러가 "이거 처리 안 하면 컴파일 안 해줌"이라고 강제하는 예외다. SQLException이 대표적인 예. 그래서 JDBC 코드에서 SQL 관련 메서드를 호출하면, try-catch로 잡아주거나 메서드 시그니처에 throws SQLException을 붙여줘야만 컴파일이 된다. 즉 컴파일러 차원에서 "예외 상황 대비했어?"를 검사당하는 셈이다.
반대로 unchecked exception(RuntimeException 계열)은 처리를 안 해도 컴파일이 그냥 통과된다. catch를 하든 안 하든 자유고, 강제되지 않는다.
JdbcTemplate은 SQLException을 잡아서 RuntimeException 계열인 DataAccessException으로 바꿔서 던져주기 때문에, 매번 SQL 호출할 때마다 강제로 try-catch를 쓸 필요가 없어진다. 이게 "checked exception 처리 부담이 줄어든다"는 말의 의미다.
반응형
'코드잇 스프린트 > TIL' 카테고리의 다른 글
| [TIL] ORM은 뭔데 Spring은 왜 JPA를 사용할까 (0) | 2026.09.04 |
|---|---|
| [TIL] JDBC와 Transaction 처리 - 스프링이 구현하는 방식 (0) | 2026.09.04 |
| [TIL] JDBC API로 DB 연결하고 쿼리 결과 다루기 (0) | 2026.09.03 |
| [TIL] 데이터베이스 설계 순차적 정리 (0) | 2026.09.02 |
| [TIL] 물리적 모델링 (0) | 2026.09.02 |