PreparedStatement 생성
PreparedStatement는 쿼리문을 미리 준비하여 컴파일하고, 매번 실행할 때마다 파라미터 값을 전달하여 실행한다.
public class Application {
public static void main(String[] args) {
Connection connection = getConnection();
PreparedStatement statement = null;
ResultSet resultSet = null;
String query = "SELECT MENU_CODE,NAME,PRICE FROM TBL_MENU";
try {
statement = connection.prepareStatement(query);
resultSet = statement.executeQuery();
while (resultSet.next()) {
System.out.println(resultSet.getString("MENU_CODE") + ", " + resultSet.getString("NAME"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(resultSet);
close(statement);
close(connection);
}
}
}
일반적인 statement는 statement를 만들 때 아무것도 전달하지 않고 statement를 실행할 때 쿼리문을 전달한다.
preparedStatement는 statement를 만들 때부터 쿼리문을 전달하고 실행할 때는 아무런 쿼리도 전달하지 않는다.
preparedStatement를 인자로 받는 close메소드를 구현하지 않았지만 정상적으로 동작하는데 preparedStatement는 statement를 상속받았기 때문이다.
위와 같은 상황에서는 preparedStatement의 편의성을 체감하지 못한다.
하지만 LIKE문을 통해서 검색을 하는 상황이라고 가정하면 확실히 알 수 있다.
public class Application {
public static void main(String[] args) {
Connection connection = getConnection();
PreparedStatement statement = null;
ResultSet resultSet = null;
MenuDTO row = null;
List<MenuDTO> menuList = null;
Scanner sc = new Scanner(System.in);
String menuName = sc.nextLine();
String query = "SELECT * FROM TBL_MENU WHERE NAME LIKE '%' || ? || '%'";
try {
statement = connection.prepareStatement(query);
statement.setString(1, menuName);
resultSet = statement.executeQuery();
menuList = new ArrayList<>();
while (resultSet.next()) {
row = new MenuDTO();
row.setMenuCode(resultSet.getString("MENU_CODE"));
row.setName(resultSet.getString("NAME"));
row.setPrice(resultSet.getInt("PRICE"));
row.setOrderableStatus(resultSet.getString("ORDERABLE_STATUS"));
row.setCategoryCode(resultSet.getString("CATEGORY_CODE"));
menuList.add(row);
}
for (MenuDTO menu : menuList) {
System.out.println(menu);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(resultSet);
close(statement);
close(connection);
}
}
}
'?' 키워드를 통해서 값이 들어갈 위치를 미리 잡고 setString으로 검색할 값을 바인딩하여 실행하는 방식이다.
이것을 위치홀더라 하며 위치홀더를 사용하면 코드를 작성하기 편리하다.
편의성 뿐만 아니라 속도면에서도 빠르다.
statement를 사용하면 조회를 할 때마다 쿼리문이 달라진다.
preparedStatement를 사용하면 쿼리문 자체에 변화가 없고 값만 다르게 설정한다.
쿼리문을 컴파일 할 때 쿼리문 문법을 1차적으로 검사하고 테이블명, 컬럼명을, 연산을 검사하고 컴파일을 진행한다.
쿼리문과 컴파일된 쿼리문을 key-value 형태로 가지고 있다가 같은 쿼리문을 요청하면 속도가 빨라진다.
하지만 key값이 조금이라도 변하게 된다면 다시 컴파일을 하게 된다.
한 번 컴파일 한 쿼리문을 다시 컴파일 하지 않기 때문에 preparedStatement의 수행속도가 더욱 빠르다.
statement를 쓸 때 더욱 유리한 경우가 있는데 쿼리문에 변화가 없을 경우,쿼리에 전달되는 값이 없는 경우이다.
컴파일 측면에서는 속도가 비슷하지만 바인딩 하는 과정이 없어 속도가 더 빠르다.
또한 보안적인 부분에서도 이점이 있다.
public class Application {
private static String menuCode = "10";
private static String menuName = "' OR 1=1 AND MENU_CODE = '10";
public static void main(String[] args) {
Connection connection = getConnection();
PreparedStatement statement = null;
ResultSet resultSet = null;
String query = "SELECT * FROM TBL_MENU WHERE MENU_CODE = ? AND NAME = ?";
try {
statement = connection.prepareStatement(query);
statement.setString(1, menuCode);
statement.setString(2, menuName);
resultSet = statement.executeQuery();
if (resultSet.next()) {
System.out.println("조회된 메뉴 : " + resultSet.getString("NAME"));
} else {
System.out.println("조회된 메뉴가 없습니다.");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(resultSet);
close(statement);
close(connection);
}
}
}
위 코드는 sql injection을 테스트 해볼 수 있는 코드이다.
statement를 사용하면 쿼리문을 조작해서 Name값을 사용할 수 있다.
이 떄 preparedStatement를 사용하면 이러한 해킹기법을 코드레벨에서 방지할 수 있다.
sql문에 작성되어있는 불필요한 '들을 내부에서 제거하기 때문에 보안적인 측면에서 우수하다.
'Backend > JDBC' 카테고리의 다른 글
JDBC DTO의 개념 (0) | 2023.06.12 |
---|---|
JDBC statement와 resultSet 생성 (0) | 2023.06.11 |
JDBC Template 구현 (0) | 2023.06.10 |
JDBC 데이터베이스 접속 정보 분리 (0) | 2023.06.09 |
JDBC Connection 생성 (0) | 2023.06.08 |
댓글