JAVA) JDBC 동작 순서 및 SQL 전송에서 Statement, PreparedStatement 차이점 ( 소스코드 포함 )
본문 바로가기
programming/Java

JAVA) JDBC 동작 순서 및 SQL 전송에서 Statement, PreparedStatement 차이점 ( 소스코드 포함 )

by 코딩하는 핑가 2020. 12. 31.
반응형

* 이 포스팅은 kyun2.tistory.com/ 님과 cocodo.tistory.com/ 님의 포스팅을 학습한 뒤 추가로 공부한 후 재정리한 글입니다.

* 2차 가공 및 재배포를 금지합니다.

* 오탈자 및 잘못된 내용은 댓글달아주세요.

* 부분별 소스코드를 잘못 작성하여 새롭게 포스팅했습니다. 아래 포스팅은 개념 참고만 하되, 소스코드는 아래의 링크에서 확인해주세요.

>> 소스코드 최종

 

* SQL 전송에 대해 알아보기 전 JDBC 동작 순서에 대해 먼저 살펴보겠습니다.

1. JDBC

- Java Database Connectivty

- 자바에서 제공하는 데이터베이스와 연결하여 데이터를 주고받을 수 있도록 하는 인터페이스

- 자바 언어로 다양한 종류의 관계형 데이터베이스에 접속하고 SQL문을 수행하여 처리하고자 할 때 데이터베이스를 조작하는 표준 SQL 인터페이스 API

- java.sql 패키지에 위치 ( JDBC 프로그래밍을 할 때 java.sql의 인터페이스로 작업함 )

- JDBC 드라이버 : java.sql의 인터페이스들을 상속하여 메소드의 몸체를 구현한 클래스 파일

- 단점 : DB에서 정보를 가져올 때 마다 DB Connection, Disconnection을 해야하고 서버 과부하, 속도 저하의 문제가 있음

- 이러한 단점을 극복하기 위해 JNDI를 사용

JDBC 간단한 동작 순서

1-1. JDBC 사용법

- Class.forName() : 드라이버를 로드한다.

- DriverManager : 로드된 드라이버를 통해서 Connection을 활성화해주는 객체

- Connection : 데이터베이스와의 연결

- Statement : SQL을 실행하는 객체

- ResultSet : SQL문 실행 후 데이터를 받는 객체

 

2. 드라이버 및 데이터베이스 접속 연결 소스코드 - private void connect()

package ex1;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;

public class TstJDBC {
	private String ip = "해당 IP";
	private int port = 해당포트;
	private String database = "해당 데이터베이스 이름";
	private String user = "DB 사용자 이름";
	private String password = "DB 사용자 비밀번호";

	private final String DRIVER_NAME = "DB DRIVER NAME";
	private final String JDBC_URL = "jdbc:해당 DB:thin:@" + ip + ":" + port + ":" + database;

	private Connection conn = null;
	private Statement stmt = null;

	// 드라이버 및 데이터베이스 접속
	private void connect() {
		try {
			Class.forName(DRIVER_NAME);
			System.out.println("드라이버 로드 성공");

		} catch (ClassNotFoundException e) {
			System.err.println("Class Not Found");
		}

		try {
			conn = DriverManager.getConnection(JDBC_URL, user, password);
			System.out.println("데이터베이스 접속 성공");

		} catch (SQLException e) {
			System.err.println("DB Connection Error");
		}
	}
    
	public static void main(String[] args) {
		TstJDBC test = new TstJDBC();

		test.connect();
	}

}

* 출처 포스팅과 소스코드가 다름을 밝힙니다.

* 학습을 위해 connect를 분리했습니다.

* 소스코드에서 계정 정보를 하드코딩하는 것은 절대로 피해야하는 위험한 방법이지만 예시이기 때문에 하드코딩을 해서 작성했습니다.

 

2-1. Class.forName() 동작 원리

- 의문1 : Class.forName() 메소드에 Driver 클래스 위치를 넘겨줄 뿐 리턴값을 받는다던지 하는 아무런 동작이 없음

- 의문2 : 후에 DriverManager.getConnection()을 하면 해당 연결객체를 넘겨주는 것의 이유

 

