Java Category/Spring

[Spring DB] JDBC Template

ReBugs 2024. 3. 29.

공식 메뉴얼 

DataSource 설정

JDBC Template 사용을 위해서는 데이터베이스와의 연결을 관리하는 DataSource를 설정해야 한다. 

 

application.properties

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=yourName
spring.datasource.password=yourPassword

이렇게 하면 내부적으로 DataSource 빈을 자동으로 생성하고 구성합니다.

 

JdbcTemplate 인스턴스 생성

DataSource 설정 후, JdbcTemplate 인스턴스를 생성하고 이를 빈으로 등록한다.

@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
    return new JdbcTemplate(dataSource);
}

 

CRUD 작업

Create

jdbcTemplate.update("INSERT INTO 테이블명 (컬럼1, 컬럼2) VALUES (?, ?)", 값1, 값2);

 

Read

String name = jdbcTemplate.queryForObject("SELECT 이름 FROM 테이블명 WHERE 아이디 = ?", new Object[]{아이디}, String.class);

 

조회

List<String> names = jdbcTemplate.query("SELECT 이름 FROM 테이블명", (rs, rowNum) -> rs.getString("이름"));

 

Update

jdbcTemplate.update("UPDATE 테이블명 SET 컬럼명 = ? WHERE 조건", 새값);

 

Delete

jdbcTemplate.update("DELETE FROM 테이블명 WHERE 조건", 조건값);

 

jdbcTemplate.update(), queryForObject(), query()

jdbcTemplate.update

update 메소드는 INSERT, UPDATE, DELETE와 같은 DML(Data Manipulation Language) 작업을 실행할 때 사용된다. 즉, 데이터베이스에 데이터를 추가, 수정, 삭제할 때 이 메소드를 활용한다.

int updateCount = jdbcTemplate.update("INSERT INTO 테이블명 (컬럼1, 컬럼2) VALUES (?, ?)", 값1, 값2);

이 예시는 데이터베이스의 특정 테이블에 새로운 행을 추가하는 경우이다. update 메소드는 실행 후 영향을 받은 행의 수를 반환한다.

 

jdbcTemplate.queryForObject

queryForObject 메소드는 단일 객체를 반환할 때 사용된다. 이 메소드는 주로 단일 행의 결과를 반환하는 SELECT 쿼리에 사용되며, 결과가 정확히 하나의 객체로 매핑되어야 한다. 결과가 없거나 둘 이상일 경우 예외가 발생한다.

String itemName = jdbcTemplate.queryForObject("SELECT item_name FROM items WHERE id = ?", new Object[]{itemId}, String.class);

이 예시는 주어진 itemId에 해당하는 아이템의 이름을 데이터베이스에서 조회하여 반환한다. queryForObject는 SQL 쿼리, 쿼리에 바인딩할 파라미터 배열, 그리고 반환될 객체의 타입을 매개변수로 받는다.

 

 

queryForObject() 메소드 시그니처

<T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args) throws DataAccessException;
  • <T>: 제네릭 타입을 의미하며, 이 메소드가 반환할 객체의 타입을 지정한다.
  • T queryForObject: 메소드가 실행된 후 반환할 객체의 타입을 나타낸다.
  • String sql: 실행할 SQL 쿼리 문자열이다.
  • RowMapper<T> rowMapper: SQL 쿼리의 결과로 반환된 ResultSet을 객체로 매핑하는 방법을 정의한 RowMapper 인터페이스의 구현체이다.
  • Object... args: SQL 쿼리에 바인딩할 파라미터들. 이 파라미터들은 쿼리 내의 '?' 플레이스홀더에 순서대로 바인딩된다.
  • throws DataAccessException: 쿼리 실행 중 발생할 수 있는 예외를 나타낸다. DataAccessException은 Spring이 제공하는 데이터 접근 관련 예외의 베이스 클래스이다.

아래 예제는 queryForObject 메소드를 사용하여 id에 해당하는 사용자의 이름을 데이터베이스에서 조회하는 방법을 보여준다.

