본문 바로가기
국비학원

[국비지원] KH 정보교육원 116일차

by 도전하는 개발자 2022. 9. 14.

* 트랜잭션 (Transaction, TX)

가. 분산 TX ( == Global TX) 
 - 한 요청 처리에 2개 이상의 연결(Connection)을 사용하여 SQL 처리를 하는 트랜잭션
 - 동일한 DB에 2개 이상의 연결 (JDBC Connection)들을 사용
 - 각 연결 (JDBC Connection)마다, 각각 다른 DB를 사용
 - 개발자가 직접 TCL 사용 불가! (TX 관리자가 대신 수행)
 - 스프링은 @Transactional 어노테이션으로 TX 관리
 - 2PC 사용 (완전하지 않음) : 2 Phase Commit
   ex) 2개의 데이터 소스 사용 (각각 연결된 DB가 다름)
       1st  phase :  현재 분산 TX에 참여한 각각의 데이터 소스에게 Commit할 준비가 되어 있는지 물어봄
       2nd phase :  각 데이터 소스에 연결된 DB의 TX 관리자에게 Commit 부탁
 - TX관리자 (규약 : X/OPEN XA (분산TX)) : WAS, Spring, Database

나. 지역 TX ( == Local TX) 
 - 1개의 연결 (JDBC Connection)으로, 여러개의 SQL(DML) 문장을 수행하는 경우의 트랜잭션
 - 이때 TCL (commit/rollback 문장) 사용
 - 분산 TX 관리는 애시당초 불가능!


* Transactional Boundary (하나의 TX로 처리할 Connection의 범위)
 - 하나의 TX로 처리(All or nothing)할 DML 작업의 범위

* 계좌이체 : DataSource 객체 활용 (Connention Pool)
* 항공사의 2가지 시스템 : (1) 예약(티켓), (2) 발권

* Connection.commit(), Connection.rollback(), => for local TX

try {
    Connection c1 = ...
    Connection c3 = ...
    Connection c2 = ...

    c1.SQL(DML, 10번)
    c2.SQL(DML, 2번)
    c3.SQL(DML, 1번)

    c1.commit();  ---> OK : 영속성 부여완료 (영구기록)
    c2.commit();  ---> Network 끊김 -> 오류발생
    c3.commit();

} catch (SQLException e) {

    c1.rollback(); ---> XX : 이미 try 블록에서 영속성 부여완료
    c2.rollback(); ---> XX : 이미 끊어진 연결이기 때문
    c3.rollback(); ---> XX : 수행조차 안 됨

}

---

* 트랜잭션 전파(Transaction Propagation)

@Transactional      // 분산 트랜잭션 처리 => 즉, 스프링의 트랜잭션 관리자를 이용한 2PC 수행(***)

1. @Transactional(propagation=Propagation.REQUIRED)      // Default
기본 : 새로운 TX 만들어 실행 (격리성을 가짐)

2. @Transactional(propagation=Propagation.MANDATORY)
반드시 특정(***) TX 하에서만 수행가능
비유: 물이 반드시 필요는 한대, 자기의 물을 만들지 않고, 이미 흐르는 `특정` 물에 자기 몸을 담가서 같이 흘러가겠다)
   
3. @Transactional(propagation=Propagation.NESTED)
기존 TX 이 존재하는 경우, 이 Tx에 포함되어 수행
(비유: 흐르는 물에 몸을 담그겠다. 대신 어떤 물이든 괜찮다!!!)
   
4. @Transactional(propagation=Propagation.SUPPORTS)
TX가 필요없으나(***), TX 하에 있다면 포함되어 실행
(비유: 굳이 흐르는 물이 있으면 몸을 담그고, 없으면 오히려 좋다!)
   
5. @Transactional(propagation=Propagation.NEVER)
TX 없이 수행. 만일 TX하에서 수행하면 오류발생
(비유: 난 절대 물이 필요없다. 오히려 물에 담그면 죽는다!)
   
6. @Transactional(propagation=Propagation.NOT_SUPPORTED)
기존 TX가 있는 경우, 이 TX이 끝날 때까지 실행을 잠시 보류한 후, 기존 TX이 끝나면 재수행
(비유: 나 역시 물이 필요한데, 이미 흐르는 물이 있으면, 다 지나갈때까지 기다렸다, 나만의 물을 만들어 흘러가겠다)
   
7. @Transactional(propagation=Propagation.REQUIRES_NEW)
무조건 자신만의 고유한 TX 생성하여 실행
(비유: 기존에 흐르는 물이 있더라도, 나만의 물을 별도로 만들어 가겠다)

---

* 트랜잭션 격리레벨(Transaction Isolation Level)
DQL작업과 관련이 있음
대전제 : 각 트랜잭션 격리된다(Isolation 성질을 가진다). 즉, 트랜잭션들끼리는 서로 간섭하지 않는다
하지만, 위의 대전제는 격리수준(Isolation Level)을 조절함으로써, 깨뜨릴 수 있다! 
   
