1. 학습 주제
Java 애플리케이션이 데이터베이스와 연결되는 전체 흐름을 정리했다.
단순히 SQL을 작성하는 것이 아니라, Java 코드 안에서 Connection, Statement, PreparedStatement, ResultSet를 어떻게 사용하고, 왜 JDBCTemplate, DTO, SQL 분리 구조가 필요한지도 함께 이해할 수 있었다.
- JDBC로 DB 연결하기
- properties 파일로 접속 정보 분리하기
- JDBCTemplate로 공통 코드 정리하기
- Statement와 PreparedStatement 차이 이해하기
- ResultSet으로 조회 결과 읽기
- SQL Injection이 왜 위험한지 확인하기
- PreparedStatement가 왜 더 안전한지 이해하기
- DTO와 SQL 분리 파일을 활용한 CRUD 구조 익히기
2. JDBC란
JDBC는 Java Database Connectivity의 줄임말로, 자바 프로그램이 데이터베이스와 통신할 수 있게 해주는 표준 API이다.
즉 JDBC를 사용하면 자바 코드에서:
- DB에 접속하고
- SQL을 전달하고
- 결과를 받아오고
- 연결을 종료할 수 있다
흐름을 아주 단순하게 보면 다음과 같다.
- DB 연결 생성
- SQL 실행 객체 생성
- SQL 실행
- 결과 처리
- 자원 반납
3. DB 연결
가장 먼저 필요한 것은 Connection 객체이다.
Connection con = DriverManager.getConnection(
"jdbc:mysql://localhost/employeedb",
"ohgiraffers",
"ohgiraffers"
);
Connection은 자바 프로그램과 DB 사이의 연결 통로 역할을 한다.
연결 전에 JDBC 드라이버도 로딩했다.
Class.forName("com.mysql.cj.jdbc.Driver");
즉 DB 연결의 핵심은:
- 어떤 드라이버를 쓸지 정하고
- 어떤 DB 주소로 갈지 정하고
- 어떤 계정으로 접속할지 정하는 것
이다.
4. 접속 정보 분리
예제에서는 DB 접속 정보를 코드에 직접 쓰는 대신 properties 파일로 분리했다.
Properties prop = new Properties();
prop.load(reader);
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String user = prop.getProperty("user");
String password = prop.getProperty("password");
이 방식이 중요한 이유는:
- 코드 수정 없이 설정값만 바꿀 수 있고
- 유지보수가 쉬워지고
- 민감한 정보를 코드와 분리할 수 있기 때문이다
즉 실무에서는 접속 정보를 직접 하드코딩하기보다 외부 설정 파일로 분리하는 것이 훨씬 일반적이다.
5. JDBCTemplate
JDBC는 반복 코드가 많다.
예를 들면:
- Connection 생성
- Connection 닫기
- Statement 닫기
- ResultSet 닫기
이런 코드가 계속 반복된다.
그래서 예제에서는 JDBCTemplate를 만들어 공통 처리 로직을 모아두었다.
public static Connection getConnection() { ... }
public static void close(Connection con) { ... }
public static void close(Statement stmt) { ... }
public static void close(ResultSet rset) { ... }
이 구조의 장점은:
- 중복 코드 감소
- 코드 가독성 향상
- 자원 반납 실수 줄이기
즉 JDBCTemplate는 JDBC에서 자주 반복되는 연결/닫기 로직을 재사용하기 위해 만든 공통 유틸 클래스라고 볼 수 있다.
6. Statement
Statement는 SQL 문자열을 DB로 전달해서 실행하는 객체이다.
stmt = con.createStatement();
rset = stmt.executeQuery("SELECT MENU_NAME, MENU_PRICE FROM TBL_MENU");
조회 쿼리를 실행할 때는 executeQuery()를 사용하고,
결과는 ResultSet으로 받는다.
while(rset.next()) {
System.out.println(rset.getString("MENU_NAME") + ", " + rset.getInt("MENU_PRICE"));
}
여기서 중요한 점:
- rset.next()는 다음 행으로 이동
- 현재 행이 존재하면 true
- 각 컬럼 값은 getString, getInt 같은 메서드로 꺼낸다
즉 Statement는 가장 기본적인 SQL 실행 방식이다.
7. ResultSet
ResultSet은 SELECT 결과를 담고 있는 객체이다.
조회된 여러 행을 커서 방식으로 하나씩 읽는다.
while(rset.next()) {
...
}
읽는 방식은 다음 흐름으로 이해하면 쉽다.
- executeQuery() 실행
- 결과가 ResultSet에 담김
- next()로 한 줄씩 이동
- 현재 줄에서 컬럼 값 읽기
즉 ResultSet은 “조회 결과 테이블을 자바에서 한 줄씩 읽어오는 도구”라고 볼 수 있다.
8. Statement의 한계
예제에서 사용자 입력을 문자열로 이어붙여 SQL을 만든 코드가 있었다.
String query =
"SELECT MENU_NAME, MENU_PRICE FROM TBL_MENU WHERE MENU_CODE = '" + menuCode + "'";
이 방식은 간단해 보이지만 문제가 있다.
- 문자열 연결이 번거롭다
- 숫자/문자 타입 구분이 불편하다
- 입력값에 따라 SQL 구조가 깨질 수 있다
- SQL Injection에 취약하다
즉 Statement는 동적으로 사용자 입력을 조합할 때 위험할 수 있다.
9. SQL Injection
예제에서 SQL Injection 공격 상황도 보여줬다.
private static String empName = "' OR 1=1 AND EMP_ID = '200";
이 값을 문자열로 이어붙이면 원래 의도한 조건식이 깨지고,
공격자가 원하는 SQL 구조로 바뀔 수 있다.
즉 SQL Injection은:
- 사용자 입력값이 SQL 문법 일부처럼 동작하면서
- 원래 의도하지 않은 쿼리가 실행되게 만드는 공격
이다.
이건 JDBC에서 왜 PreparedStatement를 써야 하는지 설명하는 가장 중요한 이유 중 하나다.
10. PreparedStatement
PreparedStatement는 미리 SQL 틀을 준비하고,
사용자 입력은 나중에 값으로만 넣는 방식이다.
String query = "SELECT * FROM EMPLOYEE WHERE EMP_ID = ? AND EMP_NAME = ?";
pstmt = con.prepareStatement(query);
pstmt.setString(1, empId);
pstmt.setString(2, empName);
여기서 ?는 위치 홀더이다.
즉 SQL 구조와 사용자 입력 데이터를 분리한다.
이 방식의 장점은:
- SQL Injection 방어
- 가독성 향상
- 타입에 맞게 값 설정 가능
- 동적 입력 처리에 적합
즉 실무에서는 Statement보다 PreparedStatement가 훨씬 더 자주 쓰인다.
11. PreparedStatement가 안전한 이유
가장 중요한 차이는 이것이다.
Statement는 입력값을 SQL 문장 일부처럼 이어붙인다.
반면 PreparedStatement는 입력값을 SQL 문법이 아니라 “데이터 값”으로 처리한다.
예를 들어:
pstmt.setString(2, empName);
이렇게 넣으면 입력값 안에 ', OR, 1=1 같은 내용이 있어도
SQL 구조를 바꾸지 못하고 그냥 문자열 데이터로만 인식된다.
즉 PreparedStatement는 SQL Injection 방어에 매우 중요하다.
12. 조회 결과를 DTO로 담기
예제에서는 조회한 한 행의 데이터를 MenuDTO 객체에 담았다.
row = new MenuDTO();
row.setMenuCode(rset.getInt("MENU_CODE"));
row.setMenuName(rset.getString("MENU_NAME"));
row.setMenuPrice(rset.getInt("MENU_PRICE"));
row.setCategoryCode(rset.getInt("CATEGORY_CODE"));
row.setOrderableStatus(rset.getString("ORDERABLE_STATUS"));
그리고 여러 행이면 List<MenuDTO>에 담았다.
menuList = new ArrayList<>();
menuList.add(row);
이 방식의 장점은:
- DB 결과를 객체 형태로 다룰 수 있다
- 출력, 전달, 가공이 쉬워진다
- SQL 결과와 프로그램 로직을 자연스럽게 연결할 수 있다
즉 DTO는 DB 한 행을 자바 객체로 옮겨 담는 그릇 역할을 한다.
13. SQL을 XML로 분리
예제에서는 SQL 문장을 자바 코드에 직접 쓰지 않고 XML 파일에 분리해 두었다.
<entry key="insertMenu">
INSERT INTO
TBL_MENU (
MENU_NAME
, MENU_PRICE
, CATEGORY_CODE
, ORDERABLE_STATUS
)
VALUES(?, ?, ?, ?)
</entry>
그리고 자바에서는 이렇게 불러왔다.
prop.loadFromXML(queryStream);
String query = prop.getProperty("insertMenu");
이 방식의 장점은:
- 자바 코드와 SQL 분리
- 쿼리 관리 편리
- SQL 수정 시 자바 코드 수정 최소화
- 유지보수성 향상
즉 규모가 커질수록 SQL을 별도 파일로 관리하는 구조가 더 중요해진다.
14. CRUD 중 INSERT
예제에서는 INSERT도 PreparedStatement로 처리했다.
pstmt = con.prepareStatement(query);
pstmt.setString(1, "봉골레청국장");
pstmt.setInt(2, 11000);
pstmt.setInt(3, 1);
pstmt.setString(4, "Y");
result = pstmt.executeUpdate();
SELECT와 다른 점은:
- 조회 결과인 ResultSet을 받지 않는다
- 대신 영향받은 행 수를 int로 반환한다
즉:
- executeQuery() : 조회용
- executeUpdate() : INSERT, UPDATE, DELETE용
이라고 정리하면 된다.
15. JDBC에서 자원 반납이 중요한 이유
JDBC는 사용 후 반드시 닫아줘야 하는 객체가 많다.
- ResultSet
- Statement / PreparedStatement
- Connection
예제에서도 항상 마지막에 닫아주었다.
close(rset);
close(stmt);
close(con);
그리고 보통 “나중에 열린 것부터 먼저 닫는 순서”를 사용한다.
이걸 잊으면:
- 연결이 계속 남아 있을 수 있고
- 자원 누수가 발생할 수 있고
- 프로그램 성능과 안정성에 문제가 생길 수 있다
그래서 JDBC에서는 자원 반납이 매우 중요하다.
16. 전체 흐름 정리
이번 JDBC 학습 흐름은 크게 이렇게 정리할 수 있었다.
16.1 연결
- 드라이버 로딩
- Connection 생성
- properties 파일로 접속 정보 분리
16.2 실행
- Statement로 기본 SQL 실행
- PreparedStatement로 안전한 SQL 실행
- ResultSet으로 조회 결과 읽기
16.3 구조화
- JDBCTemplate로 공통 로직 재사용
- DTO로 결과를 객체화
- XML 파일로 SQL 분리
16.4 CRUD 시작
- executeUpdate()로 데이터 변경 처리
- 영향받은 행 수 확인
17. 핵심 정리
- JDBC는 자바에서 데이터베이스와 연결하고 SQL을 실행하기 위한 표준 API이다.
- Connection은 자바와 DB를 이어주는 연결 통로이다.
- 접속 정보는 보통 properties 파일로 분리해 관리한다.
- Statement는 기본 SQL 실행 객체이지만 동적 입력 처리에는 취약할 수 있다.
- PreparedStatement는 SQL 틀과 입력값을 분리해 더 안전하게 실행한다.
- PreparedStatement는 SQL Injection 방어에 매우 중요하다.
- ResultSet은 SELECT 결과를 한 행씩 읽어오는 객체이다.
- 조회 결과는 DTO에 담아 객체처럼 다루는 것이 편리하다.
- SQL을 XML이나 별도 파일로 분리하면 유지보수성이 좋아진다.
- executeQuery()는 조회, executeUpdate()는 데이터 변경 작업에 사용한다.
- JDBC에서는 ResultSet, Statement, Connection을 반드시 닫아야 한다.
- JDBCTemplate는 반복되는 연결/닫기 코드를 줄이기 위한 공통 유틸 구조이다.
'TIL > [TIL]' 카테고리의 다른 글
| [TIL]Servlet 기초부터 요청/응답, 파라미터 처리, JSON API까지 (0) | 2026.05.20 |
|---|---|
| [TIL]JDBC 연결, Statement/PreparedStatement, SQL Injection, CRUD와 DAO 구조 (0) | 2026.05.19 |
| [TIL]SQL 심화: GROUP BY, 서브쿼리, 집합 연산, DML, 트랜잭션, DDL, 제약조건, 데이터 타입, 인덱스 (1) | 2026.05.15 |
| [TIL]SQL 조회 기초: SELECT, ORDER BY, WHERE, DISTINCT, LIMIT, JOIN (0) | 2026.05.14 |
| [TIL]enum, lambda, stream 실무 관점 정리 (0) | 2026.05.13 |