본문 바로가기
Spring

Spring - JdbcTemplate

by icblue21 2022. 11. 21.
728x90

JdbcTemplate

데이터베이스에서 모든 고객 정보를 반환하는 메소드 코드입니다.

@Override
public List<Member> selectAll() {
    List<Member> memberList = new LinkedList<>();
    try {
        conn = JDBCMgr.getConnection(); // DB 커넥션 객체 생성
        stmt = conn.prepareStatement(MEMBER_SELECT_ALL); // 쿼리문 저장 및 컴파일

        rs = stmt.executeQuery();
        while (rs.next()) {
            String uId = rs.getString("uId");
            String uPw = rs.getString("uPw");
            String uEmail = rs.getString("uEmail");

            memberList.add(new Member(uId, uPw, uEmail));
        }

    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        JDBCMgr.close(rs, stmt, conn); // DB 커넥션 객체 및 JDBC 관련 객체 반환
    }
    return memberList;
}

다음 코드는 얼핏 보기엔 괜찮아 보이지만 SQL문 실행과 관련없는 DB 커넥션 객체를 생성하거나, 객체를 반환하는 코드가 중복되고 있습니다.
따라서, 이러한 반복 작업들을 제거하고 실제 핵심 로직만 구현할 수 있도록 도와주는 API, JdbcTemplate을 사용하여 코드를 더 깔끔하게 수정할 필요가 있습니다. JdbcTemplate은 Spring에서 제공하는 JDBC 접근법 중 하나로 가장 기본적으로 많이 쓰이고 있는 API입니다. JdbcTemplate을 사용하면 위에서의 비효율적인 코드를 JdbcTemplate을 사용하면 다음과 같이 변경할 수 있습니다.

@Override
public List<Member> selectAll() {
    return jdbcTemplate.query(MEMBER_SELECT_ALL, (rs, rowNum) -> new Member(
            rs.getString("uId"),
            rs.getString("uPw"),
            rs.getString("uEmail")));
}

JdbcTemplate의 장점

JdbcTemplate의 장점은 다음과 같습니다.

  1. 설정이 간편하다.
  2. 복잡하고 반복되는 알고리즘을 캡슐화하여 재사용하는 디자인 패턴인 템플릿 콜백 패턴이 적용되어 있다.
    • JDBC를 사용할 때 발생하는 대부분의 반복 작업을 처리
    • 개발자는 SQL을 작성하고 전달할 파라미터 매핑 + 응답 값 매핑만 하면 된다.
  3. 위 코드에서 반복으로 이루어지는 다음과 같은 작업들을 대체한다.
    1. 커넥션 획득
    2. statement를 준비하고 실행
    3. 결과를 반복하도록 루프를 실행
    4. Connetion / Statement / ResultSet 종료
    5. 트랜잭션을 위한 Connection 동기화
    6. 예외 발생 시 스프링 예외 변환기 실행

JdbcTemplate 위치와 구조

JdbcTemplate은 내부적으로 JDBC API를 이용하여 실제 DB연동에 필요한 반복적인 작업을 수행합니다. 따라서, 개발자는 JdbcTemplate 클래스가 어떻게 JDBC API를 이용하는지 전혀 신경 쓸 필요가 없습니다.

JdbcTemplate 예외처리

JdbcTemplate에서는 순수 JDBC에서 발생할 수 있는 SQLException을 DataAccessException으로 랩핑하여 반환합니다.
이렇게 랩핑을 하는 이유는 DB마다 에러의 종류와 원인이 다양하고, JDBC드라이버에서 SQLException을 담을 상태코드를 정확하게 만들어주지 않아 SQLException의 호환성이 좋지 않기 때문입니다.

DataAcessException으로 랩핑할 때 계층구조 클래스 중 하나로 매핑해야 합니다.

JdbcTemplate 사용법

JdbcTemplate을 사용하기 위해서는 DataSource 빈을 생성해야 합니다. DataSource는 DB와 관계된 커넥션 정보를 담고있으며 빈으로 등록하여 인자를 넘기는 과정을 통해 DB와 연결하기 위해 사용합니다.또한 DBCP의 기능을 가지고 있습니다.

1. DataSource 빈 생성

datasource.properties

######################### h2 db 설정 #########################
spring.datasource.url=jdbc:h2:tcp://localhost/~/JDBC
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=


###################### HikariCP Properties ######################
# https://github.com/brettwooldridge/HikariCP
com.zaxxer.hikari.config.poolName=springHikariCP
com.zaxxer.hikari.config.maximumPoolSize=10
com.zaxxer.hikari.config.idleTimeout=3000

WebAppConfig.java

