[JSP] DB 연결 뒤 쿼리 실행 방법(Statement VS PreparedStatement)

2025. 5. 31. 10:02·[Programming Language]/[JSP]

** 이 포스팅은 DB 연결이 완료된 후의 상황을 기준으로 합니다. 만약 DB 연결이 되지 않은 경우 아래 포스팅을 참고해주세요. **

** DB는 Oracle을 기준으로 합니다. **

 

[JSP] DB 연결 방법(DriverManager, Connection Pool)

** 본 포스팅은 Oracle 11g을 기준으로 작성되었습니다 **1. JDBC 파일 필요자바에서 데이터베이스에 접속하기 위해선 JDBC를 사용해야 한다. JDBC(Java Database Connectivity)란 자바에서 데이터베이스와 연결

semin1127.tistory.com


DB 연결은 Connection Pool 방식으로 하는 것이 좋지만, 이 포스팅에서는 단순 DB 연결을 1번만 맺어 쿼리를 실행하는 것을 가정하므로 DriverManager 방식을 사용하도록 하겠다.

1. DB 연결 (by. DriverManager)

<%@ page language="java" contentType="text/html; charset=EUC-KR"
    pageEncoding="EUC-KR"%>
<%@ page import="java.sql.Connection" %>
<%@ page import="java.sql.PreparedStatement" %>
<%@ page import="java.sql.Statement" %>
<%@ page import="java.sql.ResultSet" %>
<%@ page import="java.sql.DriverManager" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">
		<title>Query Test</title>
	</head>
	<body>
		<%
			Connection conn = null;
			Statement stmt = null;
			PreparedStatement pstmt = null;
			ResultSet rs = null;
			
			String user = "ORCLSTUDY";
			String pwd = "1234";
			
			try {
				Class.forName("oracle.jdbc.driver.OracleDriver");
				
				conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", user, pwd);
				
                /*
                 * 이 부분에서 쿼리 실행
                 */ 
			} catch(Exception e) {
				e.printStackTrace();
			} finally {
				if (rs != null) rs.close();
				if (pstmt != null) pstmt.close();
				if (stmt != null) stmt.close();
				if (conn != null) conn.close();
			}
		%>
	</body>
</html>

DriverManager을 사용하여 Oracle DB Connection을 얻는 과정이다. 자원을 닫아주지 않으면 누수가 발생하고, 최대 커넥션 수에 도달하면 더 이상 커넥션을 얻을 수 없어 무한 로딩 현상이 발생하므로 반드시 자원을 닫아줘야 한다.

 

2. 쿼리

2-1. Statement 사용

첫 번째 방법은 Statement 클래스를 사용하는 것이다. 코드는 다음과 같다. JSP 부분만 담았다.

<%
        Connection conn = null;
        Statement stmt = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        String user = "ORCLSTUDY";
        String pwd = "1234";

        try {
            Class.forName("oracle.jdbc.driver.OracleDriver");
            String sql = "SELECT * FROM member";

            conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", user, pwd);
            stmt = conn.createStatement();
            rs = stmt.executeQuery(sql);

            while (rs.next()) {
                System.out.println("id: " + rs.getString("ID"));
                System.out.println("name: " + rs.getString("NAME"));
                System.out.println();
            }
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            if (rs != null) rs.close();
            if (pstmt != null) pstmt.close();
            if (stmt != null) stmt.close();
            if (conn != null) conn.close();
        }
    %>
  • stmt = conn.createStatement() : Statement 객체 생성
  • rs = stmt.executeQuery(sql) : 쿼리 실행 결과 저장
  • while (rs.next()) {...} : 데이터 하나씩 순회하며 추출

ResultSet 클래스는 쿼리 실행 결과를 저장할 때 사용되는 것으로 행을 순회하는 커서가 존재한다. 그러므로 rs.next( ) 메서드를 사용하여 행을 순차적으로 탐색할 수 있고, 만약 모든 데이터를 다 탐색했다면 이 메서드는 false를 반환한다.

2-2. PreparedStatement 사용