- 의문1의 해답 : Class.forName()은 클래스로더를 통해서 해당 데이터베이스 드라이버를 로드할 뿐 데이터베이스와의 연결에 관해서는 아무런 동작을 하지 않음

 

- 다시 확인 : kyun2.tistory.com/23?category=732999

 

* private void connect()를 통해 드라이버 및 데이터베이스 접속을 한 뒤 private void exeuteQuery()에서 SQL 쿼리문을 작성합니다.

 

3. SQL 쿼리문 전송 소스코드 - private void exeuteQuery()

package ex1;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;

public class TstJDBC {
	private String ip = "해당 IP";
	private int port = 해당포트;
	private String database = "해당 데이터베이스 이름";
	private String user = "DB 사용자 이름";
	private String password = "DB 사용자 비밀번호";

	private final String DRIVER_NAME = "DB DRIVER NAME";
	private final String JDBC_URL = "jdbc:해당 DB:thin:@" + ip + ":" + port + ":" + database;

	private Connection conn = null;
	private Statement stmt = null;

	// SQL SELECT 쿼리문 전송
	private void exeuteQuery() {
		String sql = "SELECT * FROM test";

		try (Statement stmt = conn.createStatement();
			ResultSet rs = stmt.executeQuery(sql)) {
			
			ResultSetMetaData rsmd = rs.getMetaData();
			
			// ResultSetMetaData의 getColumnCount() 메서드는 컬럼의 갯수 반환
			int columnCount = rsmd.getColumnCount();
			
			// 컬럼 목록 보여주기
			// 해당 위치의 컬럼명 반환
			for(int i = 1; i <= columnCount; i++) {
				System.out.print(rsmd.getColumnName(i) + "\t\t");			
			}
			System.out.println();	
		
			// 얻어낸 컬럼 값 보여주기
			while(rs.next()) {
				for(int i = 1; i<= columnCount; i++) {
					System.out.print(rs.getObject(i) + "\t\t");
				}
				System.out.println();
			}

		} catch (SQLException e) {
			System.err.println("Execute sql Error");
		}
	}

	// disconnect
	private void disconnect() {
		if (conn != null && stmt != null) {
			try {
				stmt.close();
				conn.close();
			} catch (SQLException e) {
				System.err.println("Disconnect Error");
			}
		}
	}

	public static void main(String[] args) {
		TstJDBC test = new TstJDBC();
        
		test.exeuteQuery();
		test.disconnect();

	}

}

* 출처 포스팅과 소스코드가 다름을 밝힙니다.

* 학습을 위해 exeuteQuery, disconnect를 나눴습니다.

* 위의 소스는 Statement를 사용했습니다.

* 소스코드에서 계정 정보를 하드코딩하는 것은 절대로 피해야하는 위험한 방법이지만 예시이기 때문에 하드코딩을 해서 작성했습니다.

* 모든 작업이 끝나면 반드시 statement와 connection을 close해줘야합니다.

 

* SQL 쿼리문 전송 시 Statement, PreparedStatement 객체

- 자바는 주로 웹 개발을 위해서 사용되는데, 웹 개발에서는 보안문제(SQL 인젝션) 때문에 PreparedStatement를 사용합니다.

- 또한 반복적으로 쿼리를 수행해야한다면 PreparedStatement가 성능이 더 좋습니다.

 

< 가장 큰 차이점 >

- 캐시 사용 유무 

- Statement : SQL문을 실행할 때 마다 SQL을 매 번 구문을 새로 작성해야하고 해석해야 해서 오버헤드가 있음

- PreparedStatement : 선처리 방식 사용 ( 준비된 statement ) 즉 SQL문을 미리 준비해 놓고 바인딩 변수(? 연산자)를 사용해서 반복되는 비슷한 SQL문을 쉽게 처리

 

* 쿼리 실행 순서

1. 쿼리 문장 분석

2. 컴파일

3. 실행

 

- Statement를 사용하면 매번 쿼리를 수행할 때마다 1 ~ 3 단계를 거침

- PreparedStatement를 사용하면 처음 한 번만 세 단계를 거친 후 캐시에 담아 재사용함

 

