프로젝트 개요: NewShop
https://github.com/GEISHAz/NewShop-jdbc-jpa-compare
GitHub - GEISHAz/NewShop-jdbc-jpa-compare: 👀 JDBC와 JPA성능을 비교하기위한 간단 쇼핑몰 프로젝트 : A projec
👀 JDBC와 JPA성능을 비교하기위한 간단 쇼핑몰 프로젝트 : A project to compare CRUD performance between JDBC and JPA using MySQL. - GEISHAz/NewShop-jdbc-jpa-compare
github.com
이번 프로젝트는 옷 쇼핑몰 시스템인 NewShop을 단계적으로 구현하며,
JDBC와 JPA의 특징과 차이를 학습하는 데 초점을 맞췄습니다.
특히 Ver 1과 Ver 2 단계에서는 JDBC를 활용해 기본적인 데이터베이스 연결과 CRUD 작업을 구현하며
여러 깨달음을 얻을 수 있었습니다.
Ver 1: 데이터베이스 연결과 데이터 삽입
구현 내용
- JDBC를 활용해 MySQL 데이터베이스와 연결.
- PreparedStatement를 통해 간단한 데이터 삽입 작업을 성공적으로 수행.
코드 주요 부분
private static void jdbcInsert() {
String sql = "INSERT INTO clothes (title, brand, prices) VALUES (?, ?, ?)";
try (Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/NewShop", "root", "ssafy");
PreparedStatement statement = con.prepareStatement(sql)) {
statement.setString(1, "무스탕");
statement.setString(2, "에르메스");
statement.setInt(3, 1000000);
statement.executeUpdate();
System.out.println("데이터 삽입 성공!");
} catch (SQLException e) {
e.printStackTrace();
}
}
깨달은 점
- JDBC의 기본 구조 이해
- JDBC는 데이터베이스와 애플리케이션 간의 직접적인 연결을 제공하며, 작업 흐름은 다음과 같음을 학습:
- Connection 객체 생성.
- SQL 실행을 위한 Statement 또는 PreparedStatement 생성.
- 자원 해제(명시적으로 close() 호출).
- JDBC는 데이터베이스와 애플리케이션 간의 직접적인 연결을 제공하며, 작업 흐름은 다음과 같음을 학습:
- 자원 해제의 중요성
- Connection, Statement, ResultSet 같은 자원은 GC가 관리하지 않으므로, 누수를 방지하려면 반드시 닫아야 함.
- 이를 자동으로 처리할 수 있는 **try-with-resources**의 필요성을 배움.
- PreparedStatement 활용
- SQL 매개변수 바인딩을 통해 SQL 인젝션 방지와 유연한 쿼리 작성이 가능하다는 점을 실감.
Ver 2: CRUD 기능 완성
구현 내용
- JDBC를 사용해 CREATE, READ, UPDATE, DELETE 작업을 성공적으로 구현.
- 각 작업을 메서드로 분리하여 구조화하고, 코드의 재사용성을 높임.
코드 주요 부분
UPDATE 예시
private static void jdbcUpdate() {
String sql = "UPDATE clothes SET prices = ? WHERE id = ?";
try (Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/NewShop", "root", "ssafy");
PreparedStatement statement = con.prepareStatement(sql)) {
statement.setInt(1, 970000);
statement.setInt(2, 1);
statement.executeUpdate();
System.out.println("데이터 수정 성공!");
} catch (SQLException e) {
e.printStackTrace();
}
}
실행 결과