두 번째 방법은 PreparedStatement 클래스를 사용하는 것이다. Statement와는 다르게 객체를 생성할 때 sql 문을 파라미터로 넣어줘야 한다. 코드는 크게 다르지 않다.

<%
        Connection conn = null;
        Statement stmt = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        String user = "ORCLSTUDY";
        String pwd = "1234";

        try {
            Class.forName("oracle.jdbc.driver.OracleDriver");
            String sql = "SELECT * FROM member";

            conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", user, pwd);
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();

            while (rs.next()) {
                System.out.println("id: " + rs.getString("ID"));
                System.out.println("name: " + rs.getString("NAME"));
                System.out.println();
            }
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            if (rs != null) rs.close();
            if (pstmt != null) pstmt.close();
            if (stmt != null) stmt.close();
            if (conn != null) conn.close();
        }
%>
  • pstmt = conn.prepareStatement(sql) : 미리 선언된 sql을 컴파일하여 객체를 생성
  • rs = pstmt.executeQuery( ) : 쿼리 실행 결과 저장
  • while (rs.next()) {...} : 데이터 하나씩 순회하며 추출

Statement를 사용했을 때와의 차이는 Connection으로부터 객체를 생성할 때 sql문을 파라미터로 설정하여 컴파일 시점에 미리 객체를 생성한다는 것이다. 컴파일 시점에 객체를 생성하는 것이므로 Statement보다 성능이 좋다.

 

3. Statement VS PreparedStatement

3-1. 객체 생성 시점에 따른 성능 차이

Statement는 런타임 시점에 쿼리를 사용할 때마다 계속해서 컴파일이 이뤄지지만, PreparedStatement는 실행 전에 최초 1회 컴파일 된 후 이후에는 컴파일이 이뤄지지 않는다. 따라서 PreparedStatement의 성능이 더 좋다.

3-2. 쿼리 파라미터 설정 방법 차이 (feat. SQL Injection 공격)

3-2-1. Statement

Statement는 쿼리 파라미터를 설정할 때 쿼리문 내부에 설정해야 한다. 만약 사용자가 입력한 값을 쿼리에 추가해야 한다면 사용자 입력 값이 그대로 들어간다.

// try 부분만 추출

try {
    Class.forName("oracle.jdbc.driver.OracleDriver");
    String id = request.getParameter("id");
    String passwd = request.getParameter("passwd");
    String sql = "SELECT * FROM member WHERE id=" + id + " AND passwd=" + passwd;

    conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", user, pwd);
    stmt = conn.createStatement();
    rs = stmt.executeQuery(sql);

    while (rs.next()) {
        System.out.println("id: " + rs.getString("ID"));
        System.out.println("name: " + rs.getString("NAME"));
        System.out.println();
    }
}

request.getParameter("...") 메서드는 이전 페이지에서 전달된 값을 반환한다. 이는 GET 방식이나 POST 방식의 요청 모두 동일하다. 실제 로그인 요청은 POST 방식으로 처리되지만 여기선 단순성을 위해 직접 URL에 파라미터 값을 입력해주었다.

id=3, passwd=1234

 

그러나 이처럼 sql에 사용자가 입력한 값을 그대로 설정하는 것은 문제가 될 수 있다. 요즘에는 기본적으로 방어가 되어 있는 SQL Injection 공격에 뚫릴 수 있는 것이다. SQL에서 주석은 --로 설정하는데 이를 이용해서 다음과 같이 입력하면 쿼리의 조건이 true가 되어 결과를 반환한다.

id=3--, passwd=

분명 비밀번호를 입력하지 않았는데도 쿼리가 실행되었다. 이는 쿼리가 다음과 같이 해석되었기 때문에 가능한 것이다.

SELECT * FROM member WHERE id=3-- AND passwd=

이처럼 -- 뒷부분이 전부 주석이 되어버리기 때문에 id만 일치한다면 쿼리가 실행되버리는 것이다. 물론 Java 코드에서 추가적인 조치를 할 수는 있겠지만 근본적으로 Statement의 위험성이 있다는 것을 말하고자 하는 것이다.

