TIL/[TIL]

[TIL]JDBC 연결, Statement/PreparedStatement, SQL Injection, CRUD와 DAO 구조

namerong 2026. 5. 19. 13:54

1. 학습 주제

JDBC의 기본 연결부터 Statement, PreparedStatement, SQL Injection 방어, 그리고 CRUD와 DAO + DTO + XML 쿼리 분리 구조까지 학습했다.
단순히 SQL을 실행하는 수준을 넘어서, 자바 애플리케이션이 데이터베이스와 어떻게 역할을 나눠서 연결되는지 흐름을 익히는 데 집중했다.

  • JDBC로 DB 연결하기
  • properties 파일로 접속 정보 분리하기
  • JDBCTemplate로 공통 코드 정리하기
  • Statement와 PreparedStatement 차이 이해하기
  • ResultSet으로 조회 결과 읽기
  • SQL Injection이 왜 위험한지 확인하기
  • CRUD의 기본 흐름 익히기
  • DTO, DAO, XML 쿼리 분리 구조 이해하기

2. JDBC란

JDBC는 Java Database Connectivity의 약자로, 자바 프로그램이 데이터베이스와 통신할 수 있게 해주는 API이다.

JDBC의 기본 흐름은 보통 다음과 같다.

  1. Connection 생성
  2. SQL 실행 객체 생성
  3. SQL 실행
  4. 결과 처리
  5. 자원 반납

즉 자바 코드 안에서 DB에 접속하고, SQL을 전달하고, 결과를 객체로 받아오는 전체 통로라고 이해하면 된다.


3. DB 연결과 Connection

가장 먼저 필요한 것은 Connection 객체이다.

Class.forName("com.mysql.cj.jdbc.Driver");
con = DriverManager.getConnection(
    "jdbc:mysql://localhost/employeedb",
    "ohgiraffers",
    "ohgiraffers"
);
  • JDBC 드라이버를 로딩한다
  • DB 주소, 계정, 비밀번호를 전달한다
  • 연결이 성공하면 Connection 객체를 얻는다

Connection은 자바와 데이터베이스 사이의 연결 통로 역할을 한다.


4. properties 파일로 설정 분리

접속 정보를 코드에 직접 쓰는 대신 connection-info.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와 SELECT 조회

가장 기본적인 SQL 실행 객체는 Statement이다.

stmt = con.createStatement();
rset = stmt.executeQuery("SELECT MENU_NAME, MENU_PRICE FROM TBL_MENU");

조회 결과는 ResultSet으로 받는다.

while(rset.next()) {
    System.out.println(
        rset.getString("MENU_NAME") + ", " + rset.getInt("MENU_PRICE")
    );
}

여기서 핵심은:

  • executeQuery()는 SELECT용
  • ResultSet은 조회 결과 집합
  • next()는 다음 행으로 이동
  • getString(), getInt() 등으로 현재 행 값을 읽는다

7. ResultSet

ResultSet은 데이터베이스에서 조회한 결과를 자바에서 한 줄씩 읽기 위한 객체이다.

흐름은 이렇게 이해하면 쉽다.

  1. SQL 실행
  2. 결과가 ResultSet에 담김
  3. next()로 한 행씩 이동
  4. 각 컬럼 값을 꺼냄

즉 ResultSet은 “조회된 테이블 결과를 자바 코드에서 순차적으로 읽는 도구”이다.


8. Statement의 한계

Statement로 사용자 입력을 그대로 이어붙여 쿼리를 만들 수도 있다.

String query =
    "SELECT MENU_NAME, MENU_PRICE FROM TBL_MENU WHERE MENU_CODE = '" + menuCode + "'";

하지만 이런 방식은 문제가 있다.

  • 문자열 연결이 번거롭다
  • 타입 처리도 불편하다
  • 잘못된 입력이 SQL 구조를 깨뜨릴 수 있다
  • SQL Injection에 취약하다

즉 Statement는 단순 실행에는 가능하지만, 사용자 입력이 들어가는 동적 쿼리에는 위험하다.


9. PreparedStatement

PreparedStatement는 SQL 문장을 미리 준비하고, 값은 나중에 안전하게 채워 넣는 방식이다.

String query = "SELECT MENU_NAME FROM TBL_MENU WHERE MENU_CODE = ?";
pstmt = con.prepareStatement(query);
pstmt.setString(1, menuCode);
rset = pstmt.executeQuery();

핵심은 ? 위치 홀더이다.

  • SQL 구조는 고정
  • 입력값은 데이터로만 들어감
  • 문법과 값을 분리할 수 있다

장점:

  • SQL Injection 방어
  • 가독성 향상
  • 값 바인딩이 명확함
  • 동적 입력 처리에 적합

실무에서는 Statement보다 PreparedStatement를 훨씬 더 자주 사용한다.


10. SQL Injection

예제에서는 SQL Injection 공격도 확인했다.

private static String empName = "' OR 1=1 AND EMP_ID = '200";

이 값을 Statement 방식으로 이어붙이면 SQL 조건식 자체가 변형될 수 있다.

즉 SQL Injection은:

  • 사용자 입력이 단순 값이 아니라
  • SQL 문법 일부처럼 해석되면서
  • 원래 의도하지 않은 쿼리가 실행되는 문제

