18. 스프링 JDBC 기능

스프링 프레임워크가 제공하는 JDBC 추상화 계층과 JdbcTemplate 사용법을 다룬다.


1. 기존 JDBC의 문제점

순수 JDBC는 이해하기 쉽고 직관적이지만, 코드가 반복적이고 리소스 관리가 번거롭다.

Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
    conn = dataSource.getConnection();
    pstmt = conn.prepareStatement(
        "SELECT * FROM member WHERE id = ?");
    pstmt.setString(1, id);
    rs = pstmt.executeQuery();
    // 결과 처리
} catch (SQLException e) {
    e.printStackTrace();
} finally {
    // 리소스 해제 (역순)
    if (rs != null) rs.close();
    if (pstmt != null) pstmt.close();
    if (conn != null) conn.close();
}
문제설명
반복 코드Connection 획득 → Statement 생성 → 실행 → 해제가 매번 반복
리소스 누수close() 누락 시 커넥션 풀 고갈
예외 처리 복잡SQLException이 모든 상황에 사용되어 원인 파악이 어려움
SQL 혼재비즈니스 로직과 데이터 접근 코드가 뒤섞임

2. 스프링 JDBC의 개선점

스프링 JDBC는 기존 JDBC의 장점은 유지하면서 반복적인 저수준 코드를 제거한다.

  기존 JDBC vs 스프링 JDBC

  [기존 JDBC]
  Connection 획득
       │
       ▼
  Statement 생성
       │
       ▼
  파라미터 바인딩
       │
       ▼
  SQL 실행 ← 개발자 관심 영역
       │
       ▼
  결과 매핑 ← 개발자 관심 영역
       │
       ▼
  예외 처리
       │
       ▼
  리소스 해제

  [스프링 JDBC]
  SQL 작성   ← 개발자
  결과 매핑  ← 개발자
  나머지     ← 프레임워크
구분기존 JDBC스프링 JDBC
커넥션 관리직접 획득/해제자동 처리
예외 처리SQLException 직접 처리DataAccessException으로 변환
리소스 해제finally에서 직접자동 해제
코드량많음SQL과 매핑만 작성

3. 스프링 JDBC 설정

설정 파일 구성

파일역할
web.xmlContextLoaderListener로 빈 설정 파일을 로드
jdbc.propertiesDB 연결 정보(URL, 드라이버, 계정) 저장
action-dataSource.xmlDataSource 빈 설정
action-service.xmlService 빈 설정

jdbc.properties

jdbc.driverClass=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:@localhost:1521:XE
jdbc.username=scott
jdbc.password=tiger

DataSource 빈 설정

<beans ...>
    <!-- properties 파일 로드 -->
    <context:property-placeholder
        location="classpath:jdbc.properties" />

    <!-- DataSource 설정 -->
    <bean id="dataSource"
      class="org.apache.commons.dbcp
             .BasicDataSource"
      destroy-method="close">
        <property name="driverClassName"
            value="${jdbc.driverClass}" />
        <property name="url"
            value="${jdbc.url}" />
        <property name="username"
            value="${jdbc.username}" />
        <property name="password"
            value="${jdbc.password}" />
    </bean>
</beans>

web.xml — ContextLoaderListener 설정

<web-app ...>
    <listener>
        <listener-class>
            org.springframework.web
            .context.ContextLoaderListener
        </listener-class>
    </listener>

    <context-param>
        <param-name>
            contextConfigLocation
        </param-name>
        <param-value>
            /WEB-INF/config/action-*.xml
        </param-value>
    </context-param>
</web-app>
ContextLoaderListener는 서블릿 컨텍스트가 초기화될 때 스프링의 ApplicationContext를 생성한다. 와일드카드(action-*.xml)를 사용하면 여러 설정 파일을 한 번에 로드할 수 있다.

4. JdbcTemplate 클래스

스프링 JDBC의 핵심 클래스로, SQL 실행과 결과 매핑을 간결하게 처리한다.

주요 메서드

기능메서드설명
INSERT/UPDATE/DELETEupdate(String sql)SQL 실행, 영향받은 행 수 반환
INSERT/UPDATE/DELETEupdate(String sql, Object[] args)파라미터 바인딩 후 실행
단건 조회queryForObject(String sql, RowMapper, args)결과를 객체로 매핑
목록 조회query(String sql, RowMapper)결과를 리스트로 매핑
단일 값 조회queryForObject(String sql, Class)단일 값(int, String 등) 반환

DAO 클래스 작성