1. DEFAULT          : 사용하는 데이터소스에 설정된 격리수준을 그대로 따르겠다!! => 기본설정 / 기본 격리수준은 READ_COMMITTED 임
2. READ_COMMITTED   : SELECT 수행시, 읽을 수 있는 데이터는 commit이 완료된 데이터만 읽을 수 있다!!
3. READ_UNCOMMITTED : READ_COMMITTED의 반대 => 즉, 커밋이 완료되지 않는 데이터도 읽을 수 있다!!
4. REPEATABLE_READ  : 테이블의 같은 행에 대해, 두가지 이상의 다른 행위를 하는 트랜잭션들이 있을때,
                             이 행을 반복적으로 읽어온다 하더라도, 다른 트랜잭션 변경결과를 읽어오지 않는다
5. SERIALIZABLE     : 가장 강력한 격리수준으로, 각 트랜잭션을 먼저 도착한 순서대로, 각자의 트랜잭션 처리를 수행하고, 그 다음 트랜잭션 수행하고, 그 다음 ....

---

* Password 암호화

* Bcrypt => 수리적으로 단방향 해쉬 생성
  - 특징: 1비트까지 동일한 데이터라고 해도, 매번 해쉬를 수행할 때마다, 다른 해쉬값을 생성
  - 로그인(NOT 회원가입) 수행시, 사용자가 입력한 암호데이터와 회원테이블에 저장된 해쉬값을 비교
  - 사용자가 입력한 암호(평문-Plain Text) => 동일한 Bcrypt 알고리즘통과     
    => 해쉬값(Cipher Text) 생성하긴 하는데, 매번 다르다!!!
    => 그런데 위의 특징때문에, 매번 같은 데이터(암호)라고 해도, 다른 해쉬값이 생성되는데 어떻게 비교하느냐!?
  - 매번 같은 데이터에 대해서, 다른 해쉬값을 생성하더라도, 수리적으로 평문과 이전 해쉬값을 비교해도, 단 1비트라도 다르지 않으면 동일한 결과(=즉, 같다!!!!)가 나오고, 다른 해쉬값하고 비교해도 동일한 결과(=즉, 같다!!!)가 나오게 수리적으로 처리.

 

@Log4j2
@NoArgsConstructor

@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BCryptPasswordEncoderTests {
	
	@Disabled
	@Test
	@Order(1)
	@DisplayName("1. testBCryptPasswordEncoder")
	@Timeout(value = 10, unit =TimeUnit.SECONDS)
	void testBCryptPasswordEncoder() {
		log.trace("testBCryptPasswordEncoder() invoked.");
		
		String password = "John1234!!";  // Plain Text
			
		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
		String cipherText = encoder.encode(password);
		log.info("\t+ cipherText : {}", cipherText);
				
	} // testBCryptPasswordEncoder
	
	
	@Disabled
	@Test
	@Order(2)
	@DisplayName("2. testMatches")
	@Timeout(value = 10, unit =TimeUnit.SECONDS)
	void testMatches() {
		log.trace("testMatches() invoked.");
		
		String plainText = "John1234!!";  // Raw Password
			
		// Case1 : 저장된 해쉬값과 입력한 암호의 비교
//		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
//		String cipherText = encoder.encode(plainText);
//		log.info("\t+ cipherText : {}", cipherText);
//		
////		------------------------------------
// 
//		// plainText : 로그인 창에서 사용자가 입력한 Raw Password
//		// cipherText : 사용자 테이블의 암호컬럼에 저장된 해쉬값
//		boolean isMatched = encoder.matches(plainText, cipherText);
//		log.info("\t+ 2. isMatched : {}", isMatched);
		
		
		// Case2 : 같은 암호(Plain text)에 대해서 여러번 해쉬값을 생성하여 비교
		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
		
		for(int i=0; i<10; i++) {
			String cipherText = encoder.encode(plainText);
			log.info("\t+ [{}]. cipherText : {}", i,  cipherText);
			
			boolean isMatched = encoder.matches(plainText, cipherText);
			log.info("\t+ [{}]. isMatched : {}",i,  isMatched);
		} // for
		
				
	} // testMatches
	
	
	@Test
	@Order(3)
	@DisplayName("3. testBCryptPasswordEncoderWithSalt")
	@Timeout(value = 10, unit =TimeUnit.SECONDS)
	void testBCryptPasswordEncoderWithSalt() {
		log.trace("testBCryptPasswordEncoderWithSalt() invoked.");
		
		String password = "John1234!!";  // Plain Text
		String salt = "SALT_";
		
//		암호의 해쉬값 생성시, "salt"추가 권장
//		"Salt" - 사용자가 가입시 입력한 암호 + 내부에서 지정된 추가 문자열
			
		BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
		String cipherText = encoder.encode(salt + password);
		log.info("\t+ 1. cipherText : {}", cipherText);
		
//		---
		
		log.info("\t+ 2. isMatchWithSalt : {}", encoder.matches(salt+password, cipherText) );
		
				
	} // testBCryptPasswordEncoderWithSalt
	

} // end class

BCryptPasswordEncoderTests.java

---

* Git & Github

1. git : 버전관리(VCS, Version Control System) 도구 

2. github : 원격저장소, 내 PC에 저장해 놓은 버전관리를 위해서 만든 원본파일 + 변경내역등을 백업하는 웹사이트. 

git을 이용한 버전관리의 큰 흐름을 우선적으로 이해하고 실습하자!!! (GUI로)
그 다음에 이 큰 흐름을 명령어 (CLI)로 그대로 수행

https://git-scm.com/downloads