이다.

PreparedStatement는 이 입력값을 SQL 구조가 아니라 데이터 값으로만 다루기 때문에 이런 공격을 막는 데 매우 중요하다.


11. DTO 사용

조회 결과를 바로 출력만 하는 것이 아니라, 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>에 담았다.

DTO를 쓰는 이유:

  • 한 행의 데이터를 객체로 표현할 수 있다
  • 출력, 전달, 재사용이 쉬워진다
  • DB 결과와 자바 코드 구조가 자연스럽게 연결된다

즉 DTO는 “DB 한 줄을 자바 객체로 옮겨 담는 그릇”이다.


12. 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을 외부 파일로 분리하는 방식이 더 중요해진다.


13. CRUD 중 INSERT, UPDATE, DELETE

CRUD 중 INSERT, UPDATE, DELETE 흐름도 함께 학습했다.

INSERT

result = pstmt.executeUpdate();
  • 새로운 메뉴를 추가한다
  • executeUpdate()는 변경된 행 수를 반환한다

UPDATE

  • 메뉴 번호를 기준으로 이름과 가격을 수정한다
  • 변경 대상 데이터를 MenuDTO에 담아 처리했다

DELETE

  • 삭제 전에 먼저 메뉴를 조회하고
  • 사용자에게 정말 삭제할지 확인한 뒤
  • DELETE를 실행했다

이 흐름을 통해 단순히 SQL만 실행하는 것이 아니라,
사용자 입력 -> DTO -> SQL -> 결과 처리의 구조를 만드는 방식을 익혔다.


14. DAO 구조

예제의 중요한 부분은 MenuDAO였다.

public class MenuDAO {
    public List<CategoryDTO> selectAllCategory(Connection con) { ... }
    public int insertNewMenu(Connection con, MenuDTO newMenu) { ... }
}

DAO는 Database Access Object의 약자이다.

역할:

  • 데이터베이스 접근 전담
  • SQL 실행 담당
  • CRUD 메서드 보관

즉 DAO는:

  • 화면 입출력 코드와 DB 코드를 분리하고
  • 데이터 접근 로직을 한 곳에 모으는 역할

을 한다.

이 구조가 중요한 이유는 애플리케이션 규모가 커질수록
DB 관련 코드를 여기저기 흩뿌리면 관리가 어려워지기 때문이다.


15. 카테고리 조회 후 메뉴 등록 흐름

  1. MenuDAO를 통해 등록 가능한 카테고리 목록 조회
  2. 사용자에게 메뉴 이름, 가격, 카테고리, 판매 여부 입력 받기
  3. 입력값을 MenuDTO에 담기
  4. insertNewMenu() 호출
  5. 결과에 따라 성공/실패 메시지 출력

이 구조를 통해 JDBC는 단순히 “DB 연결 연습”이 아니라
“작은 콘솔 애플리케이션 안에서 데이터 흐름을 설계하는 기술”이라는 점을 느낄 수 있었다.


16. 자원 반납의 중요성

JDBC에서는 사용한 자원을 반드시 닫아야 한다.

  • ResultSet
  • Statement / PreparedStatement
  • Connection

예제에서도 항상 마지막에 닫아줬다.

close(rset);
close(pstmt);
close(con);

닫는 순서도 중요하다.

  • 나중에 연 것부터 먼저 닫는다
  • 보통 ResultSet -> Statement -> Connection

즉 JDBC에서는 자원 반납이 안정성과 성능에 매우 중요하다.


17. 학습 흐름 정리

17.1 연결과 설정

  • JDBC 드라이버 로딩
  • Connection 생성
  • properties 파일로 접속 정보 분리
  • JDBCTemplate로 공통 처리 정리

17.2 조회와 실행

  • Statement로 기본 조회
  • PreparedStatement로 안전한 조회
  • ResultSet으로 결과 읽기
  • SQL Injection 위험 확인

17.3 구조화

  • DTO로 행 데이터를 객체화
  • XML에 SQL 분리
  • DAO로 DB 접근 로직 모으기
  • CRUD 흐름을 콘솔 프로그램 형태로 확장

18. 핵심 정리

  1. JDBC는 자바 프로그램이 데이터베이스와 통신하기 위한 표준 API이다.
  2. Connection은 자바와 DB를 연결하는 통로이다.
  3. 접속 정보는 보통 properties 파일로 분리해서 관리한다.
  4. JDBCTemplate는 반복되는 연결/닫기 코드를 줄이기 위한 공통 클래스이다.
  5. Statement는 기본 SQL 실행 객체이지만, 사용자 입력 처리에는 취약할 수 있다.
  6. PreparedStatement는 SQL 구조와 입력값을 분리해 더 안전하게 실행한다.
  7. PreparedStatement는 SQL Injection 방어에 매우 중요하다.
  8. ResultSet은 조회 결과를 한 행씩 읽는 객체이다.
  9. DTO는 DB 한 행을 자바 객체로 담기 위한 구조이다.
  10. SQL을 XML 파일로 분리하면 유지보수성이 좋아진다.
  11. DAO는 데이터베이스 접근 로직을 한 곳에 모으는 역할을 한다.
  12. JDBC에서는 ResultSet, Statement, Connection을 반드시 닫아야 한다.