❗ 개인적으로 공부했던 내용을 복습하고 정리하기 위한 글입니다! 따라서 내용이 정확하지 않을 수 있습니다!
ojdbc6버전과 JDK 1.8버전, 윈도우 환경에서 작성된 코드들입니다!
PreparedStatement
은 Statement
클래스를 상속받았다. Statement
보다 기능이 향상 되었고, Statement
보다 코드의 가독성이 상승된다!
extends
키워드를 통해 Statement코드를 상속받는다!
사용방법은 간단하다. Statement
객체 대신 PrepareStatement
객체를 생성하고, 미완성된 SQL문을 먼저 전달한다. 그 후 실행하기 전에 완성된 형태로 만든 후 실행만 한다. 미완성된 SQL문은 사용자가 입력한 값들을 등록할 수 있는 공간을 ?(위치홀더)
로 확보한다.
미완성된 SQL문은 뭐고, 위치홀더는 대체 무엇인가?
바로 전 포스팅에서도 JDBC를 사용할 때,
String sql = "INSERT INTO TEST VALUES (33, '쥬니', 22/10/12)";
와 같이 사용했다. 이게 짧은 구문이면 상관없는데, 만약 값을 많이 넣어야하고, 그걸 객체에서 꺼내오면 String형으로 선언된 sql이 많이 지저분해 보이고, 가독성도 떨어질 것이다. 아래 코드는 그 예시이다.
String sql =
"INSERT INTO MEMBER VALUES(SEQ_USERNO.NEXTVAL, "
+ "'" + m.getUserId() + "', "
+ "'" + m.getUserPwd() + "', "
+ "'" + m.getUserName() + "', "
+ "'" + m.getGender() + "', "
+ m.getAge() + ", "
+ "'" + m.getEmail() + "', "
+ "'" + m.getPhone() + "', "
+ "'" + m.getAddress() + "', "
+ "'" + m.getHobby() + "', SYSDATE)";
위와 같이 getter
를 쓰기 전에 앞뒤로 홑 따옴표를 붙어야하고, 그걸 다 문자열로 합쳐서 하나의 완성된 sql문을 만들어야 한다. 가독성도 가독성이고, 귀찮다! 쿼리에서 오타가 나버리면, 안경 벗고 모니터에 얼굴 가까이하고 코드를 한참 들여다 보아야 한다.
그래서 PreparedStatement
를 사용할 때는 아래와 같은 미완성된 SQL문을 사용한다.
String sql = "INSERT INTO TEST VALUES(?, ?, SYSDATE)";
위의 쿼리와는 다른 점이, 우리가 getter
로 값을 가져올 필요 없이 ?
로 적어두었다. 즉 미완성된 SQL문이 된다는 것이다.
그럼 이 미완성된 SQL문은 언제 완성시키는 것일까? 바로 PreparedStaement
객체 생성 후 미완성된 SQL문의 위치홀더를 set
메소드로 채워준다.
pstmt = conn.prepareStatement(sql); // PreparedStatement 객체 생성
pstmt.setInt(1, 100); // 미완성된 SQL문을 완성시켜 준다
pstmt.setString(2, "무홍");
result = pstmt.executeUpdate(); // 그리고 매개변수 없는 execute메소드 호출
이때 인자값이 두개가 사용되는데, 첫번째 인자는 위치홀더의 위치이고(앞에서부터 시작하고 1, 2, 3, ...으로 정해진다.) 뒤에는 우리가 삽입할 데이터이다. setInt()
, setString()
, setDate()
등이 있으며, 데이터베이스의 컬럼의 자료형에 알맞는 메소드를 호출하면 된다.
또한 setString
을 할 때에는, 앞 뒤에 자동으로 홑 따옴표를 붙여 보내기 때문에 우리가 하나하나 홑 따옴표를 안 붙이고 사용해도 되는 것이다.
마지막으로 Statemet
와는 다르게 매개변수가 없는 execute()
메소드를 호출하면 된다.
int result = 0;
Connection conn = null;
PreparedStatement pstmt = null;
String sql = "INSERT INTO TEST VALUES (?, ?, SYSDATE)";
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "JDBC", "JDBC");
pstmt = conn.prepareStatement(sql); // PreparedStatement 객체 생성
pstmt.setInt(1, 100); // 미완성된 SQL문을 완성시켜 준다
pstmt.setString(2, "무홍");
result = pstmt.executeUpdate(); // 그리고 매개변수 없는 execute메소드 호출
if(result > 0) {
conn.commit();
} else {
conn.rollback();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
pstmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
우리가 와일드카드를 사용해서 특정 키워드가 포함되게 SELECT를 하려고 한다. 그럼 쿼리문을 아래와 같이 작성할 것이고,
SELECT * FROM TEST WHERE TNAME LIKE '%쥬니%';
이를 위치홀더를 통해 아래와 같이 String형으로 선언한 뒤, 실행을 한다.
String sql = "SELECT * FROM TEST WHERE TNAME LIKE '%?%'";
그럼 놀랍게도 "부적합한 열 인덱스" 오류가 난다!
분명 위치홀더로 지정하고, setString()
메소드로 위치홀더를 잘 채워준 것 같은데... 그 이유는 다음에 있다. 위에 말했듯 setString()
메소드는 앞뒤로 홑 따옴표를 붙이기 때문에, 만약 위와같은 쿼리문에서 setString(1, "쥬니");
를 했다면, 쿼리문은
SELECT * FROM TEST WHERE TNAME LIKE '%'쥬니'%';
가 되어버리기 때문에 오류가 발생한다.
이에 대한 해결방법은 두가지가 있다.
오라클에서 지원해주는 연결연산자 ||
을 사용하는 방법이 있다. 이를 사용하면 쿼리문을 작성하면 아래와 같다.
String sql = "SELECT * FROM TEST WHERE TNAME LIKE '%'||?||'%'";
이렇게 되면, 위치홀더를 지정할 때 setString(1, "쥬니");
를 해주면 값이 정상적으로 들어간다.
또다른 방법으로는 setString()
에서 가공하는 방법이다. 다시 말하지만, 위치홀더는 문자열 앞뒤로 홑 따옴표를 붙여 보내준다고 했다. 그럼 쿼리를 아래와 같이 쓰고,
String sql = "SELECT * FROM TEST WHERE TNAME LIKE ?";
위치홀더에서 아래와 같이 가공을 해주면 된다.
pstmt.setString(1, "%쥬니%");
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rset = null;
// String sql = "SELECT * FROM TEST WHERE TNAME LIKE '%'||?||'%'";
String sql = "SELECT * FROM TEST WHERE TNAME LIKE ?";
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "JDBC", "JDBC");
pstmt = conn.prepareStatement(sql);
// pstmt.setString(1, "쥬니");
pstmt.setString(1, "%쥬니%");
rset = pstmt.executeQuery();
while(rset.next()) {
System.out.println(rset.getInt("TNO"));
System.out.println(rset.getString("TNAME"));
System.out.println(rset.getDate("TDATE"));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
pstmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
뭔가 전에 Statement
를 사용했던 때에는 쿼리가 정말 너무 더럽고 보기도 불편했는데 PreparedStatement
를 사용하니 가독성이 올라갈 수 있었다! 그런데, 단점아닌 단점이라면 내가 작성한 쿼리문을 볼 수 없다는 것이다... 그래도 가독성이 올라갔다는 데에 큰 점수를 줄 것 같다!
개인적으로 와일드카드에서 사용할 때에는 2번방법이 편한 것 같다. 쿼리 깔끔하게 보는 게 제일이라고 생각한다!(진짜 개인적인 생각이다.)
끝!😗