깨달은 점
- CRUD 작업의 복잡성
- JDBC로 CRUD를 구현할 때, Connection 생성과 자원 해제 작업이 반복적으로 발생.
- 코드가 길어지고 중복이 많아지는 단점을 직접 경험.
- 트랜잭션 관리의 필요성
- 여러 SQL 작업이 연결된 경우, 하나의 작업이라도 실패하면 **전체 작업을 취소(rollback)**해야 데이터 정합성을 유지할 수 있음.
- 이를 위해 connection.setAutoCommit(false)와 commit, rollback의 중요성을 느꼈음.
- SQL 중심의 개발 방식
- JDBC는 SQL 작성이 중심이 되므로, 데이터베이스 스키마 설계와 SQL 작성 능력이 매우 중요하다는 점을 학습.
프로젝트 진행 중 알게 된 점
1. Garbage Collector와 외부 자원 관리
Garbage Collector(GC)는 JVM 메모리 안의 객체를 관리하고, 더 이상 참조되지 않는 객체를 자동으로 회수하는 역할을 합니다. 하지만 JDBC의 Connection, PreparedStatement, ResultSet과 같은 자원은 GC의 관리 범위 밖에 있었습니다.
이유: 외부 자원의 특징
- Connection: 데이터베이스와의 네트워크 연결을 유지하며, 외부 리소스를 점유.
- PreparedStatement: SQL 실행과 관련된 메타데이터를 보유.
- ResultSet: 데이터베이스에서 가져온 결과를 저장하며, DB 커서(Cursor)와 연결.
이러한 외부 자원은 JVM 외부에서 작동하기 때문에, GC는 이 자원을 회수하거나 닫을 수 없었습니다.
만약 이를 명시적으로 닫지 않으면, 리소스 누수(Resource Leak)가 발생하여 시스템의 성능이 저하되고, 심각한 경우 Too many open cursors 같은 오류가 발생한다고 합니다.
- JDBC의 Connection, PreparedStatement, ResultSet은 GC가 관리하지 않는 외부 자원으로, 명시적으로 해제해야 함.
- 이를 자동화하기 위해 try-with-resources를 사용하면 자원 누수를 방지할 수 있음.
2. try-with-resources의 필요성
자원을 안전하게 해제하는 방법 중 하나가 try-with-resources 입니다.
이 문법은 AutoCloseable 인터페이스를 구현한 객체를 자동으로 닫아주기 때문에, 개발자가 명시적으로 close()를 호출하지 않아도 되어 편리했습니다.
String sql = "SELECT * FROM clothes";
try (Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/NewShop", "root", "ssafy");
PreparedStatement stmt = con.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
System.out.printf("ID: %d, Title: %s, Brand: %s, Prices: %d\n",
rs.getInt("id"),
rs.getString("title"),
rs.getString("brand"),
rs.getInt("prices"));
}
} catch (SQLException e) {
e.printStackTrace();
}
// Connection, PreparedStatement, ResultSet이 자동으로 닫힘
try-with-resources의 장점
- 자원 누수 방지: 예외가 발생해도 finally 블록 없이 자원이 안전하게 해제.
- 코드 간결화: 자원 해제 코드가 필요 없으므로 코드가 간결해짐.
- 안정성 향상: 개발자가 close() 호출을 깜빡하거나 실수해도 안전.
3. Connection Pool의 필요성
Connection Pool은 JDBC Connection을 미리 생성해 두고, 요청이 발생하면 이를 재사용하도록 관리하는 기술입니다.
각 요청마다 새로운 Connection을 생성하고 닫는 방식은 다음과 같은 문제를 야기합니다.
문제점: Connection 생성의 높은 비용
- Connection 생성 과정:
- 애플리케이션 → 데이터베이스 연결 요청 → 인증 → 연결 객체 생성.
- 이 과정은 네트워크 작업과 데이터베이스의 리소스 점유를 수반하며, 매우 비용이 큼.
- 빈번한 생성과 닫기:
- 요청마다 Connection을 생성하고 닫으면, 데이터베이스와 애플리케이션 모두 부하가 커짐.
- 성능 저하:
- 대규모 트래픽이 발생하면 Connection 생성 요청이 몰려 시스템 성능이 저하.
Connection Pool의 원리
- 애플리케이션 시작 시 일정 개수의 Connection을 미리 생성.
- 요청이 발생하면 Connection Pool에서 사용 가능한 Connection을 할당.
- 작업이 끝난 후, Connection을 닫는 대신 Pool로 반환해 재사용 가능 상태로 유지.
각 요청마다 Connection을 생성하고 닫는 작업을 직접 구현해본 결과 매우 비효율적이었습니다.
JDBC Ver 1, Ver 2를 통해 얻은 교훈
- 직접적인 데이터베이스 연결
- JDBC는 SQL을 직접 작성해야 하고, 자원 해제와 같은 세부 작업까지 개발자가 처리해야 하므로 직접적이고 세밀한 제어가 가능함.
- 하지만 코드의 복잡성이 높아지는 단점이 있음.
- 데이터 정합성 유지
- 트랜잭션(commit, rollback)을 통해 여러 작업을 하나의 묶음으로 처리해야만 데이터 정합성을 유지할 수 있음을 경험.
- 트랜잭션 관리가 없다면 데이터베이스 상태가 불안정해질 가능성이 큼.
- 자동화 도구의 필요성
- 반복적으로 작성되는 CRUD 코드와 자원 관리 코드를 줄이기 위해, **ORM(JPA)**이나 Connection Pool의 필요성을 느꼈음.
느낀 점
데이터베이스와 애플리케이션이 어떻게 상호작용하는지에 대해 깊이 이해할 수 있었습니다. JDBC를 사용하면서 단순히 SQL 문을 실행하는 것 이상의 많은 부분을 배웠습니다. 특히 Connection, PreparedStatement, ResultSet과 같은 객체들이 데이터베이스와의 연결을 직접적으로 다루며, 이러한 자원들이 Garbage Collector의 관리 대상이 아님을 알게 되었습니다. 이로 인해 자원을 명시적으로 해제하지 않으면 리소스 누수가 발생할 수 있다는 점을 깨달았고, 이를 해결하기 위해 try-with-resources 같은 안전한 자원 관리 기법이 필수적임을 느꼈습니다.
CRUD 기능을 구현하면서 JDBC의 반복적인 코드 작성 문제와 유지보수성의 한계를 체감했습니다. 각 작업마다 비슷한 코드가 반복되었고, 작은 실수로도 전체 흐름에 영향을 줄 수 있다는 점이 어렵게 다가왔습니다. 이를 통해 더 나은 생산성을 위해 JPA와 같은 도구를 도입해야 하는 이유를 알게 되었습니다. 또한, 여러 SQL 작업을 묶어 데이터 정합성을 유지하는 트랜잭션 관리의 중요성도 크게 느낄 수 있었습니다. commit과 rollback을 적절히 활용하지 않으면 데이터베이스 상태가 불안정해질 수 있다는 점을 실제 작업을 통해 체험했습니다.
Connection Pool의 필요성도 이번 프로젝트를 통해 크게 체감했습니다. 요청마다 Connection을 생성하고 닫는 작업이 얼마나 비효율적인지 깨닫게 되었고, Connection Pool을 사용해 미리 연결을 생성하고 재사용하는 방식이 성능 향상에 얼마나 중요한 역할을 하는지도 알게 되었습니다. HikariCP 같은 도구는 이러한 작업을 간편하게 만들어 주는 강력한 도구라는 점도 새롭게 배웠습니다.
전체적으로 JDBC의 기본 동작을 이해하고 데이터베이스와의 연동 작업에 대한 실질적인 감각을 키울 수 있었습니다.
'DB' 카테고리의 다른 글
JDBC로 배우는 단계별 프로젝트 개발: Ver 3과 Ver 4를 통해 얻은 깨달음 🚀 (0) | 2025.01.06 |
---|---|
데이터 베이스 정규형 (0) | 2024.07.24 |
SQLD ) 데이터 모델링 (1) | 2023.11.09 |