JdbcTemplate 사용

대영·2023년 11월 12일
2

Spring

목록 보기
2/16

🙏내용에 대한 피드백은 언제나 환영입니다!!🙏

이 내용은 Spring의 시작에서 이어진다.
Jdbc구조로 만들었던 내용을 JdbcTemplate를 이용하여 CRUD구조를 변경해 보았다.

📌JdbcTemplate이란 무엇인가!

JDBC는 일반적으로 반복적이고 번거로운 코드를 필요로 한다. JdbcTemplate은 스프링 프레임워크에서 제공하는 JDBC(Java Database Connectivity)를 간소화하고 편리하게 사용할 수 있도록 도와주는 클래스이다.
JDBC는 자바에서 데이터베이스에 접근하기 위한 표준 API이지만, 보다 편리하게 데이터베이스와 상호작용하기 위해서는 반복적이고 번거로운 코드가 필요하다.
이러한 문제를 해결하고자 JdbcTemplate이 등장하였다.

JdbcTemplate을 사용하면 데이터베이스 연결, 쿼리 실행, 결과 처리, 예외 처리 등을 편리하게 처리할 수 있다.

👉주요기능

query(): SELECT 쿼리를 실행하고 결과를 가져오는데 사용.
update(): INSERT, UPDATE, DELETE 쿼리를 실행하고 영향을 받은 행의 수를 반환.
queryForObject(): 단일 결과를 가져오는데 사용되며, 결과가 하나의 값인 경우에 유용.
queryForList(): 결과를 리스트로 가져오는데 사용.
execute(): 임의의 쿼리를 실행하고 그 결과를 가져오는 데 사용.
RowMapper: ResultSet 한 로우의 결과를 오브젝트에 매핑.

❕RowMapper에 대해 잠시 설명!

아래의 코드를 보자! 나의 글 "Spring의 시작"에서 가져온 일부 코드이다. (getAll() 메소드)

// 쿼리 받기
ResultSet rs = ps.excuteQuery("SELECT * FROM USER");

// 결과값 가져오기
while(rs.next()) {
     // user 객체에 값 저장
     user = new User();
     user.setId(rs.getInt(1));
     user.setName(rs.getString(2));
     user.setDescription(rs.getString(3));
     
     // 리스트에 추가
     userList.add(user);
}
rs.close();

이 코드에서 보면 3가지 과정이 거쳐진다고 볼 수 있다.
1. ResultSet에 쿼리 받기.
2. 객체에 값 저장.
3. ResultSet 반환.

RowMapper은 이러한 과정이 다 담겨있다.

private RowMapper<User> userMapper =
            new RowMapper<User>() { // ResultSet 한 로우의 결과를 오브젝트에 매핑해주는 RowMapper.
                // 이 시점에 이미 ResultSet은 첫 번째 로우를 가르키기에 rs.next() 호출 안해도 됨.
                @Override
                public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                    User user = new User();
                    user.setId(rs.getString("id"));
                    user.setName(rs.getString("name"));
                    user.setPassword(rs.getString("password"));

                    return user;
                }
            };

👉장점, 단점

JDBCTemplate의 장, 단점에 대해서 알아 보겠다.

장점

  1. JDBC의 반복적인 코드를 간소화하여 가독성을 향상시킨다.
  2. 1번과 같은 이유로 생산성 또한 향상된다.

단점

  1. SQL 쿼리를 직접 다루기 때문에, 데이터베이스 변화에 따라 코드 수정이 필요하다.
  2. 조건에 맞게 검색해야하는 경우 등. 동적 쿼리문에 취약하다.

📌코드

아래의 코드는 처음에서 말했듯이 "Spring의 시작" 글에 담겨 있는 CRUD구조인 UserDaoRepository class를 JdbcTemplate 구조로 바꾼 것이다.

package com.practice.demo.Repository;

import com.practice.demo.Dao.ConnectionMaker;
import com.practice.demo.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class UserDaoRepository {

    private JdbcTemplate jdbcTemplate;
    @Autowired
    public void setDataSource(DataSource dataSource) {
        jdbcTemplate = new JdbcTemplate(dataSource);
    }

    private RowMapper<User> userMapper =
            new RowMapper<User>() { // ResultSet 한 로우의 결과를 오브젝트에 매핑해주는 RowMapper.
                // 이 시점에 이미 ResultSet은 첫 번째 로우를 가르키기에 rs.next() 호출 안해도 됨.
                @Override
                public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                    User user = new User();
                    user.setId(rs.getString("id"));
                    user.setName(rs.getString("name"));
                    user.setPassword(rs.getString("password"));

                    return user;
                }
            };

    public void add(final User user) throws DuplicateKeyException {  // 익명 내부 클래스를 사용함. 파일 크기 줄이기. 로컬 변수를 바로 사용 가능.
        this.jdbcTemplate.update("insert into users(id, name, password) values(?,?,?)",
                user.getId(), user.getName(), user.getPassword());
    }

        public User get(String id) {                                           // queryForObject()는 쿼리의 결과가 로우 하나이기에. 여러개라면 query()
            return this.jdbcTemplate.queryForObject("select * from users where id = ?",    // "로우(row)"란 데이터베이스 테이블에 저장되어 있는 정보.
                    new Object[]{id},   // SQL에 바인딩할 파라미터 값. 가변인자 대신 배열을 사용한다. 뒤에 다른 파라미터가 있으므로 Object 타입 배열을 사용해야함
                    this.userMapper);
        }


        public void deleteAll() {
                this.jdbcTemplate.update("delete from users");
            }

        public void delete(User user) { this.jdbcTemplate.update("DELETE FROM users WHERE id = ?", user.getId());}

        public int getCount() {
            return this.jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Integer.class);
        }

        public List<User> getAll() {   // query()의 리턴타입은 List<T>이기에 반환형을 List<User>.
            return this.jdbcTemplate.query("select * from users order by id",  // 로우가 여러개 일 때는 query() 사용.
                        this.userMapper);
                // query()는 queryForObject()와 달리 아무런 데이터가 없으면 예외를 던지지 않고 크기가 0인 List<T> 오브젝트를 돌려준다.
                // 그래서 Test 메소드에서 데이터를 추가해주지 않고 assertThat(users0.size()).isEqualTo(0); 을 해보면 된다.
        }

        public void update(User user1) {
            this.jdbcTemplate.update("update users set name = ?, password = ? where id = ? ",
                    user1.getName(), user1.getPassword(), user1.getId());
        }
}

💡느낀점

코드가 간편해졌다. 또한, 가독성도 높아졌다. Spring을 공부할 때마다 새로운 것을 알아가면서 더 생산적으로 코드를 작성하도록 바뀌고 있다는 것을 생각하였다.

profile
Better than yesterday.

0개의 댓글