@Repository("memberDAO")
public class MemberDAOImpl implements MemberDAO {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    // 전체 회원 조회
    @Override
    public List<MemberVO> listMembers() {
        String sql = "SELECT * FROM t_member"
            + " ORDER BY joinDate DESC";

        List<MemberVO> list =
            jdbcTemplate.query(sql,
                new RowMapper<MemberVO>() {
            @Override
            public MemberVO mapRow(
                    ResultSet rs, int rowNum)
                    throws SQLException {
                MemberVO vo = new MemberVO();
                vo.setId(rs.getString("id"));
                vo.setPwd(rs.getString("pwd"));
                vo.setName(rs.getString("name"));
                vo.setEmail(rs.getString("email"));
                vo.setJoinDate(
                    rs.getDate("joinDate"));
                return vo;
            }
        });
        return list;
    }

    // 회원 추가
    @Override
    public int addMember(MemberVO vo) {
        String sql = "INSERT INTO t_member"
            + " (id, pwd, name, email)"
            + " VALUES (?, ?, ?, ?)";

        return jdbcTemplate.update(sql,
            vo.getId(),
            vo.getPwd(),
            vo.getName(),
            vo.getEmail());
    }

    // 회원 삭제
    @Override
    public int delMember(String id) {
        String sql = "DELETE FROM t_member"
            + " WHERE id = ?";

        return jdbcTemplate.update(sql, id);
    }
}

5. RowMapper를 이용한 결과 매핑

RowMapperResultSet의 각 행을 자바 객체로 변환하는 콜백 인터페이스다.

  query() 실행 흐름

  jdbcTemplate.query()
       │
       ▼
  SQL 실행 → ResultSet
       │
       ▼
  ┌──────────────┐
  │  RowMapper   │
  │  mapRow() 호출│
  │  (행마다 반복) │
  └──────┬───────┘
         │
         ▼
  List<MemberVO> 반환

람다식으로 간결하게 작성

public List<MemberVO> listMembers() {
    String sql =
        "SELECT * FROM t_member";

    return jdbcTemplate.query(sql,
        (rs, rowNum) -> {
            MemberVO vo = new MemberVO();
            vo.setId(rs.getString("id"));
            vo.setPwd(rs.getString("pwd"));
            vo.setName(rs.getString("name"));
            vo.setEmail(rs.getString("email"));
            return vo;
        });
}
RowMapper는 함수형 인터페이스이므로 람다식으로 간결하게 작성할 수 있다. Java 8 이상이라면 람다식 사용을 권장한다.

6. JdbcTemplate을 빈으로 주입하기

XML 설정 방식

<!-- JdbcTemplate 빈 등록 -->
<bean id="jdbcTemplate"
  class="org.springframework.jdbc
         .core.JdbcTemplate">
    <property name="dataSource"
        ref="dataSource" />
</bean>

<!-- DAO에 JdbcTemplate 주입 -->
<bean id="memberDAO"
    class="com.spring.dao.MemberDAOImpl">
    <property name="jdbcTemplate"
        ref="jdbcTemplate" />
</bean>

어노테이션 방식

@Repository("memberDAO")
public class MemberDAOImpl
        implements MemberDAO {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    // ...
}
방식장점단점
XML설정이 한 곳에 모임코드와 설정이 분리되어 추적 어려움
어노테이션코드에서 바로 확인 가능설정 변경 시 재컴파일 필요

7. DataAccessException 계층 구조

스프링은 JDBC의 SQLException을 의미 있는 예외 계층으로 변환한다.

스프링 예외원인
BadSqlGrammarExceptionSQL 문법 오류
DuplicateKeyException중복 키 위반
DataIntegrityViolationException무결성 제약 위반
DataAccessResourceFailureExceptionDB 연결 실패
EmptyResultDataAccessException결과가 없는데 단건 조회 시도
DataAccessExceptionUnchecked Exception(RuntimeException)이다. 기존 JDBC의 SQLException(Checked)과 달리 try-catch를 강제하지 않으므로, 필요한 곳에서만 선택적으로 처리할 수 있다.

요약

개념한 줄 정리
스프링 JDBC기존 JDBC의 반복 코드를 제거한 추상화 계층
JdbcTemplateSQL 실행과 결과 매핑을 담당하는 핵심 클래스
RowMapperResultSet → 자바 객체 변환 콜백
DataSourceDB 연결 정보를 관리하는 빈
DataAccessExceptionJDBC 예외를 의미 있는 Unchecked 예외로 변환

기존 JDBC vs 스프링 JDBC 비교

구분기존 JDBC스프링 JDBC
커넥션 관리DriverManager 직접 사용DataSource 빈으로 주입
SQL 실행PreparedStatement 직접 생성JdbcTemplate 메서드 호출
결과 매핑ResultSet 수동 순회RowMapper 콜백
예외 처리SQLException (Checked)DataAccessException (Unchecked)
리소스 해제finally에서 직접 close프레임워크가 자동 처리