@Bean
public HikariDataSource hikariDataSource() {
    // com.zaxxer.hikari.HikariConfig

    com.zaxxer.hikari.HikariConfig hikariConfig = new HikariConfig();
    hikariConfig.setDriverClassName(environment.getProperty("spring.datasource.driverClassName"));
    hikariConfig.setJdbcUrl(environment.getProperty("spring.datasource.url"));
    hikariConfig.setUsername(environment.getProperty("spring.datasource.username"));
    hikariConfig.setPassword(environment.getProperty("spring.datasource.password"));

    hikariConfig.setPoolName(environment.getProperty("com.zaxxer.hikari.config.poolName"));
    hikariConfig.setMaximumPoolSize(Integer.parseInt(environment.getProperty("com.zaxxer.hikari.config.maximumPoolSize")));
    hikariConfig.setIdleTimeout(Long.parseLong(environment.getProperty("com.zaxxer.hikari.config.idleTimeout")));

    com.zaxxer.hikari.HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);

    System.out.println(hikariDataSource);

    return hikariDataSource;
}

@Bean
public DataSource dataSource() {
    return hikariDataSource(); // hikariConfig

}

2. JdbcTemplate 빈 생성

WebAppConfig.java

@Bean
public JdbcTemplate jdbcTemplate() {
    JdbcTemplate jdbcTemplate = new JdbcTemplate();
    jdbcTemplate.setDataSource(dataSource());
    return jdbcTemplate;
}

3. DAO 클래스에 JdbcTemplate 주입

MemberDAO.class

@Repository
public class MemberDAO implements IMemberDAO {
        private JdbcTemplate jdbcTemplate;

        public MemberDAO() {
        }

        @Autowired
        public MemberDAO(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }

        private static final String MEMBER_SEARCH = "select * from member where uId like ? or uEmail like ?";

        private static final String MEMBER_SELECT_ALL = "select * from member";
        private static final String MEMBER_SELECT = "select * from member where uId = ?";
        private static final String MEMBER_INSERT = "insert into member values(?, ?, ?)";

        private static final String MEMBER_PASSWORD_UPDATE = "update member set uPw = ? where uId = ?";
        private static final String MEMBER_EMAIL_UPDATE = "update member set uEmail = ? where uId = ?";
        private static final String MEMBER_DELETE = "delete member where uId = ?";
        private static final String MEMBER_DELETE_ALL = "delete member";


        @Override
        public List<Member> search(String q) { // 이름이나 이메일로 검색
            return jdbcTemplate.query(MEMBER_SEARCH, (rs, rowNum) -> new Member(
                    rs.getString("uId"),
                    rs.getString("uPw"),
                    rs.getString("uEmail")), "%" + q + "%", "%" + q + "%");
        }

        @Override
        public Member select(String uId) { // PK를 통한 SELECT
            List<Member> memberList = jdbcTemplate.query(MEMBER_SELECT, (rs, rowNum) -> new Member(
                    rs.getString("uId"),
                    rs.getString("uPw"),
                    rs.getString("uEmail")), uId);

            if (memberList.isEmpty()) return null;
            return memberList.get(0);

        }

        @Override
        public List<Member> selectAll() {
            return jdbcTemplate.query(MEMBER_SELECT_ALL, (rs, rowNum) -> new Member(
                    rs.getString("uId"),
                    rs.getString("uPw"),
                    rs.getString("uEmail")));
        }

        @Override
        public int insert(Member member) {
            return jdbcTemplate.update(MEMBER_INSERT,
                    member.getuId(), member.getuPw(), member.getuEmail());
        }

        @Override
        public int insertAll(List<Member> members) {
            return members.stream().map(m -> insert(m)) // 1
                    .collect(Collectors.toList()) // [1, 1, 1, 1, 1]
                    .stream()
                    .reduce((x, y) -> x + y) // 1 + 1 + 1 + 1 + 1
                    .orElse(0);
        }

        @Override
        public int updatePassword(String uId, String uPw) {
            return jdbcTemplate.update(MEMBER_PASSWORD_UPDATE, uPw, uId);
        }

        @Override
        public int updateEmail(String uId, String uEmail) {
            return jdbcTemplate.update(MEMBER_EMAIL_UPDATE, uEmail, uId);
        }


        @Override
        public int delete(String uId) {
            return jdbcTemplate.update(MEMBER_DELETE, uId);
        }

        @Override
        public int deleteAll() {
            return jdbcTemplate.update(MEMBER_DELETE_ALL);
        }

}

JdbcTemplate 함수

  • select
    • query() - 주어진 SQL에 바인딩할 인수를 RowMapper를 통해 행을 매핑하고 결과 리스트에 저장
    • queryForObject() - 주어진 SQL에 바인딩할 인수를 RowMapper을 통해 행을 매핑하고 결과 객체 반환
  • insert / update / delete
    • update() - 주어진 SQL에 인수를 바인딩하고 준비된 명령문을 통해 단일 SQL 트랜잭션 작업을 실행함

'Spring' 카테고리의 다른 글

Spring - MyBatis  (0) 2022.11.21
Spring - Transaction Manager  (0) 2022.11.21
Spring - ConnectionPool  (0) 2022.11.21
Spring - Log4j2  (0) 2022.11.21
Spring - ExceptionHandler  (0) 2022.11.21

댓글