본문 바로가기

개발/Spring Boot

[Spring Boot] JDBC Template이란?

반응형

JDBC Template 소개

JDBC Template은 Java에서 데이터베이스와의 상호 작용을 쉽게 만들어주는 스프링 프레임워크의 일부입니다.
이를 통해 개발자는 SQL 쿼리를 실행하고 데이터베이스와 상호 작용하는 코드를 간소화할 수 있습니다.

설정 방법은 2가지가 있다.

1. yml에서 설정하는 방법

spring.datasource.url=jdbc:mysql://localhost:3306/yourdatabase
spring.datasource.username=yourusername
spring.datasource.password=yourpassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

2. Spring Bean으로 등록하여 사용하는 방법

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

@Configuration
public class DataSourceConfig {

    @Bean
    public DriverManagerDataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/yourdatabase");
        dataSource.setUsername("yourusername");
        dataSource.setPassword("yourpassword");
        return dataSource;
    }
}

jdbc Template 사용법 (CRUD 작업 예제)

1. DB에 받은 정보를 매핑 시킬 객체를 생성.

@Getter
@Setter
public class User {
    private Long id;
    private String username;
    private String email;
}

2. Data Access Object 생성 (DB 접근 객체)

@Repository
public class UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void addUser(User user) {
        String sql = "INSERT INTO users (username, email) VALUES (?, ?)";
        jdbcTemplate.update(sql, user.getUsername(), user.getEmail());
    }

    public User getUserById(Long userId) {
        String sql = "SELECT * FROM users WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), userId);
    }

    public List<User> getAllUsers() {
        String sql = "SELECT * FROM users";
        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
    }

    public void updateUser(User user) {
        String sql = "UPDATE users SET username = ?, email = ? WHERE id = ?";
        jdbcTemplate.update(sql, user.getUsername(), user.getEmail(), user.getId());
    }

    public void deleteUser(Long userId) {
        String sql = "DELETE FROM users WHERE id = ?";
        jdbcTemplate.update(sql, userId);
    }
}

3. 비즈니스 로직 동작을 관리하는 Service 객체 생성

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public void addUser(User user) {
        userDao.addUser(user);
    }

    public User getUserById(Long userId) {
        return userDao.getUserById(userId);
    }

    public List<User> getAllUsers() {
        return userDao.getAllUsers();
    }

    public void updateUser(User user) {
        userDao.updateUser(user);
    }

    public void deleteUser(Long userId) {
        userDao.deleteUser(userId);
    }
}

4.Service 동작을 실행시키는 Controller 생성

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public void addUser(@RequestBody User user) {
        userService.addUser(user);
    }

    @GetMapping("/{userId}")
    public User getUserById(@PathVariable Long userId) {
        return userService.getUserById(userId);
    }

    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }

    @PutMapping
    public void updateUser(@RequestBody User user) {
        userService.updateUser(user);
    }

    @DeleteMapping("/{userId}")
    public void deleteUser(@PathVariable Long userId) {
        userService.deleteUser(userId);
    }
}

NamedParameterJdbcTemplate

JDBC에서 제공하는 기능 중 하나로, JDBC Template의 확장된 버전입니다.
이를 사용하면 JDBC 작업에서 파라미터를 이름으로 지정하여 SQL 쿼리를 더 간결하고 가독성 있게 작성할 수 있습니다.

기본적으로 JdbcTemplate은 ?를 사용하여 파라미터를 바인딩하는데, 이는 순서에 의존하므로 쿼리가 길어지거나 복잡해질 경우 유지보수가 어려울 수 있습니다.
NamedParameterJdbcTemplate은 이러한 문제를 해결하기 위해 파라미터에 이름을 지정하여 사용합니다.

사용 예제

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class NamedParameterDao {

    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    public void addUser(User user) {
        String sql = "INSERT INTO users (username, email) VALUES (:username, :email)";

        MapSqlParameterSource parameters = new MapSqlParameterSource()
            .addValue("username", user.getUsername())
            .addValue("email", user.getEmail());

        namedParameterJdbcTemplate.update(sql, parameters);
    }

    public User getUserById(Long userId) {
        String sql = "SELECT * FROM users WHERE id = :id";

        MapSqlParameterSource parameters = new MapSqlParameterSource()
            .addValue("id", userId);

        return namedParameterJdbcTemplate.queryForObject(sql, parameters, new BeanPropertyRowMapper<>(User.class));
    }

    // 다른 NamedParameterJdbcTemplate 사용 메서드들...
}

