Java 어플리케이션과 RDBMS를 연결해주는 모듈
2개의 레이어로 구성
JDBC의 플로우
resultSet.getXXX("필드명")
의 형태로 데이터를 읽을 수 있다close()
해줘야 자원 낭비가 없다LocalDateTime
객체로 변환해주면 다양한 메소드를 활용할 수 있다 public class JdbcCustomerRepository {
private static final Logger logger = LoggerFactory.getLogger(JdbcCustomerRepository.class);
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// connection 획득
connection = DriverManager.getConnection("jdbc:mysql://localhost/order_mgmt", "root", "1234");
// statement 객체 생성
statement = connection.createStatement();
// RDB와 통신
resultSet = statement.executeQuery("SELECT * FROM customers");
while (resultSet.next()) {
var customerId = UUID.nameUUIDFromBytes(resultSet.getBytes("customer_id"));
var name = resultSet.getString("name");
logger.info("customer id -> {} name -> {}", customerId, name);
}
} catch (SQLException throwables) {
logger.error("Got error while closing connection", throwables);
} finally {
// 사용한 객체들 반납
try {
if (connection != null) connection.close();
if (statement != null) statement.close();
if (resultSet != null) resultSet.close();
} catch (SQLException e) {
logger.error("Got error while closing connection", e);
}
}
}
}
public class JdbcCustomerRepository {
private static final Logger logger = LoggerFactory.getLogger(JdbcCustomerRepository.class);
public static void main(String[] args) {
try (
// try문이 끝난후 자동으로 close할 객체들
var connection = DriverManager.getConnection("jdbc:mysql://localhost/order_mgmt", "root", "1234");
var statement = connection.createStatement();
var resultSet = statement.executeQuery("SELECT * FROM customers");
)
{
while (resultSet.next()) {
var customerId = UUID.nameUUIDFromBytes(resultSet.getBytes("customer_id"));
var name = resultSet.getString("name");
logger.info("customer id -> {} name -> {}", customerId, name);
}
} catch (SQLException throwables) {
logger.error("Got error while closing connection", throwables);
}
}
}
// name은 사용자 입력 문자열
findName(String name) {
SQL = "SELECT * FROM customers WHERE name = '" + name + "'";
...
statement.executeQuery(SQL)
...
}
하지만 만약 "' OR 'a' = 'a"
같은 입력이 들어온다면, 조건이 항상 true가 되어 모든 결과를 전송할 것이다
이런 SQL 인젝션 문제를 방지하기 위해 prepareStatement()
를 사용한다
public List<String> findNames(String name) {
var SELECT_SQL = "select * from customers where name = ?";
List<String> names = new ArrayList<>();
try (
var connection = DriverManager.getConnection("jdbc:mysql://localhost/order_mgmt", "root", "1234");
var statement = connection.prepareStatement(SELECT_SQL);
)
{
statement.setString(1, name);
try (var resultSet = statement.executeQuery()) {
while (resultSet.next()) {
var customerId = UUID.nameUUIDFromBytes(resultSet.getBytes("customer_id"));
var customerName = resultSet.getString("name");
var createdAt = resultSet.getTimestamp("created_at").toLocalDateTime();
logger.info("customer id -> {} name -> {} createdAt -> {}", customerId, customerName, createdAt);
names.add(customerName);
}
}
} catch (SQLException throwables) {
logger.error("Got error while closing connection", throwables);
}
return names;
}
-- 단순 문자열 조합
SELECT * FROM customers WHERE name = '' OR 'a' = 'a';
-- prepareStatements() 사용
SELECT * FROM customers WHERE name = ''' OR ''a''=''a'
public int insertCustomer(UUID customerId, String name, String email) {
String INSERT_SQL = "insert into customers(customer_id, name, email) VALUES (UUID_TO_BIN(?), ?, ?)";
try (
var connection = DriverManager.getConnection("jdbc:mysql://localhost/order_mgmt", "root", "1234");
var statement = connection.prepareStatement(INSERT_SQL);
)
{
statement.setBytes(1, customerId.toString().getBytes());
statement.setString(2, name);
statement.setString(3, email);
return statement.executeUpdate();
} catch (SQLException throwables) {
logger.error("Got error while closing connection", throwables);
}
return 0;
}
public int updateCustomerName(UUID customerId, String newName) {
String UPDATE_BY_ID_SQL = "update customers set name = ? WHERE customer_id = UUID_TO_BIN(?)";
try (
var connection = DriverManager.getConnection("jdbc:mysql://localhost/order_mgmt", "root", "1234");
var statement = connection.prepareStatement(UPDATE_BY_ID_SQL);
)
{
statement.setString(1, newName);
statement.setBytes(2, customerId.toString().getBytes());
return statement.executeUpdate();
} catch (SQLException throwables) {
logger.error("Got error while closing connection", throwables);
}
return 0;
}
public int deleteAllCustomer() {
String DELETE_ALL_SQL = "delete from customers";
try (
var connection = DriverManager.getConnection("jdbc:mysql://localhost/order_mgmt", "root", "1234");
var statement = connection.prepareStatement(DELETE_ALL_SQL);
)
{
return statement.executeUpdate();
} catch (SQLException throwables) {
logger.error("Got error while closing connection", throwables);
}
return 0;
}
매번 Connection을 생성하는 것은 오버헤드가 크다
DataSource를 통해 커넥션 풀을 활용할 수 있다
커넥션을 미리 만들어 캐싱하고 필요시마다 사용/반환할 수 있다
DataSource가 커넥션 풀을 관리한다
SimpleDriverDataSource : 스프링부트에서 제공하는 기본적인 데이터 소스 구현체
HikariCP :
DataSource datasource = DataSourceBuilder.create()
.url("jdbc:mysql://localhost/order_mgmt")
.username("root")
.password("1234")
.type(HikariDataSource.class)
.build();
DriverManager
로 매번 커넥션을 생성했지만, 이제dataSource.getConnection
으로 더 효율적으로 커넥션을 관리할 수 있다 dataSource.setMaximumPoolSize(int maxPoolSize)
: idle과 사용중인 커낵션을 포함한 최대 풀 크기
dataSource.setMinimumIdle(int minIdle)
: 풀 내에 유지하는 최소 idle 커낵션 수
DriverManager
대신 DataSource
를 사용해 커낵션을 가져오는것 이외에는 동일하다public Optional<List<Customer>> findAll() {
List<Customer> customerList = new ArrayList<>();
try (
var connection = dataSource.getConnection();
var statement = connection.prepareStatement(SELECT_ALL_SQL);
var resultSet = statement.executeQuery();
)
{
while(resultSet.next()) {
mapToCustomer(customerList, resultSet);
}
} catch (SQLException throwable) {
logger.error("Got error while closing connection", throwable);
throw new RuntimeException(throwable);
}
return Optional.of(customerList);
}