[ 6주차 - 0916 ]
금일 커리큘럼
├ 09:00 ~ 12:00 JDBC 프로그래밍 (DAO/DTO 복습)
└ 13:00 ~ 18:00 JDBC 프로그래밍 (JDBC 트랜잭션, HikariCP)
1. DAO DTO 복습
연습용 table 생성
CREATE TABLE products (
id int PRIMARY KEY AUTO_INCREMENT,
name varchar(150) UNIQUE NOT NULL,
price int CHECK(price >= 0),
reg_date datetime DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO products(name,price) VALUE('pen', 3000);
INSERT INTO products VALUE(null, 'pen2', 3000, now());
패키지 구조 설계 (임시)
kr.likelion.demo
│
├─ common # 공통
│ ├─ DBUtil (class)
│ └─
│
├─ controller # 관리 로직
│ └─ 미정
│
├─ dto # DB 데이터 객체
│ └─ ProductDTO (class)
│
├─ repository # DB 데이터 접근
│ ├─ ProductDAO (interface)
│ └─ ProductDAOImpl (class)
│
├─ service # 로직 제어
│ └─ ServiceTest (class)
│
└─
DBUtil 클래스 설계
역할 : DB 커넥션 세팅
package week_06._0916.kr.likelion.demo.common;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DBUtil {
static final String url =
"jdbc:mysql://localhost:3306/liondb" +
"?useSSL=false" +
"&serverTimezone=Asia/Seoul" +
"&characterEncoding=UTF-8";
public static Connection getConnection() throws ClassNotFoundException, SQLException {
String user = "star1431";
String password = "star1431";
return DriverManager.getConnection(url, user, password);
}
public static void close(Connection conn) {
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
public static void close(PreparedStatement ps) {
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
public static void close(ResultSet rs) {
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
DTO 클래스 설계
역할 : DB 테이블 정보 담는 그릇
package week_06._0916.kr.likelion.demo.dto;
import java.time.LocalDateTime;
public class ProductDTO {
private int id;
private String name;
private int price;
private LocalDateTime reg_date;
public ProductDTO() {
}
public ProductDTO(String name, int price) {
this.name = name;
this.price = price;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public LocalDateTime getRegdate() {
return reg_date;
}
public void setRegdate(LocalDateTime reg_date) {
this.reg_date = reg_date;
}
@Override
public String toString() {
return "[" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
", reg_date=" + reg_date +
']';
}
}
DAO 인터페이스
역할 : DB 접근 쿼리문 관련 기능 정의
package week_06._0916.kr.likelion.demo.repository;
import week_06._0916.kr.likelion.demo.dto.ProductDTO;
import java.util.ArrayList;
import java.util.List;
public interface ProductDAO {
public int insertAndGetId(ProductDTO pDTO); // insert id 반환
public boolean insert(ProductDTO pDTO); // insert 기본
public boolean delete(int id); // delete where id
public boolean delete(ProductDTO pDTO); // delete 기본
public boolean update(ProductDTO pDTO); // update 기본
public ProductDTO select(int id); // select where id
public ProductDTO select(String name); // select where name
public List<ProductDTO> selectAll(); // seelect *
}
DAO 클래스 설계
역할 : DB 접근 쿼리문 관련 기능 정의
package week_06._0916.kr.likelion.demo.repository;
import week_06._0916.kr.likelion.demo.common.DBUtil;
import week_06._0916.kr.likelion.demo.dto.ProductDTO;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
public class ProductDAOImpl implements ProductDAO {
/** 삽입 및 아이디 get */
@Override
public int insertAndGetId(ProductDTO pDTO) {
String sql = "INSERT INTO products(name, price, reg_date) VALUES (?, ?, now())";
int result = 0;
try (
Connection conn = DBUtil.getConnection();
// Statement.RETURN_GENERATED_KEYS
// = INSERT 실행 후 DB에서 생성된 자동 증가 키(AUTO_INCREMENT 값)를 가져오도록 설정
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
) {
ps.setString(1, pDTO.getName());
ps.setInt(2, pDTO.getPrice());
result = ps.executeUpdate();
if(result > 0) {
// ps.getGeneratedKeys()
// = INSERT 실행 시 생성된 자동 증가 키를 ResultSet 형태로 반환
try (ResultSet rs = ps.getGeneratedKeys()) {
// getGeneratedKeys 는 컬럼명 없음
if(rs.next()) pDTO.setId(rs.getInt(1));
}
}
} catch (Exception e) {
System.out.println("삽입(id) error : " + e.getMessage());
}
return pDTO.getId();
}
/** 삽입 */
@Override
public boolean insert(ProductDTO pDTO) {
String sql = "INSERT INTO products(name, price, reg_date) VALUES (?, ?, now())";
int result = 0;
try (
Connection conn = DBUtil.getConnection();
// Statement.RETURN_GENERATED_KEYS
// = INSERT 실행 후 DB에서 생성된 자동 증가 키(AUTO_INCREMENT 값)를 가져오도록 설정
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
) {
ps.setString(1, pDTO.getName());
ps.setInt(2, pDTO.getPrice());
result = ps.executeUpdate();
if(result > 0) {
// ps.getGeneratedKeys()
// = INSERT 실행 시 생성된 자동 증가 키를 ResultSet 형태로 반환
try (ResultSet rs = ps.getGeneratedKeys()) {
// getGeneratedKeys 는 컬럼명 없음
if(rs.next()) pDTO.setId(rs.getInt(1));
}
}
} catch (Exception e) {
System.out.println("삽입 error : " + e.getMessage());
}
return result > 0;
}
/** 삭제 (id) */
@Override
public boolean delete(int id) {
String sql = "DELETE FROM products WHERE id = ?";
int result = 0;
try (
Connection conn = DBUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
) {
ps.setInt(1, id);
result = ps.executeUpdate();
} catch (Exception e) {
System.out.println("삭제 error : " + e.getMessage());
}
return result > 0;
}
/** 삭제 (dto) */
@Override
public boolean delete(ProductDTO pDTO) {
String sql = "DELETE FROM products WHERE id = ?";
int result = 0;
try (
Connection conn = DBUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
) {
ps.setInt(1, pDTO.getId());
result = ps.executeUpdate();
} catch (Exception e) {
System.out.println("삭제 error : " + e.getMessage());
}
return result > 0;
}
/** 변경 */
@Override
public boolean update(ProductDTO pDTO) {
String sql = "UPDATE products SET " +
"name = ?, price = ? " +
"WHERE id = ?";
int result = 0;
try (
Connection conn = DBUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
) {
ps.setString(1, pDTO.getName());
ps.setInt(2, pDTO.getPrice());
ps.setInt(3, pDTO.getId());
result = ps.executeUpdate();
} catch (Exception e) {
System.out.println("변경 error : " + e.getMessage());
}
return result > 0;
}
/** 단건조회(id) */
@Override
public ProductDTO select(int id) {
String sql = "SELECT * FROM products WHERE id = ?";
ProductDTO pDTO = null;
try (
Connection conn = DBUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
) {
ps.setInt(1, id);
try (ResultSet rs = ps.executeQuery()) {
if(rs.next()) pDTO = resultSet(rs);
} catch (Exception e) {
System.out.println("단건선택(id) DTO 삽입 error :" + e.getMessage());
}
} catch (Exception e) {
System.out.println("단건선택(id) error : " + e.getMessage());
}
return pDTO;
}
/** 단건조회(name) */
@Override
public ProductDTO select(String name) {
String sql = "SELECT * FROM products WHERE name = ?";
ProductDTO pDTO = null;
try (
Connection conn = DBUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
) {
ps.setString(1, name);
try (ResultSet rs = ps.executeQuery()) {
if(rs.next()) pDTO = resultSet(rs);
} catch (Exception e) {
System.out.println("단건선택(name) DTO 삽입 error :" + e.getMessage());
}
} catch (Exception e) {
System.out.println("단건선택(name) error : " + e.getMessage());
}
return pDTO;
}
/** 전체조회 */
@Override
public List<ProductDTO> selectAll() {
String sql = "SELECT * FROM products";
List<ProductDTO> lists = new ArrayList<>();
try (
Connection conn = DBUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()
) {
while (rs.next()) {
lists.add(resultSet(rs));
}
} catch (Exception e) {
System.out.println("전체선택 error : " + e.getMessage());
}
return lists;
}
/** 리절트 공통 */
private ProductDTO resultSet(ResultSet rs) throws SQLException {
ProductDTO pDTO = new ProductDTO();
pDTO.setId(rs.getInt("id"));
pDTO.setName(rs.getString("name"));
pDTO.setPrice(rs.getInt("price"));
// pDTO.setRegdate(rs.getTimestamp("reg_date").toLocalDateTime());
Timestamp regDate = rs.getTimestamp("reg_date");
if(regDate != null) pDTO.setRegdate(regDate.toLocalDateTime());
return pDTO;
}
}
2. JDBC 트랜잭션
DAO 패턴에서 트랜잭션 처리 역할은 어디?
DAO는 데이터베이스 작업(SQL 실행)만 담당하고, 트랜잭션은 비즈니스로직 담당하는 클래스에서 처리해야 한다.
- DAO는 단일 쿼리 단위로 끝남
- 트랜잭션 범위는 여러 DAO 호출하는 곳에서 처리
- DAO 내에서 트랜잭션 열고 커밋할 경우 벨리데이션체크없이 확정되어 원하는 결과를 얻지 못함
즉 DAO는 쿼리 담당 / 트랜잭션은 DAO 호출하는 로직에서
심플 예시
// AccountDAO.java
public class AccountDAO {
// 출금
public void withdraw(Connection conn, int accountId, int money) throws SQLException {
String sql = "UPDATE account SET balance = balance - ? WHERE id = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, money);
ps.setInt(2, accountId);
ps.executeUpdate();
}
}
// 입금
public void deposit(Connection conn, int accountId, int money) throws SQLException {
String sql = "UPDATE account SET balance = balance + ? WHERE id = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, money);
ps.setInt(2, accountId);
ps.executeUpdate();
}
}
}
// TransferService.java
public class TransferService {
private final AccountDAO dao = new AccountDAO();
public void transfer(int fromId, int toId, int money) {
Connection conn = null;
try {
conn = DBUtil.getConnection();
conn.setAutoCommit(false); // 트랜잭션 시작
dao.withdraw(conn, fromId, money); // 출금
dao.deposit(conn, toId, money); // 입금
conn.commit(); // 둘 다 성공 → 커밋
} catch (Exception e) {
try {
if (conn != null) conn.rollback();
} catch (SQLException e) {
System.out.println(e.getMessage());
}
} finally {
try {
if (conn != null) conn.close();
} catch (SQLException e) {
System.out.println(e.getMessage());
}
}
}
}
3. HikariCP
가볍고 빠른 JDBC 커넥션 풀(Connection Pool) 라이브러리
- 스프링부트 같은 프레임워크에서 기본 사용
- 성능과 안정성으로 실무에서 많이 쓰이는 풀링 라이브러리
커넥션 풀 개념
기존 DB 연결은 생성/해제시 비용이 크고, 매요청마다 열고 닫을때 성능 저하 발생됨.
그래서 미리 일정수의 커넥션을 만들어 풀에 저장해서 필요할때 빌려쓰고 반납하는 구조
즉 빠른 성능 + 자원 효율성 확보됨
HikariCP 특징
- 불필요한 기능을 최대한 배제하고 핵심 로직만 단순·최적화
- 벤치마크 상 다른 풀보다 TPS가 높고 지연시간이 짧음
- 코드가 간결하고, 메모리 사용량도 적음
- 최소한의 프로퍼티만으로 동작 가능
의존성 설정하기
- 메이븐인 경우 pom.xml
<dependencies>
<!-- HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version> <!-- 최신 안정 버전 사용 -->
</dependency>
<!-- JDBC 드라이버 (예: MySQL) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
- 그래들인 경우 build.gradle
dependencies {
// HikariCP
implementation 'com.zaxxer:HikariCP:5.1.0'
// JDBC 드라이버 (예: MySQL)
implementation 'mysql:mysql-connector-java:8.0.33'
}
HikariCP 기본 설정
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
public class DatabaseConfig {
private static HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
// 필수 설정
config.setJdbcUrl("jdbc:mysql://localhost:3306/examplesdb");
config.setUsername("urstory");
config.setPassword("u1234");
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
// 커넥션 풀 설정
config.setMaximumPoolSize(20); // 최대 커넥션 수
config.setMinimumIdle(10); // 최소 유지 커넥션 수
config.setConnectionTimeout(30000); // 커넥션 타임아웃 (30초)
config.setIdleTimeout(600000); // 유휴 커넥션 타임아웃 (10분)
config.setMaxLifetime(1800000); // 커넥션 최대 생존시간 (30분)
// MySQL 최적화 설정
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.addDataSourceProperty("useServerPrepStmts", "true");
config.addDataSourceProperty("useLocalSessionState", "true");
config.addDataSourceProperty("rewriteBatchedStatements", "true");
config.addDataSourceProperty("cacheResultSetMetadata", "true");
config.addDataSourceProperty("cacheServerConfiguration", "true");
config.addDataSourceProperty("elideSetAutoCommits", "true");
config.addDataSourceProperty("maintainTimeStats", "false");
dataSource = new HikariDataSource(config);
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void close() {
if (dataSource != null && !dataSource.isClosed()) {
dataSource.close();
}
}
// 커넥션 풀 모니터링
public static void printPoolStats() {
if (dataSource != null) {
System.out.println("Active connections: " + dataSource.getHikariPoolMXBean().getActiveConnections());
System.out.println("Idle connections: " + dataSource.getHikariPoolMXBean().getIdleConnections());
System.out.println("Total connections: " + dataSource.getHikariPoolMXBean().getTotalConnections());
System.out.println("Threads awaiting connection: " + dataSource.getHikariPoolMXBean().getThreadsAwaitingConnection());
}
}
}
properties 파일을 이용한 설정
외부 설정 파일
- hikari.properties 파일
dataSourceClassName=com.mysql.cj.jdbc.MysqlDataSource
dataSource.url=jdbc:mysql://localhost:3306/examplesdb
dataSource.user=urstory
dataSource.password=u1234
dataSource.cachePrepStmts=true
dataSource.prepStmtCacheSize=250
dataSource.prepStmtCacheSqlLimit=2048
maximumPoolSize=20
minimumIdle=10
connectionTimeout=30000
idleTimeout=600000
maxLifetime=1800000
poolName=MyHikariPool
- java 사용시
public class DatabaseConfig {
private static HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig("hikari.properties");
dataSource = new HikariDataSource(config);
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
설정 후 DAO에서 활용시
public class UserDAO {
public void insertUser(User user) throws SQLException {
String sql = "INSERT INTO user (email, name, password) VALUES (?, ?, ?)";
try (Connection conn = DatabaseConfig.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, user.getEmail());
pstmt.setString(2, user.getName());
pstmt.setString(3, user.getPassword());
pstmt.executeUpdate();
}
// Connection은 자동으로 풀에 반환됨
}
public List<User> getAllUsers() throws SQLException {
List<User> users = new ArrayList<>();
String sql = "SELECT * FROM user ORDER BY user_id";
try (Connection conn = DatabaseConfig.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
User user = new User();
user.setUserId(rs.getInt("user_id"));
user.setEmail(rs.getString("email"));
user.setName(rs.getString("name"));
users.add(user);
}
}
return users;
}
}
// 사용 예제
public class Main {
public static void main(String[] args) {
try {
UserDAO userDAO = new UserDAO();
// 사용자 추가
User newUser = new User("test@test.com", "테스트", "1234");
userDAO.insertUser(newUser);
// 전체 사용자 조회
List<User> users = userDAO.getAllUsers();
users.forEach(System.out::println);
// 커넥션 풀 상태 확인
DatabaseConfig.printPoolStats();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 애플리케이션 종료 시
DatabaseConfig.close();
}
}
}'멋사 - 부트캠프 19기 : Java > Java' 카테고리의 다른 글
| (09.29) Java 프로그래밍 - 스레드 통신, 제어, 데드락 해결방식, 함수형 프로그래밍 (0) | 2025.09.29 |
|---|---|
| (09.25 ②) Java 프로그래밍 - 프로세스와 스레드, 스레드 생성, 동기화, 데몬스레드, 스레드 제어 메서드 (0) | 2025.09.25 |
| (09.15) JDBC 프로그래밍 - JDBC, Gradle/Maven, executeUpdate/executeQuery, DAO/DTO (0) | 2025.09.15 |
| (09.05) java 객체지향 - OOP 개념, SOLID 원칙 : SRP OCP LSP ISP DIP (0) | 2025.09.05 |
| (09.04) java 기초 - 바이트 스트림, 표준 입출력 스트림, IO 와 컬렉션 사용, 데이터 스트림 (0) | 2025.09.04 |