Exception 관리 방법

DataAccessException:
Spring JDBC에서 발생하는 모든 데이터 액세스 예외의 상위 예외입니다.
구체적인 예외 타입에 대한 처리를 위해 catch 블록에서 이 예외를 잡을 수 있습니다.

DuplicateKeyException:
데이터베이스에 유일성 제약 조건을 위반하는 경우 발생합니다.
즉, 중복된 키(primary key)를 삽입하려고 할 때 발생합니다.

import org.springframework.dao.DuplicateKeyException;

try {
    // 중복된 키 삽입 시도
} catch (DuplicateKeyException e) {
    // 중복된 키 예외 처리
}

EmptyResultDataAccessException:
단일 결과를 예상했지만, 쿼리 결과가 비어있는 경우 발생합니다.

import org.springframework.dao.EmptyResultDataAccessException;

try {
    // 단일 결과를 기대하는 쿼리 실행
} catch (EmptyResultDataAccessException e) {
    // 결과가 없을 때 예외 처리
}

IncorrectResultSizeDataAccessException:
예상 크기와 다른 결과 크기가 반환된 경우 발생합니다.

import org.springframework.dao.IncorrectResultSizeDataAccessException;

try {
    // 특정 크기의 결과를 기대하는 쿼리 실행
} catch (IncorrectResultSizeDataAccessException e) {
    // 예상 크기와 다른 크기의 결과에 대한 예외 처리
}

CannotGetJdbcConnectionException:
데이터베이스 연결을 가져올 수 없는 경우 발생합니다.

import org.springframework.jdbc.CannotGetJdbcConnectionException;

try {
    // 데이터베이스 연결을 가져오는 작업
} catch (CannotGetJdbcConnectionException e) {
    // 연결을 가져오지 못했을 때 예외 처리
}

Transaction 관리

  1. 선언적 트랜잭션 관리 (Declarative Transaction Management):
    @Transactional 어노테이션을 제공하여 메서드에 트랜잭션을 적용할 수 있습니다.
  2. 프로그래밍적 트랜잭션 관리 (Programmatic Transaction Management):
    프로그래밍적 트랜잭션 관리는 코드 수준에서 명시적으로 트랜잭션을 관리하는 방법입니다. PlatformTransactionManager를 사용하여 트랜잭션을 시작하고 종료할 수 있습니다.

주의사항

메소드에서 트랜잭션을 사용하는 경우:

  • 트랜잭션은 주로 비즈니스 서비스 레이어에서 사용됩니다. 따라서 트랜잭션은 주로 서비스 계층의 메서드 내에서 관리됩니다.

커밋 또는 롤백:

  • 트랜잭션은 성공적으로 완료되면 커밋되고, 예외가 발생하면 롤백됩니다.

트랜잭션 경계 (Propagation):

  • 트랜잭션 경계 설정은 여러 메서드 간에 트랜잭션을 전파하는 방법을 제어합니다. (Propagation.REQUIRED, Propagation.REQUIRES_NEW 등)

예외 처리:

  • 트랜잭션에서 예외가 발생하면 롤백이 자동으로 수행됩니다. 롤백을 원하지 않는 경우 @Transactional(rollbackFor = Exception.class)과 같이 롤백을 제어할 수 있습니다.

속성:

propagation:
트랜잭션 전파 방식을 설정합니다. (기본값은 Propagation.REQUIRED)

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void performUserOperations(User user) {
    // ...
}

isolation:
트랜잭션 격리 수준을 설정합니다. (기본값은 Isolation.DEFAULT)

@Transactional(isolation = Isolation.READ_COMMITTED)
public void performUserOperations(User user) {
    // ...
}

readOnly:
읽기 전용 트랜잭션으로 설정합니다. (기본값은 false)

@Transactional(readOnly = true)
public void performReadOnlyOperations(User user) {
    // ...
}

timeout:
트랜잭션 타임아웃(초)을 설정합니다. (기본값은 -1로 무한대)

@Transactional(timeout = 30)
public void performTimedOperations(User user) {
    // ...
}

