Day 30. JDBC 3 ( PrepareStatement )

ho_c·2022년 3월 29일
0

국비교육

목록 보기
30/71
post-thumbnail
post-custom-banner

JDBC를 시작한 지 3일째, 정말 투박했던 내 코드에 이것, 저것 넣으면서 볼만하게 만들고 있다.

이런 걸 리팩토링이라고 하는데, 사실 너무 투박한 코드에 당연한 것을 넣는 것이다 보니 ‘리팩토링’이라는 거창한 단어를 못 붙이겠다 ㅠㅠ
하지만 확실히 코드가 훨씬 깔끔해지는 느낌이다.

무튼 오늘은 다음 세 가지를 배워봤다.

📝목차

  1. PrepareStatement
  2. try-resource
  3. 암호화

1. PrepareStatement

[ 문제 ]

기존 실습할 때, 워크시트 용도로 Statement 인스턴스를 사용했다.
근데 가장 큰 문제가 Statement를 사용하면 쿼리를 작성할 때, 다음처럼 문자열을 연결해 만들었는데, 해보면 알겠지만 무지 불편하고 투박하다. 오타는 덤..

"insert into cafe_menu values(cafe_seq.nextval, '"+pname+"', "+price+", '"+iced+"')"

이런 불편함을 극복하기 위해 사용하는 게 PrepareStatement 이다.

[ PrepareStatement ]

  • Statement를 상속받는 클래스이다.
  • Statement 의 보안문제와 쿼리작성의 불편함을 제거하였다.
  • Statement 를 상속받기 때문에 Statement로 했던 처리를 할 수 있다.
  • 실행 중 컬럼 명이 바뀌는 동적 쿼리를 짤 때, 사용하기 어렵다.

[ 장점 ]

1) 대부분, 내부적인 동작 방식의 특징상 성능이 더 좋다.
2) ? 로 변수를 설정하기 때문에 변수 설정이 편리하다.
3) 내부적으로 SQL 주입을 방지하기 위한 기능이 있다 (보안 공격이 먹히지 않는다)

[ 사용법 ]

  • 인자값을 SQL로 받는다.
  • SQL의 빈칸은 ? 으로 채우고, 이를 인덱스로 인식해 처리한다. (1부터 시작)
  • set 으로 들어갈 자료형을 정하고 그에 따라 홑따옴표를 붙여준다.
// (1) SQL 작성
String sql = "delete from cafe_menu where pid=?";
// (2) prepareStatement 인스턴스를 통해 연결
PreparedStatement pstat = con.prepareStatement(sql);
// (3) ? 의 빈 칸을 채워준다.
pstat.setInt(1, 1013);
// (4) 연결을 통해 쿼리를 실행한다. statement처럼 sql을 인자값으로 넣지 않는다.
pstat.executeUpdate();

2. try-resource

[ 문제 ]

DB의 연결은 한정적인 자원으로 일정 수 이상, DB에 접속하면 DB서버가 터지고 만다.
그래서 DAO에서 각 기능을 처리할 때, close(); 를 통해 연결을 닫는다.

하지만 한 가지 문제는, 연결이 열리고 메서드가 진행되는 중 예외가 발생하면 DAO의 Callee 메서드들은 모두 throws Exception 으로 main 으로 예외를 전가해버린다.
즉, 코드의 흐름이 예외가 발생하면 main으로 나가서 끝나게 되어 close(); 가 실행되지 않는다.

결국 메서드를 사용할 때마다 연결들이 쌓이게 되고, DB의 한계를 넘어서면 DB가 터진다.
이런 문제를 해결하기 위해 3가지 방법이 있었다.

[ 해결방안 ]

1) try-catch

  • 가장 원시적인 방법으로 기존의 예외전가를 없애고 메서드 자체에서 예외처리를 해버린다.
  • catch 절을 통해 무조건 닫아주게 해놨다.
  • 하지만, catch절의 코드도 동작하다 예외가 발생할 경우 똑같은 문제가 생긴다.
Connection con = null;
try {
	con = Driver~
	con.close(); 
} catch{
	con.close(); 
}