- 두 클래스의 주요 메소드

Statement PreparedStatement
ResultSet executeQuery(String sql) : Select문을 실행 ResultSet executeQuery(String sql) : Select문을 실행
int executeUpdate(String sql) : insert, update, delete int executeUpdate(String sql) : insert, update, delete
  void setXXX() : ?에 바인딩 시킬 변수를 데이터타입에 맞게

 

* SQL 쿼리문 전송 소스코드 - Statement

try {
	Connection conn = null;
	Statement stmt = null;
    
    	String sql = "INSERT CUSTOMER (NAME, AGE) VALUES('" + name + "', "+ age +"')";
    
    	conn = DriverManager.getConnection(JDBC_URL, user, password);
    	stmt = conn.createStatement();
    	stmt.executeQuery(sql);
    
    	stmt.close();
       	conn.close();
    
} catch (SQLException e) {
	System.err.println("Execute sql Error");
}

 

String sqlstr = "SELECT name, memo FROM TABLE WHERE num =" + num
Statement stmt = conn.credateStatement();
ResultSet rst = stmt.executeQuerey(sqlstr);

* sqlstr를 실행 시 결과값을 생성

 

* 동일 코드에 대해 Statement가 아닌 PreparedStatement를 사용해서 예시를 만들어보도록 하겠습니다.

* 구조가 크게 차이가 나지는 않지만 sql문을 생성할 때, 쿼리문을 실행할 때 차이가 있습니다.

* PreparedStatement를 사용하면 sql문을 생성할 때 변수 값 대신 ?를 사용하게 됩니다.

 

* SQL 쿼리문 전송 소스코드 - PreparedStatement

try {
	Connection conn = null;
	PreparedStatement pstmt = null;
    
    	String sql = "INSERT CUSTOMER (NAME, AGE) VALUES(?, ?)";
    
    	conn = DriverManager.getConnection(JDBC_URL, user, password);
    	pstmt = conn.createStatement(sql);
        
        pstmt.setString(1, name);
        pstmt.setInt(2, age);
        
        pstmt.executeUpate(sql);
    
    	stmt.close();
       	conn.close();
    
} catch (SQLException e) {
	System.err.println("Execute sql Error");
}

 

String sqlstr = "SELECT name, memo FROM TABLE WHERE num = ? "
PreparedStatement stmt stmt = conn.credateStatement(sqlstr);
pstmt.setint(1, num);
ResultSet rst = stmt.executeQuerey();

* sqlstr은 생성시에 실행

* 인덱스에 해당하는 숫자는 물음표의 순번이라 정해진 순서대로 변수를 채워주면 됨

 

* 데이터 타입에 따른 set~

- setString(int, String) : 인덱스를 String값으로 지정

- setCharacter(int, Reader, int) : 인덱스의 파라미터 값을 LONG VARCHAR로 지정

- setInt(int, int) : 인덱스를 int값으로 지정

- setLong(int, long) : 인덱스를 long으로 지정

- setDouble(int, double) : 인덱스를 double로 지정

- setFloat(int, float) : 인덱스를 float로 지정

- setTime(int, Time) : 인덱스를 java.sql.Time으로 지정

- sysdate를 사용하는 경우 : sql문 생성 시 sysdate를 넣으면 됨

 

String sql = "INSERT CUSTOMER (NAME, AGE, ENROLLDATE) VALUES(?,?,sysdate)";

 

* Statement를 사용해야 하는 경우

- Dynamic SQL을 사용할 경우

: Dynamic SQL을 사용한다면 매번 조건절이 틀려지게 됨으로 statement가 낫다.

 

* PreparedStatement를 사용해야 하는 경우

- 사용자 입력값으로 쿼리를 생성하는 경우

- 쿼리 반복수행 작업일 경우

 

* executeQuery와 executeUpdate의 차이

- executeQuery : result set을 만드는 sql문에서 사용하며, 주로 SELECT문을 수행할 때 사용됨

- executeUpdate : INSERT나 UPDATE와 같은 DDL이나 DML을 실행할 때 사용

 

* 마지막으로 자원누수가 되지 않게 stmt, conn close는 필수 작업

반응형

댓글