rollbackFor 및 noRollbackFor:
롤백을 수행할 예외와 롤백을 수행하지 않을 예외를 설정합니다.

@Transactional(rollbackFor = {CustomException.class}, noRollbackFor = {AnotherException.class})
public void performCustomOperations(User user) {
    // ...
}

propagation과 readOnly 조합:
Propagation.REQUIRES_NEW와 readOnly = true를 함께 사용하여 현재 트랜잭션을 일시 중단하고 새로운 읽기 전용 트랜잭션을 시작할 수 있습니다.

@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
public void performNestedReadOnlyOperations(User user) {
    // ...
}

효율적인 리소스 관리.

데이터베이스 연결 관리:
Connection Pooling: 데이터베이스 연결은 리소스 소모가 큰 작업 중 하나입니다. Connection Pooling을 사용하면 미리 정의된 풀에서 데이터베이스 연결을 빌려오고 반납함으로써 연결을 효율적으로 관리할 수 있습니다. Spring Boot에서는 기본적으로 HikariCP와 같은 강력한 커넥션 풀 라이브러리를 사용하고 있습니다.

트랜잭션 관리:
트랜잭션의 범위를 적절하게 관리하여 효율적인 리소스 사용을 유지할 수 있습니다. 특히 선언적 트랜잭션 관리를 활용하여 트랜잭션의 시작과 종료를 명시적으로 지정하면서 롤백이나 커밋의 영향을 효율적으로 관리할 수 있습니다.

캐시 사용:
Spring에서는 다양한 캐싱 기술을 지원합니다. @Cacheable 어노테이션을 통해 메서드의 결과를 캐시로 저장하고 재사용함으로써 리소스를 절약할 수 있습니다. EhCache, Caffeine, Redis 등 다양한 캐싱 프로바이더를 지원합니다.

리소스 해제:
Spring에서는 리소스를 효과적으로 해제하기 위해 close() 메서드 호출이 필요한 리소스를 try-with-resources 구문과 함께 사용할 수 있습니다. 특히 JDBC의 Connection, Statement, ResultSet 등을 사용할 때 리소스를 놓치지 않도록 주의해야 합니다.

try (Connection connection = dataSource.getConnection();
     PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM users");
     ResultSet resultSet = preparedStatement.executeQuery()) {
    // 리소스 사용
} catch (SQLException e) {
    // 예외 처리
}

비동기 프로그래밍:
비동기 프로그래밍을 통해 애플리케이션의 확장성을 향상시킬 수 있습니다. Spring에서는 @Async 어노테이션과 CompletableFuture를 사용하여 비동기 작업을 간편하게 구현할 수 있습니다.

@Service
public class MyService {

    @Async
    public CompletableFuture<String> performAsyncTask() {
        // 비동기 작업 수행
        return CompletableFuture.completedFuture("Task completed");
    }
}

프로파일링 및 모니터링:
애플리케이션을 운영 중인 환경에서는 프로파일링과 모니터링 도구를 사용하여 리소스 사용 상황을 지속적으로 모니터링하고 성능을 최적화할 수 있습니다. Spring Boot Actuator 등의 라이브러리를 활용하여 애플리케이션의 상태와 성능 지표를 수집할 수 있습니다.

결론

JDBC Template을 사용하는 이유는 무엇인가요?

 

  • 간결한 코드: JDBC Template을 사용하면 반복적인 JDBC 코드를 최소화하고 간결하게 작성할 수 있습니다.
  • 예외 처리: JDBC Template은 SQLException을 Spring의 DataAccessException으로 변환하여 예외 처리를 더 쉽게 할 수 있도록 합니다.
  • 리소스 관리: 연결, Statement, ResultSet 등의 리소스를 자동으로 열고 닫아줘서 리소스 누수를 방지합니다.

이와 같은 관리 차원에서의 장점 때문에 사용하는 경우가 많다.

하지만 요즘은 jpa, mybatis라는 막강한 녀석이 나왔기 때문에 jdbc template를 이용하는 것을 많이 못봤다.

jpa와 mybatis도 내부를 확인해본다면 jdbc template과 비슷한 형태일 것이다. 하지만 사용자의 편리성을 더 생각한 라이브러리라고 나는 생각한다..

반응형