2) try – catch(다중 가능) - [finally]

  • catch는 예외에 따라서 다양하게 적용이 가능하였다. 하지만 앞의 문제는 여전했다.
  • 그래서 나온 게 finally 절이다.

[ finally ]
try-catch에 한해 메서드를 나가려고 하는 모든 동작에 대해서 실행된다.

  • 코드의 흐림이 메서드의 닫는 블록을 만나게 되면, 실행된다.
  • 정상이든, 예외이든 항상 마지막에 실행된다.
try {
	con = Driver~
	~~~ 
} catch{
	~~~ 
} finally{
	con.close();
}

3) try – resource : 최신 코드

  • 여태 사용하던 try 구문의 사용법과 다르다.
  • 똑같이 예외 전가처리를 해주는데, 코드의 흐름이 다를 뿐이다.
try(-){}

[ 실행 순서 ]

① 소괄호의 내용을 실행
② 중괄호 실행
③ 소괄호 Close

try(
	Connection con = this.getConnection();
	PreparedStatement pstat = con.prepareStatement(sql);
	){
     pstat.setInt(1, pid);
			
     int result = pstat.executeUpdate();
			
     con.commit();
			
     return result;
		}
  • 실행 순서에 따라서 먼저 () 소괄호 안에 코드를 실행한다.

  • 단, 소괄호 안에 오는 코드들은 AutoCloseable 클래스를 상속받은 클래스들만 가능하다.
    = 닫을 수 있는 객체가 선언되는 코드만 가능

  • 리턴, 예외 등 어떤 상황이든 {} 중괄호 가 벗어나면 ③이 실행된다.

  • PrepareStatement 로 빈칸을 채울 때는 이중으로 사용한다.

try(
	Connection con = this.getConnection();
	PreparedStatement pstate = con.prepareStatement(sql);
	){
		String encryPw = utils.EncryptUtils.SHA256(pw);
		pstate.setString(1, id);
		pstate.setString(2, encryPw);
			try(ResultSet rs = pstate.executeQuery();){
				boolean result = rs.next();
				return result;	
			}		
		}

3. 암호화

회원가입을 하면 우리가 지정한 PW가 DB에 그대로 들어가게 된다.
이때, DB 관리자가 PW를 볼 수 있게 된다. 물론 봐도 되지만 우리의 PW을 알아봐 선 안된다.
따라서 암호화 작업을 하여 PW을 알아보지 못하게 한다.

  • 암호화 알고리즘을 내장한 클래스들이 내장 라이브러리로 제공된다.
  • 이를 통해 MD5, SHA256, SHA512등의 메서드를 만든다. (MD5는 무결성에 문제가 있음)
  • 복호화가 가능한 양방향 알고리즘을 사용하지 않는다. 단방향 암호화 알고리즘

[ SHA256 ]

  • 입력받는 pw의 길이가 몇이든 64인 암호로 반환한다.
  • 다른 텍스트여도 같은 암호화가 될 수 있지만, 그 확률이 굉장히 낮다.
  • 길이가 64이기 때문에 테이블의 pw 크기는 128로 지정한다.
  • 이래서 단방향이라 비밀번호 찾을 때, 새로 입력하라는 거다.
public static String SHA256(String pwd) {
	try{
			MessageDigest digest = MessageDigest.getInstance("SHA-256");
			byte[] hash = digest.digest(pwd.getBytes("UTF-8"));
			StringBuffer hexString = new StringBuffer();

			for (int i = 0; i < hash.length; i++) {
				String hex = Integer.toHexString(0xff & hash[i]);
				if(hex.length() == 1) hexString.append('0');
				hexString.append(hex);
			}
			//출력
			return hexString.toString();
		} catch(Exception ex){
			throw new RuntimeException(ex);
		}
	}


// DAO에 적용 시.
String sql = "insert into member values(?, ?)";
String encryPw = utils.EncryptUtils.SHA256(Pw);
pstate.setString(1, dto.getId());
pstate.setString(2, encryPw); // 입력받은 pw을 암호화하여 쿼리에 반영
pstate.executeUpdate(); // 해당 쿼리를 전달
con.commit(); // DB에 반영
profile
기록을 쌓아갑니다.
post-custom-banner

0개의 댓글