String sql = "SELECT name FROM users WHERE id = ?";
Long userId = 1L; // 조회하고자 하는 사용자의 ID

String userName = jdbcTemplate.queryForObject(sql, new RowMapper<String>() {
    @Override
    public String mapRow(ResultSet rs, int rowNum) throws SQLException {
        return rs.getString("name");
    }
}, userId);

또는 람다 표현식을 사용하여 더 간결하게 작성할 수도 있다

String userName = jdbcTemplate.queryForObject(sql, (rs, rowNum) -> rs.getString("name"), userId);

이 메소드는 결과가 정확히 한 행일 때 사용해야 한다. 쿼리 결과가 없거나 두 개 이상의 행이 반환되면, 

IncorrectResultSizeDataAccessException이 발생한다. 따라서 이 메소드를 사용할 때는 반환되는 결과가 항상 단 하나임을 확신

할 수 있을 때 사용하는 것이 좋다. 

 

jdbcTemplate.query

query 메소드는 하나 이상의 결과 행을 반환할 때 사용된다. 이 메소드는 리스트로 결과를 반환하며, 각 행은 RowMapper를 사용하여 자바 객체로 매핑된다.

List<Item> items = jdbcTemplate.query("SELECT * FROM items", (rs, rowNum) -> {
    Item item = new Item();
    item.setId(rs.getLong("id"));
    item.setItemName(rs.getString("item_name"));
    item.setPrice(rs.getInt("price"));
    item.setQuantity(rs.getInt("quantity"));
    return item;
});

이 예시는 items 테이블의 모든 행을 조회하고, 각 행을 Item 객체로 매핑하여 리스트로 반환한다. query 메소드는 SQL 쿼리와 RowMapper 인터페이스를 구현한 람다 표현식이나 클래스를 매개변수로 받는다.

 

ResultSet과 RowMapper

ResultSet을 직접 사용하는 방식과 RowMapper 인터페이스를 사용하는 방식은 데이터베이스에서 데이터를 조회하여 객체로 변환하는 과정에서 차이가 있다.

ResultSet을 직접 사용하는 방식

직접 처리: ResultSet을 사용하는 방식은 조회 결과를 반복문을 통해 직접 순회하며, 각 컬럼의 값을 얻어와서 객체의 필드에 하나씩 설정해야 한다. 이는 상당히 저수준의 작업이며, 개발자가 ResultSet의 관리와 객체 매핑의 전 과정을 책임진다.

 

오류 가능성: ResultSet을 직접 다루는 경우, 타입 오류나 컬럼 이름 오류 등이 발생할 가능성이 있으며, 이러한 오류를 직접 관리해야 한다.

 

자원 관리: ResultSet, Statement, Connection과 같은 JDBC 리소스들의 개방과 폐쇄를 개발자가 직접 관리해야 한다. 이는 코드의 복잡성을 증가시키고, 리소스 누수의 위험을 높일 수 있다.

 

 

RowMapper 인터페이스를 사용하는 방식

자동 처리: RowMapper를 사용하는 방식은 Spring JDBC Template과 함께 사용되며, ResultSet의 각 행을 어떻게 객체로 매핑할지를 정의한다. 그 후, 실제로 데이터베이스에서 데이터를 가져오는 작업과 객체 매핑은 Spring JDBC Template에 의해 자동으로 처리된다.

 

타입 안정성 및 가독성: RowMapper는 타입 안정성을 제공하며, 컬럼 값의 추출과 객체 필드 설정 코드를 한 곳에 모아둠으로써 가독성을 향상시킨다.

 

자원 관리: RowMapper를 사용할 때, JDBC 리소스들의 관리는 Spring JDBC Template에 의해 자동으로 처리된다. 이는 코드의 단순화를 도모하고, 리소스 누수의 위험을 줄여준다.
결론

 

