[JAVA] JDBC PreparedStatement

쥬니·2022년 10월 12일
1

공부

목록 보기
7/11

❗ 개인적으로 공부했던 내용을 복습하고 정리하기 위한 글입니다! 따라서 내용이 정확하지 않을 수 있습니다!

ojdbc6버전과 JDK 1.8버전, 윈도우 환경에서 작성된 코드들입니다!

🔧 PreparedStatement

PreparedStatementStatement클래스를 상속받았다. Statement보다 기능이 향상 되었고, Statement보다 코드의 가독성이 상승된다!

extends키워드를 통해 Statement코드를 상속받는다!

❓ 사용방법

사용방법은 간단하다. Statement객체 대신 PrepareStatement객체를 생성하고, 미완성된 SQL문을 먼저 전달한다. 그 후 실행하기 전에 완성된 형태로 만든 후 실행만 한다. 미완성된 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()에서 가공하기

또다른 방법으로는 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번방법이 편한 것 같다. 쿼리 깔끔하게 보는 게 제일이라고 생각한다!(진짜 개인적인 생각이다.)
끝!😗

0개의 댓글