String JDBC(1)
이번에는 스프링에서 JDBC를 어떻게 만드는 지 살펴볼 것이다.
- 매번 커넥션을 생성하고 close 한다. => 많은 리소스가 소비
- Connection Pool
- DataSource를 이용해서 Connection을 가져올 수 있다.
- 풀에서 커넥션을 썼다가 반납하는 개념이다.
HikariCP
: 매우 가볍고 매우 빠른 JDBC 풀이다.
- 상당히 빠르고 안정적이다.
// pom.xml
<dependency>
<groupId> org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
- database 속성 이름을 지을 때는 _ 를 많이 쓰고
- 자바나 엔티티 클래스 필드에서는 카멜 케이스를 많이 쓴다.
처음 클래스를 만들 때, final을 고민해서 붙이는 것이 중요하다.
//Customer.java
public class Customer {
private final UUID customerId;
private String name;
private final String email;
private LocalDateTime lastLoginAt;
private final LocalDateTime createdAt;
public Customer(UUID customerId, String email, LocalDateTime createdAt) {
this.customerId = customerId;
this.email = email;
this.createdAt = createdAt;
}
public Customer(UUID customerId, String name, String email, LocalDateTime lastLoginAt, LocalDateTime createdAt) {
this.customerId = customerId;
this.name = name;
this.email = email;
this.lastLoginAt= lastLoginAt;
this.createdAt = createdAt;
}
}
필수적인 것과, null 이 들어갈 수 있는 것을 따로 생각하여 생성자를 만든다.
- setter를 무분별하게 쓰면 안된다. 실제 엔티티 상태를 바꾸는 것이다.
> 이름을 바꾸는 메소드
- setName (x)
- changeName (o)
// Customer.java
public void changeName(String name) {
if (name.isBlank()) {
throw new RuntimeException("error : Name should not be blank");
}
this.name = name;
}
- validate을 입력시에 생성해준다.
package : customer > Customer, CustomerRepository
// CustomerRepository.java
public interface CustomerRepository {
Customer insert(Customer customer);
Customer update(Customer customer);
// insert, update를 묶어서 save로 처리하기도 한다.
List<Customer> findAll();
Optional<Customer> findById(UUID customerId);
Optional<Customer> findByName(String name);
Optional<Customer> findByEmail(String email);
void deleteAll();
}
JdbcTemplate
- 커넥션을 맺는 부분
- 예외 처리
위의 두 부분이 계속 반복된다.
데이터를 가지고 와서 원하는 부분과 매핑하는 것도 비슷하다.
jdbc 템플릿은 기본적으로 데이터 소스만 있으면 된다.
// CustomerJdbcRepository.java
public class CustomerJdbcRepository implements CustomerRepository{
private static final Logger logger = LoggerFactory.getLogger(CustomerJdbcRepository.class);
private final DataSource dataSource;
private final JdbcTemplate jdbcTemplate;
private static final RowMapper<Customer> customerRowMapper = (resultSet, i) -> {
var customerName = resultSet.getString("name");
var email = resultSet.getString("email");
var customerId = toUUID(resultSet.getBytes("customer_id"));
var lastLoginAt = resultSet.getTimestamp("last_login_at").toLocalDateTime() == null ?
resultSet.getTimestamp("last_login_at").toLocalDateTime() : null;
var createdAt = resultSet.getTimestamp("create_at").toLocalDateTime();
return new Customer(customerId, customerName, email, lastLoginAt, createdAt);
};
public CustomerJdbcRepository(DataSource dataSource, JdbcTemplate jdbcTemplate) {
this.dataSource = dataSource;
this.jdbcTemplate = jdbcTemplate;
}
@Override
public List<Customer> findAll() {
return jdbcTemplate.query("select * from customers", customerRowMapper);
}
private static final UUID toUUID(byte[] bytes) {
var byteBuffer = ByteBuffer.wrap(bytes);
return new UUID(byteBuffer.getLong(), byteBuffer.getLong());
}
//...
}
코드가 훨씬 깔끔해졌다.
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
아래의 jdbc 템플릿을 정리해야 한다.
jdbcTemplate의 query가 어떻게 구현되어 있는지 살펴보자. callback
query를 사용하면 list가 반환된다.
// CustomerJdbcRepository.java
@Override
public Optional<Customer> findById(UUID customerId) {
return Optional.ofNullable(jdbcTemplate.queryForObject("select * from customers WHERE customer_id = UUID_to_BIN(?)", customerRowMapper, customerId.toString()));
}
count 함수를 만들어보자.
// CustomerJdbcRepository.java
@Override
public int count() {
return jdbcTemplate.queryForObject("select count(*) from customers", Integer.class);
}
JDBC Template - DML
- DML 부분도 바꿔보자.
- jdbcTemplate.update() 를 사용해야 한다.
- insert, update, delete all
// CustomerJdbcRepository.java
@Override
public Customer insert(Customer customer) {
var update = jdbcTemplate.update("INSERT INTO customers(customer_id, name, email, created_at) VALUES (UUID_TO_BIN(?), ?, ?, ?)",
customer.getCustomerId().toString().getBytes(),
customer.getName(),
customer.getEmail(),
Timestamp.valueOf(customer.getCreateAt()));
if (update != 1) {
throw new RuntimeException("Nothing was inserted");
}
return customer;
}
@Override
public Customer update(Customer customer) {
var update = jdbcTemplate.update("UPDATE customers SET name = ?, email = ?, last_login_at = ? WHERE customer_id = UUID_TO_BIN(?)",
customer.getName(),
customer.getEmail(),
customer.getLastLoginAt() != null ? Timestamp.valueOf(customer.getLastLoginAt()) : null;
customer.getCustomerId().toString().getBytes(),
if (update != 1) {
throw new RuntimeException("Nothing was updated");
}
return customer;
}
@Override
public void deleteAll(){
jdbcTemplate.update("DELETE FROM customers");
}
코드가 훨씬 짧아졌다.
디버그를 걸어놓고 내부적으로 어떻게 동작하는지 한번 해보자.
'2023 활동 - 4학년 > [1월 ~ 4월] sw 아카데미 백엔드 과정' 카테고리의 다른 글
[2023.03.17 / CNU SW 아카데미] 52일차 회고록 (0) | 2023.03.17 |
---|---|
[2023.03.13 / CNU SW Academy] 48일차 회고록 (0) | 2023.03.14 |
[2023.03.08 / CNU SW 아카데미] SpringBoot Part2 D-22 (0) | 2023.03.08 |
[2023.03.08 / CNU SW 아카데미] SpringBoot Part2 D-21 (0) | 2023.03.08 |
[2023.03.03 / CNU SW 아카데미] 42일차 회고록 (0) | 2023.03.05 |