목차
개요
⭐ JDBC Template을 알아보기 전, 왜 필요하게 되었는지 간단하게 알아보겠습니다. JDBC Template을 사용하기 전에는 개발자들이 직접 JDBC를 통하여, SQL문을 작성하였습니다. JDBC로 직접 SQL문을 작성했을 때, 다음과 같은 문제가 발생하였습니다.
- 중복되고 반복적인 SQL 코드
- 비즈니스 로직과 SQL이 혼재되어 코드의 가독성과 유지보수성이 저하됩니다. (관심사의 분리가 잘 안됩니다.)
- 객체 매핑의 불편함과 타입 오류 가능성
- `ResultSet`을 수동으로 읽어 객체에 매핑해야 합니다.
- 예: `resultSet.getInt("age")` 에서처럼 수동으로 `age`가 null이면 `NullPointerException`이 발생할 수도 있습니다.
- 컴파일 타임 검증 불가
- SQL 문법 오류, 잘못된 컬럼명 등은 컴파일 타임이 아닌 런타임에서야 오류 발생
- → 디버깅 비용과 테스트 시간 증가합니다.
- 예외 처리의 불편함
- JDBC는 모든 SQL 관련 오류를 Checked Exception (SQLException) 으로 던집니다.
- DB 예외 유형별로 구체적 처리 어렵고, 코드가 지저분해집니다.
- 트랜잭션, 커넥션 관리의 반복
- Connection → Statement로 Query 실행 → ResultSet 처리 → 자원 정리(close)를 모두 수동으로 해야 합니다.
- 자원 해제을 놓치면, 커넥션 누수로 인해 서버가 과부하/다운됩니다.
💡이러한 문제를 해결을 위해 `Persistence Framework`가 등장했습니다! `Persistence Framework`는 2가지가 있습니다.
- SQL Mapper : JDBC Template, MyBatis
- ORM : JPA, Hibernate
→ 이 중 `JDBC Template`에 대해서 알아보겠습니다.
JDBC Template
정의
SQL 실행 및 JDBC 관련 작업(연결, 실행, 예외 처리, 자원 정리 등)을 간단하게 만들어 주는 클래스입니다.
의존성 추가
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
}
- JDBC를 사용하기 위해 추가한 의존성에 JDBC Template이 포함되어있습니다.
- `spring-boot-starter-jdbc`를 사용하면 `DataSource`를 기반으로 `JdbcTemplate` 빈이 자동 등록됩니다.
구조
역할
- DB 연결을 관리합니다.
- `DataSource`를 통해 Connection을 자동으로 얻고, 닫아줍니다.
- SQL 실행 처리합니다.
- `PreparedStatement` 생성 및 파라미터 바인딩, SQL 실행 수행합니다.
- ResultSet 처리 반복합니다.
- 쿼리 결과를 반복하며, 각 행마다 RowMapper를 호출합니다.
- 예외 처리를 단순화합니다.
- `SQLException`을 `DataAccessException`으로 추상화합니다.
- 리소스를 자동으로 해제합니다.
- Connection, Statement, ResultSet을 try-catch-finally 없이 자동으로 해제해줍니다.
더보기
RowMapper
- Query 수행 결과(ResultSet)를 한줄 씩 읽어와 Java 객체로 변환할 때 사용합니다.
- RowMapper로 매핑 코드를 재사용할 수 있습니다.
- 😂 하지만, Query 결과값을 객체 인스턴스에 매핑하는데 여전히 많은 코드가 필요합니다.
예제 코드
// UserRowMapper.java
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
public class UserRowMapper implements RowMapper<User> {
// JDBCTemplate 에서 row 응답을 mapRow() 메서드에 rs 파라미터로 넘겨주어 객체에 매핑하기 쉽도록 도와준다.
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
var user = new User();
user.setId(rs.getInt("ID"));
user.setName(rs.getString("NAME"));
return user;
}
}
- RowMapper를 구현한 구현체를 직접 만들어야 합니다.
- RowMapper.mapRow : RowMapper를 상속받아 mapRow() 메서드를 구현하면, JDBC Template에서 row 응답을 mapRow() 메서드에 rs 파라미터로 넘겨주어 객체에 매핑하기 쉽도록 도와줍니다.
// DataRepository.java
package com.thesun4sky.jdbc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class DataRepository {
@Autowired
private JdbcTemplate jdbcTemplate;
// 테이블 생성
public void createTable() {
jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS users (id SERIAL, name VARCHAR(255))");
}
// 사용자 추가 (Create)
public void insertUser(String name) {
jdbcTemplate.update("INSERT INTO users (name) VALUES (?)", name);
}
// 사용자 ID로 이름 조회 (Read)
public String findUserNameById(Long id) {
return jdbcTemplate.queryForObject(
"SELECT name FROM users WHERE id = ?",
new Object[]{id},
String.class
);
}
// 사용자 ID로 User 조회 (Read)
public User findUserById(Long id) {
return jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
new UserRowMapper(),
id
);
}
// 사용자 이름 변경 (Update)
public void updateUser(Long id, String newName) {
jdbcTemplate.update("UPDATE users SET name = ? WHERE id = ?", newName, id);
}
// 사용자 삭제 (Delete)
public void deleteUser(Long id) {
jdbcTemplate.update("DELETE FROM users WHERE id = ?", id);
}
}
- 코드가 JDBC를 직접 사용했을 때보다 훨씬 간결해집니다.
- findUserById : jdbcTemplate.queryForObject() 메서드에서는 두번쨰 인자로 RowMapper 를 넣어줄 경우 해당 RowMapper 의 mapRow() 메서드를 사용하여 응답을 하도록 동작한다.
// JdbcApplication.java
package com.thesun4sky.jdbc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JdbcApplication {
public static void main(String[] args) {
// 어플리케이션 실행 컨텍스트 생성
var context = SpringApplication.run(JdbcApplication.class, args);
// 데이터 조회 클래스 빈 조회
var repository = context.getBean(DataRepository.class);
// 테이블 생성
repository.createTable();
// 유저정보 추가
repository.insertUser("Teasun Kim");
// 유저정보 조회
System.out.println("User Name: " + repository.findUserNameById(1L));
}
}
- 기존 JDBC를 직접 활용하는 방법에서는 서비스 로직과 DB 접근 로직이 같은 곳에 존재하여 역할분리가 제대로 이루어지지 않았지만, JDBC Template을 사용하여 역할분리가 잘 된 것을 확인할 수 있습니다.
단점
- SQL을 문자열로 직접 작성해야 합니다.
- 복잡한 SQL 로직은 유지보수가 어렵습니다.
- 객체 지향적 쿼리 작성이 어렵습니다.
- Entity 간의 연관 관계 매핑 등을 직접 처리해야 합니다.
- 예: 1:N, N:1 관계를 직접 조인하고 매핑해야 합니다.
- 매핑 코드 반복
- `ResultSet` → 객체로 변환하는 `RowMapper` 코드가 반복됩니다.
- 동적 쿼리 작성이 불편합니다.
- if, where, and 조건 등을 기반으로 한 동적 SQL 생성이 번거롭습니다.
💡 이러한 단점을 각각 보완하기 위해 MyBatis, JPA, QueryDSL 등이 있습니다.
'MySql' 카테고리의 다른 글
[JDBC] JPA(Java Persistence API) 왜 사용할까? (0) | 2025.06.26 |
---|---|
[JDBC] Query 요청을 위한 Statement vs PreparedStatement (0) | 2025.06.25 |
[JDBC] JDBC Driver 한 방 정리 (0) | 2025.06.24 |
[내일 배움 캠프, SQL 달리기 반] Lv5. 예산이 가장 큰 프로젝트는? (0) | 2025.03.27 |
[내일 배움 캠프, 달리기 반] Lv5. 가장 많이 팔린 품목은? (0) | 2025.03.26 |