3-2-2. PreparedStatement

PreparedStatement는 Statement와 달리 메서드로 쿼리 파라미터를 설정한다. 값의 타입에 따라 setString( ), setInt( ), setDate( ) 등의 메서드를 사용하여 값을 설정하면 된다.

try {
    Class.forName("oracle.jdbc.driver.OracleDriver");
    String id = request.getParameter("id");
    String passwd = request.getParameter("passwd");
    
    // :id, :passwd 대신 물음표(?)를 써도 되고 :foawfwo, :lajlfwlie 이렇게 아무거나 입력해도 되긴 된다
    String sql = "SELECT * FROM member WHERE id=:id AND passwd=:passwd";

    conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", user, pwd);
    pstmt = conn.prepareStatement(sql);
    pstmt.setString(1, id);
    pstmt.setString(2, passwd);
    rs = pstmt.executeQuery();

    while (rs.next()) {
        System.out.println("id: " + rs.getString("ID"));
        System.out.println("name: " + rs.getString("NAME"));
        System.out.println();
    }
}

두 파라미터 타입이 모두 String이므로 setString( ) 메서드를 사용하였다. 파라미터는 앞에서부터 1번의 번호를 갖는다. 그러므로 파라미터를 설정할 땐 다음과 같이 설정해야 한다. 

pstmt.set...({파라미터 번호}, {파라미터 값});

 

이번에도 앞에서 했던 것처럼 이상한 값을 전달해보겠다.

id=3--, passwd=

PreparedStatement는 쿼리가 미리 컴파일되므로 쿼리 파라미터는 단순 값의 바인딩만 일어난다. 메서드에 파라미터를 설정하는 것처럼 생각하면 되는 것이다. 그러므로 id에 3-- 값이 들어오더라도 주석으로 설정되는 것이 아니라 통문자열인 '3--' 으로 설정되기에 SQL Injection 공격이 기본적으로 먹히지 않는 것이다.

저작자표시 비영리 (새창열림)

'[Programming Language] > [JSP]' 카테고리의 다른 글

[JSP] Excel 파일 업로드 후 내용 파싱하여 DB에 저장하기(Apache Tika, Apache POI)  (1) 2025.06.05
[JSP] DB 연결 방법(DriverManager, Connection Pool)  (0) 2025.05.23
[JSP] 데이터 주고 받는 방법(form, Fetch API)  (0) 2025.05.19
[JSP] 태그 종류(scriptlet, directive, declaration, expression, comment, action)  (0) 2025.05.15
[JSP] JSP란? JSP와 Servlet? 기본 예제  (0) 2025.05.11
'[Programming Language]/[JSP]' 카테고리의 다른 글
  • [JSP] Excel 파일 업로드 후 내용 파싱하여 DB에 저장하기(Apache Tika, Apache POI)
  • [JSP] DB 연결 방법(DriverManager, Connection Pool)
  • [JSP] 데이터 주고 받는 방법(form, Fetch API)
  • [JSP] 태그 종류(scriptlet, directive, declaration, expression, comment, action)
Semincolon
Semincolon
It seems small, that semicolon is a big deal.
  • Semincolon
    Semincolon
    Semincolon
  • 전체
    오늘
    어제
    • 분류 전체보기 (133)
      • [Programming Language] (78)
        • [JSP] (6)
        • [Swift] (23)
        • [SwiftUI] (16)
        • [Python] (22)
        • [C언어] (6)
        • [Kotlin] (4)
        • [C#] (1)
      • [Frame Work] (5)
        • [Flutter] (4)
        • [Spring Boot] (1)
      • [Projects] (3)
        • [Android][Kotlin] 공유 캘린더(20.. (1)
        • [Unity] 인내의 숲(2024.03) (2)
      • [DB] (15)
        • - Oracle (15)
      • [Programmers] (25)
        • - SQL (25)
      • [ETC] (2)
      • Today's Learning (5)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 인기 글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
Semincolon
[JSP] DB 연결 뒤 쿼리 실행 방법(Statement VS PreparedStatement)
상단으로

티스토리툴바