드라이버 매니저를 이용해 getConnection()메소드를 사용해 커넥션 객체를 받아왔습니다. 이렇게 매번 커넥션을 생성하고 클로즈하면 그 과정에서 많은 리소스가 소비됩니다. 이런 문제를 해결하기 위해서 커넥션 풀이라는 개념이 등장합니다. JDBC에서는 드라이버 매니저 외에 데이터소스를 통해서 커넥션을 가져올 수가 있습니다. 이 데이터소스가 커넥션을 관리하는 주체가 됩니다.
출처:https://brownbears.tistory.com/289
커넥션 풀은 데이터베이스과 연관된 커넥션을 미리 만들어두고 풀에 저장합니다. 필요할 때 커넥션을 가져와 쓰고 다시 반환하는 방법입니다.
HikariCP는 2012년도경에 Brett Wooldridge가 개발한 매우 가볍고 매우 빠른 JDBC 커넥션 풀입니다.
https://github.com/brettwooldridge/HikariCP
package org.prgms.kdt.customer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;
import javax.sql.DataSource;
import java.nio.ByteBuffer;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Repository
public class CustomerJdbcRepository implements CustomerRepository {
private static final Logger logger = LoggerFactory.getLogger(CustomerJdbcRepository.class);
private final DataSource dataSource;
public CustomerJdbcRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Customer insert(Customer customer) {
try (
var connection = dataSource.getConnection();
var statement = connection.prepareStatement("INSERT INTO customers(customer_id, name, email, created_at) VALUES (UUID_TO_BIN(?), ?, ?, ?)");
) {
statement.setBytes(1, customer.getCustomerId().toString().getBytes());
statement.setString(2, customer.getName());
statement.setString(3, customer.getEmail());
statement.setTimestamp(4, Timestamp.valueOf(customer.getCreatedAt()));
var executeUpdate = statement.executeUpdate();
if (executeUpdate != 1) {
throw new RuntimeException("Nothing was inserted");
}
return customer;
} catch (SQLException throwables) {
logger.error("Got error while closing connection", throwables);
throw new RuntimeException(throwables);
}
}
@Override
public Customer update(Customer customer) {
try (
var connection = dataSource.getConnection();
var statement = connection.prepareStatement("UPDATE customers SET name = ?, email = ?, last_login_at = ? WHERE customer_id = UUID_TO_BIN(?)");
) {
statement.setString(1, customer.getName());
statement.setString(2, customer.getEmail());
statement.setTimestamp(3, customer.getLastLoginAt() != null ? Timestamp.valueOf(customer.getLastLoginAt()) : null);
statement.setBytes(4, customer.getCustomerId().toString().getBytes());
var executeUpdate = statement.executeUpdate();
if (executeUpdate != 1) {
throw new RuntimeException("Nothing was updated");
}
return customer;
} catch (SQLException throwables) {
logger.error("Got error while closing connection", throwables);
throw new RuntimeException(throwables);
}
}
@Override
public List<Customer> findAll() {
List<Customer> allCustomers = new ArrayList<>();
try (
var connection = dataSource.getConnection();
var statement = connection.prepareStatement("select * from customers");
var resultSet = statement.executeQuery()
) {
while (resultSet.next()) {
mapToCustomer(allCustomers, resultSet);
}
} catch (SQLException throwables) {
logger.error("Got error while closing connection", throwables);
throw new RuntimeException(throwables);
}
return allCustomers;
}
@Override
public Optional<Customer> findById(UUID customerId) {
List<Customer> allCustomers = new ArrayList<>();
try (
var connection = dataSource.getConnection();
var statement = connection.prepareStatement("select * from customers WHERE customer_id = UUID_TO_BIN(?)");
) {
// SELECT_SQL의 ?가 여러개 일 수 있으므로 이를 알려주기위해 파라미터인덱스를 지정해주어야합니다.
statement.setBytes(1, customerId.toString().getBytes());
// SQL Injection을 발생하는 입력이 들어와도 문자열로 인식하므로 SQL문으로 실행되지않습니다.
logger.info("statement -> {}", statement);
try (var resultSet = statement.executeQuery()) {
while (resultSet.next()) {
mapToCustomer(allCustomers, resultSet);
}
}
} catch (SQLException throwables) {
logger.error("Got error while closing connection", throwables);
throw new RuntimeException(throwables);
}
return allCustomers.stream().findFirst();
}
@Override
public Optional<Customer> findByName(String name) {
List<Customer> allCustomers = new ArrayList<>();
try (
var connection = dataSource.getConnection();
var statement = connection.prepareStatement("select * from customers WHERE name = ?");
) {
statement.setString(1, name);
try (var resultSet = statement.executeQuery()) {
while (resultSet.next()) {
mapToCustomer(allCustomers, resultSet);
}
}
} catch (SQLException throwables) {
logger.error("Got error while closing connection", throwables);
throw new RuntimeException(throwables);
}
return allCustomers.stream().findFirst();
}
@Override
public Optional<Customer> findByEmail(String email) {
List<Customer> allCustomers = new ArrayList<>();
try (
var connection = dataSource.getConnection();
var statement = connection.prepareStatement("select * from customers WHERE email = ?");
) {
statement.setString(1, email);
try (var resultSet = statement.executeQuery()) {
while (resultSet.next()) {
mapToCustomer(allCustomers, resultSet);
}
}
} catch (SQLException throwables) {
logger.error("Got error while closing connection", throwables);
throw new RuntimeException(throwables);
}
return allCustomers.stream().findFirst();
}
@Override
public void deleteAll() {
try (
var connection = dataSource.getConnection();
var statement = connection.prepareStatement("DELETE FROM customers");
) {
statement.executeUpdate();
} catch (SQLException throwables) {
logger.error("Got error while closing connection", throwables);
throw new RuntimeException(throwables);
}
}
private static void mapToCustomer(List<Customer> allCustomers, ResultSet resultSet) throws SQLException {
var customerName = resultSet.getString("name");
var customerId = toUUID(resultSet.getBytes("customer_id"));
var email = resultSet.getString("email");
var lastLoginAt = resultSet.getTimestamp("last_login_at") != null ?
resultSet.getTimestamp("last_login_at").toLocalDateTime() : null;
var createdAt = resultSet.getTimestamp("created_at").toLocalDateTime();
allCustomers.add(new Customer(customerId, customerName, email, lastLoginAt, createdAt));
}
static UUID toUUID(byte[] bytes) {
var byteBuffer = ByteBuffer.wrap(bytes);
return new UUID(byteBuffer.getLong(), byteBuffer.getLong());
}
}