티스토리 뷰
KH 128일차 - Spring ( 트랜잭션 / 패스워드 암호화 / open API ) (******)
monimoni 2022. 8. 30. 15:191. 트랜잭션의 원칙 ( ACID )
- 1 ) A ( Atomicity : 원자성 )
- : 하나의 트랜잭션은 모두 하나의 단위로 처리되어야 한다.
- : 어떤 트랜잭션이 A와 B로 구성되어 있을 경우, A와 B의 처리 결과는 동일해야 되기 때문에 둘 중 하나라도 실패할 경우 원래의 상태로 돌아가야 한다. ( ALL or Nothing )
- 2 ) C ( Consistency : 일관성 )
- : 트랜잭션이 성공했다면, 데이터 베이스의 모든 데이터는 일관성을 유지해야 한다.
- : 트랜잭션으로 처리된 데이터와 일반 데이터 사이에는 어떠한 차이도 있으면 안된다.
- 3 ) I ( Isolation : 격리 )
- : 트랜잭션으로 처리되는 중에 외부에서의 간섭은 없어야 한다.
- 4 ) D ( Durability : 영속성 )
- : 트랜잭션이 성공적으로 처리되면, 그 결과는 영속적으로 보관되어야 한다.
[ 1. 트랜잭션 ] (*****)
[ 1 - 1. Service 인터페이스 생성 ]
[ + 코드 보기 ]
package org.zerock.myapp.service;
import org.zerock.myapp.exception.ServiceException;
public interface SampleTXService {
public abstract void addData(String data) throws ServiceException;
} // end interface
[ 1 - 2. Service 구현 ] (****)
[ + UncategorizedSQLException 예외를 그대로 던져줘야지만 트랜잭션 메니져가 구동된다. (***) ]
[ + 트랜잭션 메니저는 UncategorizedSQLException를 받아야만 분산처리를 해준다. (***) ]
[ + 만약, UncategorizedSQLException를 다른 예외로 감싸서 예외를 던질 경우 오류가 발생하게 된다. ]
[ + 코드 보기 ]
package org.zerock.myapp.service;
import java.util.Objects;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.zerock.myapp.exception.ServiceException;
import org.zerock.myapp.mapper.Sample1Mapper;
import org.zerock.myapp.mapper.Sample2Mapper;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
@NoArgsConstructor
@Log4j2
@Service
public class SampleTXServiceImpl implements SampleTXService {
// ============================================
// + 1. 의존성 주입
// ============================================
@Setter(onMethod_= {@Autowired})
private Sample1Mapper mapper1;
@Setter(onMethod_= {@Autowired})
private Sample2Mapper mapper2;
// ============================================
@Transactional
@Override
public void addData(String data) throws ServiceException {
log.trace("addData({}) invoked.", data);
try {
// ============================================
// + 의존성 주입 확인
// ============================================
Objects.requireNonNull(this.mapper1);
assert this.mapper2 != null;
// ============================================
// + 아래 2개의 DML 작업은 1개의 트랜잭션으로 처리해야 한다.
// ============================================
this.mapper1.insertCol(data); // 가정 1 : 소스계좌에서 출금
this.mapper2.insertCol(data); // 가정 2 : 타겟계좌에 입금
log.info("\t + Transfer done Successfully!!");
} catch ( UncategorizedSQLException e ) {
// ============================================
// + Spring TX 메니저가 Global 트랜잭션 처리를 해주려면
// + 이와 같이 UncategorizedSQLException 예외를 그대로 throw해줘야 한다. (****)
// + 만약, 이렇게 해주지 않으면, Global 트랜잭션 처리가 되지 않는다.
// ============================================
throw e;
} catch(Exception e) {
log.info("\t + Transfer done Failure....");
log.info("\t + Error Type : {}", e.getClass());
log.info("\t + Error Type : {}", e.getClass().getName());
throw new ServiceException (e);
} // try - catch
} // addData
// ============================================
} // end class
[ 1 - 3. test 하기 ]
[ + 코드 보기 ]
package org.zerock.myapp.service;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.zerock.myapp.exception.ServiceException;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/**/root-context.xml")
@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class SampleTXServiceTests {
// ===================================================
// + 1. 의존성 주입
// ===================================================
@Autowired
private SampleTXService service;
// ===================================================
// + 2. 의존성 주입 확인
// ===================================================
@BeforeAll
void beforeAll () {
log.info("beforeAll() invoked.");
assertNotNull(this.service);
log.info("\t + this.service : {}", this.service);
} // beforeAll
// ===================================================
// + 3. 분산 트랜잭션 처리 (***)
// + -> 즉, 스프링의 트랜잭션 관리자를 이용한 2PC 수행
// ===================================================
@Test
@Order(1)
@DisplayName("1. testTransfer")
@Timeout( value = 3 ,unit = TimeUnit.SECONDS)
void testTransfer() throws ServiceException {
log.info("testTransfer() invoked.");
String data = "999999999999999999999999999999999999999999999999999999999"; // 하나의 sql문에서 오류
this.service.addData(data);
log.info("\t + ALL Insert Success!!");
} // testTransfer
} // end class
[ 2. 트랜잭션 전파 ] (***)
[ + 코드 보기 ]
// ===============================================================================
// + 2. @Transactional propagation ( 트랜잭션 전파 )
// + : 2개 이상의 TX가 존재할 경우 로직에 따라 결정해야 된다. (***)
// ===============================================================================
// + NESTED와 SUPPORTS는 상당히 유사하지만,
// + SUPPORTS는 TX가 필요 없다는 것이 전재지만, NESTED는 TX가 필요하다는 전재이다.
// ===============================================================================
// + MANDATORY와 NESTED는 둘 다 TX가 필요하고 중첩이 가능하다는 점에서 유사하지만,
// + MANDATORY는 자신만의 TX를 생성하는 것이 아니라, 이미 존재하는 특정한 TX하에서 수행
// + NESTED는 이미 존재하는 TX하에서 수행하지만, 특정 TX를 따지지는 않는다.
// ===============================================================================
// + REQUIRED는 시작지점에 사용되기에 새로운 TX를 생성하여 실행되게 되고,
// + REQUIRES_NEW는 중간에 새로운 TX를 만들어서 각자 다른 길로 실행되게 된다.
// + 그렇기에 REQUIRED는 근원이 되는 메소드에 붙여줘야 한다. (***)
// ===============================================================================
// 1. @Transactional(propagation = Propagation.REQUIRED) // Default : 새로운 TX를 만들어 실행 ( 격리성 가짐 )
// 2. @Transactional(propagation = Propagation.MANDATORY) // 반드시 특정 TX 하에서만 사용 가능
// 3. @Transactional(propagation = Propagation.NESTED) // 기존 TX가 존재할 경우, 이 TX에 포함되어 수행
// 4. @Transactional(propagation = Propagation.NEVER) // TX 없이 수행 ( TX 하에서 수행되면, 오류발생 )
// 5. @Transactional(propagation = Propagation.NOT_SUPPORTED) // 기존 TX가 있는 경우, 그 TX가 끝날때까지 실행을 보류
// 6. @Transactional(propagation = Propagation.REQUIRES_NEW) // 무조건 자신만의 고유한 TX를 생성하여 실행
// 7. @Transactional(propagation = Propagation.SUPPORTS) // TX가 필요없지만, 이미 TX 하에 있다면 포함되어 실행
[ 3. 트랜잭션 격리 레벨 ] (***)
[ + 코드 보기 ]
// ===============================================================================
// + 3. @Transactional isolation ( 트랜잭션 격리 레벨 )
// ===============================================================================
// + 대전제 : 각 트랜잭션은 격리된다. ( isolation 성질을 가진다. )
// + 그렇기에 각 트랜잭션끼리는 서로 간섭하지 않는다.
// + 하지만, 대전제는 격리 레벨을 조절함으로써 깨뜨릴 수 있다.
// ===============================================================================
// 1. @Transactional(isolation =Isolation.DEFAULT ) - 사용하는 데이터 소스에 설정된 격리 수준을 따르겠다.
// - 기본설정의 기본 격리 수준은 READ_COMMITTED이다.
// 2. @Transactional(isolation =Isolation.READ_COMMITTED ) - SELECT 수행시, 읽을 수 있는 데이터는 commit이 완료된 데이터만 읽는다.
// 3. @Transactional(isolation =Isolation.READ_UNCOMMITTED ) - 커밋이 완료되지 않은 데이터도 읽을 수 있다.
// 4. @Transactional(isolation =Isolation.REPEATABLE_READ ) - 테이블의 같은 행에 대해, 2가지 이상의 다른 행위를 하는 트랜잭션이 있을때,
// - 이 행을 반복적으로 읽더라도 다른 트랜잭션 변경결과를 읽어오지 않는다.
// 5. @Transactional(isolation =Isolation.SERIALIZABLE ) - 가장 강력한 격리 수준
// - 각 트랜잭션을 먼저 도착한 순서대로 각자의 트랜잭션 처리를 수행한다.
// ===============================================================================
[ 4. 패스워드 암호화 ] (****)
[ 4 - 1. pom.xml에 spring security core 디펜던시 추가 ]
[ 4 - 2. BCryptPasswordEncoder를 통해 암호화 하기 ]
[ + 코드 보기 ]
package org.zerock.myapp.util;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.Timeout;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@NoArgsConstructor
@Log4j2
@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BCryptPasswordEncoderTests2 {
@Test
@Order(1)
@DisplayName("1. testBCryptPasswordEncoder")
@Timeout(value = 3, unit = TimeUnit.SECONDS)
void testBCryptPasswordEncoder() {
log.trace("testBCryptPasswordEncoder() invoked.");
String pw = "YOYOYO123456!!"; // 원래 암호
String pw2 = "YOYOYO123456!!" + "__SALT__"; // 원래 암호 + 솔트
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
// + 비밀번호를 해쉬 값으로 암호화해주는 클래스
String cipherText = encoder.encode(pw); // cipherText == Hash Value ( 암호화한 값 )
boolean isMatched = encoder.matches(pw, cipherText); // 비밀번호와 암호화된 값이 같은 값인지 확인 (***)
Objects.requireNonNull(cipherText);
log.info("\t + 1. cipherText : {}", cipherText);
log.info("\t + 2. cipherText.length : {}", cipherText.length());
log.info("\t + 3. isMatched : {}", isMatched);
} // testBCryptPasswordEncoder
} // end class
[ 5. Open API 활용하기 위한 기본 준비 ] (***)
[ 5 - 1. 공공데이터 포털에서 Open API 신청 ]
[ 5 - 2. 참고문서를 통해서 해당 Open API 사용법 알아보기 ]
[ 5 - 3. pom.xml에 http client 디펜던시 추가 ]
'KH 정보교육원 [ Java ]' 카테고리의 다른 글
KH 130일차 - Spring ( Java <-> JSON Serialize / Deserialize ) (****) (0) | 2022.09.01 |
---|---|
KH 129일차 - Spring ( Open API / Dom4j / jaxen ) (*****) (0) | 2022.08.31 |
KH 127일차 - Spring( AOP 2 / 트랜잭션[미완성] ) (******) (0) | 2022.08.29 |
KH 126일차 - Spring ( AOP ) (***) (0) | 2022.08.26 |
KH 125일차 - Spring( 인터셉터 3 - 세션 트레킹 : 자동 로그인 ) (******) (0) | 2022.08.25 |