728x90

JDBC란?

JDBC는 Java에서 데이터베이스 SQL파일을 읽고, 추가하고, 쿼리할 수 있는 API입니다. 사전에 java로 짜여진 Query에서 사용자의 input을 읽고 그에 따라 필요한 쿼리를 처리할 수 있습니다.

 

1. JDBC 라이브러리

그럼 JDBC는 어떻게 사용할까요? 먼저, import할 라이브러리는 아래 코드와 같습니다.

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

// connect to user
Connection conn = DriverManager.getConnection("jdbc:(driver)://(ip)/(db name)", id, password)
Statement stmt = conn.createStatement();

stmt.close();
conn.close();

1) java.sql.Connection: SQL과 연결해주는 객체 라이브러리입니다.
2) java.sql.DriverManager: JDBC 드라이버를 통해 Connection을 만듭니다. .getConnection의 parameter은 각각 url, id, pw입니다. driver에 postgresql의 경우에는 postgresql을 대입하면 됩니다. 
3) java.sql.SQLException: SQL에서 쿼리, 업데이트 등을 진행할 때 잘못된 쿼리, 잘못된 data type의 경우 SQLException Error을 날립니다.
4) java.sql.Statement: SQL 구문을 넣을 수 있는 객체 라이브러리입니다.
5) java.sql.PreparedStatement: Statement와 비슷한 목적의 라이브러리이나, 차이점은 밑에서 소개하겠습니다.
5) java.sql.ResultSet: select~로 시작하는 DML을 실행할 경우 반환하는 table 객체입니다.

Connection 객체와 Statement 객체를 생성하는 코드는 위와 같고, 모든 코드를 실행한 뒤 데이터베이스와 java의 연결을 끊기 위해서는 .close() 메소드를 실행해야 합니다.

 

2. JDBC로 쿼리 짜기

Statement 라이브러리에는 .excuteUpdate(), .excuteQuery()가 있습니다. 두 메소드 모두 parameter는 String의 SQL 쿼리문입니다. 아래 코드 예시에서 table명은 mytable이고, attribute는 String, Float type 이렇게 2개 있습니다.

try {
	stmt.executeUpdate("insert into mytable values ('string_val', 'float_val')");
} catch (SQLException e) {
	System.out.println("Error: " + e);
}

ResultSet rset = stmt.executeQuery("select string_att, float_att from mytable");

.executeUpdate()는 insert, update, delete문을 실행할 때 사용하고, return하는 객체가 없습니다.
.executeQuery()는 select문을 실행할 때 사용하고, return type은 위에서 import한 ResultSet 객체입니다.

위 코드에서 ResultSet은 mytable이 됩니다. 그리고, 처음 선언할 때 ResultSet 객체는 첫 row의 포인터로 초기화됩니다. 그럼, ResultSet에 저장된 데이터를 출력하고 싶을 때는 어떻게 할까요?

 

3. ResultSet 출력하기

위에서 말했듯이 rset은 첫 row의 포인터입니다.

ResultSet의 .getString(int), .getFloat(int) 메소드는 paremeter를 column index로 받으면(1 indexed), 포인터의 row, parameter의 column에 해당하는 값을 반환합니다.
ResultSet의 .next() 메소드는 포인터를 다음 row로 옮깁니다. 만약 마지막 row에서 .next()를 호출하면 null값이 반환됩니다.

이 메소드들을 이용해 ResultSet의 모든 data를 출력하면 다음과 같습니다.

// 테이블의 모든 data를 출력 (1번째 type은 string, 2번째 type은 float)
while (rset.next()) {
	// print Line by Line (getString() parameter은 attribute name or num(1 indexed))
	System.out.println(rset.getString(1) + " " + rset.getFloat(2));
}

 

4. 사용자의 input에 따라 쿼리 처리하기

위에까지는 SQL만으로도 얼마든지 할 수 있는 기능들입니다. 이제, 쿼리문 String에 user input값을 넣어 input에 따라 쿼리를 처리하려면 어떻게 할까요?

String input_val = scanner.nextLine();

Resultset rset = stmt.executeQuery(
"select string_att from mytable 
where string_att = 
' " + input_val + " ') ");

만약 input_val = "ggyuchive"로 받는다면 select string_att from students where string_att = 'ggyuchive'
가 실행될 것입니다. 좋은 기능이지만, 이에는 상당히 큰 문제가 있습니다.

 

5. SQL injection

바로 SQL injection이라는 것인데요. 만약 악의적인 사용자가 input_val에 이상한 값을 넣으려고 하면 어떻게 될까요?

SQL injection을 설명한 만화

위에서 든 예시와 똑같은 table로 예를 들면, input_val = "a'; DROP TABLE mytable; --"이 들어갔다고 합시다. 그럼,

select string_att from students where string_att = 'a';
DROP TABLE mytable;
--

이 되고, DROP TABLE 또한 처리하게 됩니다! --는 commit을 의미하며, 되돌릴 수 없습니다. 한 악의적인 사용자 때문에 mytable의 data가 모두 날아갈 수 있는 상황이 왔습니다. 이를 SQL injection이라 하고, JDBC에는 이를 피할 수 있는 방법이 존재합니다.

 

6. PreparedStatement

위에서 저희는 PreparedStatement를 import했습니다. 이 라이브러리는 SQL injection을 해결해줍니다. 한 마디로 statement를 바로 실행하지 않고 먼저 만든 뒤 실행한다는 의미입니다. JDBC의 PreparedStatement는 이렇게 사용합니다.

PreparedStatement pStmt = conn.prepareStatement("select string_att from mytable where string_att = ?");
String input_val = scanner.nextLine();
pStmt.setString(1, input_val);

Resultset rset = pStmt.executeQuery();

이렇게, PreparedStatement 객체 pStmt를 생성하고, 변수로 넣고 싶은 값을 ?로 설정합니다. ?의 index는 1 indexed이고, input_val을 받아 .executeQeury()까지 실행하면 4번 코드와 똑같은 logic의 코드가 됩니다.

이것이 어떻게 SQL injection을 해결하냐고요?
바로, PreparedStatement의 .setString()메소드에 비밀이 있습니다.

5번 예시와 같이 input_val = "a'; DROP TABLE mytable; --"라고 합시다.
여기서 .setString(int, String)은 String에서 ' (작은 따옴표)가 나타난다면 바로 escape하고 읽기를 중단합니다. 즉, input_val을 저렇게 설정해도 setString(1, input_val)은 "a"에서 멈추는 것이죠!

SQL injection을 발생시키기 위해서는 data type에 맞는 값을 넣고 작은 따옴표와 세미콜론으로 구문을 종료한 뒤, 다른 구문을 실행시켜야 하는데 이를 방지하고자 작은 따옴표가 등장하면 바로 escape해 injection을 방지합니다.

 

이렇게 JDBC의 간단한 사용방법과 라이브러리 및 메소드, SQL injection과 PreparedStatement에 대해 알아봤습니다. PreparedStatement의 경우 같은 logic을 처리하는 코드가 Statement보다 길어지긴 하지만, 이러한 불상사를 방지하기 위한 목적이 있습니다.

728x90

+ Recent posts