RowMapper 인터페이스를 사용하는 방식은 ResultSet을 직접 사용하는 방식에 비해 개발자의 부담을 크게 줄여주며, 코드의 가독성과 유지보수성을 향상시킨다. 또한, Spring의 JDBC Template과 결합하여 사용됨으로써, 데이터베이스 연결과 같은 저수준의 작업을 추상화하여, 데이터 액세스 로직을 보다 효율적으로 작성할 수 있게 한다.

 

RowMapper 인터페이스

public interface RowMapper<T> {
    T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
  • T: 반환될 객체의 타입
  • ResultSet rs: 쿼리 결과 집합
  • int rowNum: 현재 행의 번호

 

다음은 User 클래스의 인스턴스로 데이터베이스 결과를 맵핑하는 예제이다. User 클래스가 다음 필드를 가지고 있다고 가정해 보자

public class User {
    private Long id;
    private String name;
    private String email;
    // getters and setters 생략
}

 

RowMapper를 사용하여 User 객체로 맵핑하는 예시는 다음과 같다

public class UserRowMapper implements RowMapper<User> {
    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        User user = new User();
        user.setId(rs.getLong("id"));
        user.setName(rs.getString("name"));
        user.setEmail(rs.getString("email"));
        return user;
    }
}

 

그리고 JdbcTemplate을 사용하여 UserRowMapper를 이용하는 방법은 다음과 같다

String sql = "SELECT id, name, email FROM users";
List<User> users = jdbcTemplate.query(sql, new UserRowMapper());

이 예제에서 jdbcTemplate.query 메소드는 주어진 SQL 쿼리를 실행하고, UserRowMapper를 사용하여 결과 ResultSet의 각 행을 User 객체로 변환한다. 이렇게 변환된 객체들은 리스트에 담겨 반환된다.

 

RowMapper를 메서드 내부에 정의하여 사용

주로 간단한 쿼리나, 특정 메서드에서만 사용되는 맵핑 로직을 구현할 때 유용하다. 이 방식은 RowMapper를 익명 클래스로 선언하거나, 람다 표현식(Java 8 이상)을 사용하여 직접 구현할 수 있다. 여기서는 두 가지 방법 모두를 예로 들어 설명하겠다.

User 객체를 위한 RowMapper

데이터베이스의 users 테이블에서 사용자 정보를 조회하고, 이를 User 객체로 맵핑하는 예제를 살펴보자. User 클래스가 다음과 같이 정의되어 있다고 가정하자

public class User {
    private Long id;
    private String name;
    private String email;
    // 생성자, getter, setter 생략
}

 

익명 클래스 사용

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

public List<User> findAllUsers(JdbcTemplate jdbcTemplate) {
    String sql = "SELECT id, name, email FROM users";

    List<User> users = jdbcTemplate.query(sql, new RowMapper<User>() {
        @Override
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            User user = new User();
            user.setId(rs.getLong("id"));
            user.setName(rs.getString("name"));
            user.setEmail(rs.getString("email"));
            return user;
        }
    });

    return users;
}

 

람다 표현식 사용

import org.springframework.jdbc.core.JdbcTemplate;

public List<User> findAllUsers(JdbcTemplate jdbcTemplate) {
    String sql = "SELECT id, name, email FROM users";

    List<User> users = jdbcTemplate.query(sql, (rs, rowNum) -> {
        User user = new User();
        user.setId(rs.getLong("id"));
        user.setName(rs.getString("name"));
        user.setEmail(rs.getString("email"));
        return user;
    });

    return users;
}

 

RowMapper를 사용함으로써, 데이터베이스의 결과를 객체로 안전하고 효율적으로 맵핑할 수 있다. 이는 코드의 재사용성과 유지 보수성을 향상시킨다.

 

 

'Java Category > Spring' 카테고리의 다른 글

[Spring DB] SimpleJdbcInsert  (0) 2024.04.01
[Spring DB] NamedParameterJdbcTemplate  (0) 2024.03.31
[Spring MVC] 파일 업로드  (0) 2024.03.25
[Spring MVC] 스프링 타입 컨버터  (1) 2024.03.24
[Spring MVC] API 예외 처리  (1) 2024.03.23

댓글