티스토리 뷰
1. 분산 트랜잭션 ( Distributed TX == Global TX )
- 1 ) 2 PC ( two-phase commit : 2단계 commit )
- : 1단계. 각 지역 데이터소스에게 잘 끝났는지, 커밋할 준비가 되었는지 물어본다
- : 2단계. 모든 데이터 소스의 응답이 OK라면, 각 지역 데이터소스는 commit 수행
- : + 만약 하나라도 응답이 NO라면, 각 지역 데이터소스는 rollback 수행
- 2 ) 모든 데이터소스의 TCL을 관리하는 주체가 필요하다. -> 트랜잭션 관리자
- 3 ) 분산 트랜잭션 관리는 반드시 "트랜잭션 관리자"가 필요하다.
[ 1. AOP ] (*****)
[ 1 - 1. service interface 생성 ]
[ + 코드 보기 ]
package org.zerock.myapp.service;
public interface SampleService { // + Target
// ==================================================================================
// + 이 인터페이스를 구현할 클래스가 곧 Target 객체를 생성할 클래스가 된다. (***)
// + -> SampleService 인터페이스가 Target이라는 의미이다.
// + -> SampleService 내의 메소드인 doAdd가 JoinPoint라는 의미이다.
// ==================================================================================
// + JoinPoint
public abstract Integer doAdd(String s1, String s2) throws Exception;
public abstract String joinPoint(String s1, String s2) throws Exception;
} // end interface
[ 1 - 2. service 구현 ]
[ + 코드 보기 ]
package org.zerock.myapp.service;
import org.springframework.stereotype.Service;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
@Service // + Service에는 @Service를 붙여야 한다!
public class SampleServiceImpl implements SampleService { // + Target
@Override
public Integer doAdd(String s1, String s2) throws Exception { // + JoinPoint
log.trace("doAdd({}, {}) invoked.", s1, s2);
return Integer.parseInt(s1) + Integer.parseInt(s2);
// + Integer.parseInt는 int 타입으로 반환한다.
} // doAdd
@Override
public String joinPoint(String s1, String s2) throws Exception {
log.trace("joinPoint({}, {}) invoked.", s1, s2);
return s1 + s2;
} // joinPoint
} // end class
[ 1 - 3. Advice 생성 ]
[ + 코드 보기 ]
package org.zerock.myapp.aop;
import java.util.Arrays;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@NoArgsConstructor
@Log4j2
@Component // + Bean으로 자동 등록
@Aspect // + Aspect를 구현한 것이 Advice (****)
public class LogAdvice {
// ==================================================================================
// + Target(서비스) 객체의 JoinPoint(메소드)의 호출로그(Cross-Concern => Aspect)를 출력하는 Advice 구현
// ==================================================================================
// + 단, Advice의 종류는 "Before Advice"로 구현한다.
// + 즉, Target 객체의 JoinPoint 호출 전에 수행되는 Advice를 구현할 것이다.
// ==================================================================================
// + @Aspect만으로는 자동으로 Bean Container에 Bean으로 등록할 수는 없다.
// + 그렇기 때문에 stereotype 어노테이션이 필요하다. ( ex. @Component )
// ==================================================================================
// ==================================================================================
// + Advice의 종류
// ==================================================================================
// @Before public void before2() {;;}
// @AfterThrowing public void afterThrowing() {;;} : 예외가 발생한 뒤에 동작하는 코드
// @AfterReturning public void afterReturning() {;;} : 모든 실행이 정상적으로 일어났을때 수행
// @After public void after() {;;} : 예외 발생의 유무와 상관없이 수행
// @Around public void around() {;;} : 메소드의 실행 자체를 제어
// ==================================================================================
// + Advice는 value를 통해 advice를 어디에다가 바인딩 할 것인지 PointCut을 지정해줘야 한다.
// ==================================================================================
// + *Service이 Target, *(..)는 JoinPoint를 의미한다.
// + 즉, org.zerock.myapp.service 밑의 모든 Service로 끝나는 파일 중에 모든 메소드를 선택한 것이다.
// + (..)는 매개변수는 자유하는 의미이다.
// + JoinPoint에 Advice를 적용하게 된다.
// ==================================================================================
// + Before Advice : (1) 매개변수가 없다. (2) 리턴타입이 void이다. (3) 메소드명은 자유
// ==================================================================================
// ==================================================================================
// + PointCut Expression을 @Before 어노테이션의 기본속성 값으로 설정해야 한다. (***)
// + 1. 이때 AspctJ 언어가 제공하는 execution() 함수를 사용하여 pointcut expression을 설정
// + 2. pointcut 표현식의 형태 : ( 접근제한자 패키지.클래스.메소드(매개변수) )
// + 3. 위의 2번의 pointcut 표현식을 통해
// + (1) 어느 Target( 서비스 ) 객체에
// + (2) 어느 JoinPoint( 메소드 )에
// + (3) 어느 종류의 Advice( @Before )를 적용시킬지 설정한다.
// ==================================================================================
// ====================================================
// + 1. Target 객체의 JoinPoint가 실행되기 직전에 수행
// ====================================================
@Before(value = "execution( * org.zerock.myapp.service.*Service.*(..) )")
public void before() {
log.info("*****************************");
log.trace("<1> before() invoked.");
log.info("*****************************");
} // before
// ====================================================
// + 1-2. &&args(매개변수 이름)을 통해서 JoinPoint에 전달될 값을 미리 받아볼 수 있다.
// ====================================================
@Before(value = "execution( * org.zerock.myapp.service.SampleService.doAdd(..) ) && args(s1, s2)")
public void before2(String s1, String s2) {
log.info("*****************************");
log.trace("<1-2> before2({}, {}) invoked.", s1, s2);
log.info("*****************************");
} // before2
// ====================================================
// + 2. Target 객체의 JoinPoint가 정상적으로 완료된 후에 수행
// ====================================================
// + @AfterReturning 결과값을 받아 볼 수 있다.
// ====================================================
@AfterReturning(pointcut = "execution( * org.zerock.myapp.service.*Service.*(..) )", returning = "result")
public void afterReturning(Object result) {
log.info("*****************************");
log.trace("<2> afterReturning({}) invoked.", result);
log.info("*****************************");
} // afterReturning
// ====================================================
// + 3. Target 객체의 JoinPoint의 예외가 발생한 후에 수행
// ====================================================
// + Log4j2에서 예외를 모두 StackTrace해버리기에 e가 찍히지는 않는다.
// ====================================================
@AfterThrowing(pointcut = "execution( * org.zerock.myapp.service.*Service.*(..) )", throwing = "e")
public void afterThrowing(Exception e) {
log.info("*****************************");
log.trace("<3> afterThrowing({}) invoked.", e);
log.info("*****************************");
} // afterThrowing
// ====================================================
// + 4. Target 객체의 JoinPoint가 완료된 후에 수행 ( 예외의 유무와 상관 x )
// ====================================================
@After(value = "execution( * org.zerock.myapp.service.*Service.*(..) )")
public void after() {
log.info("*****************************");
log.trace("<4> after() invoked.");
log.info("*****************************");
} // after
// ====================================================
// + 5. 메소드의 실행 자체를 제어
// ====================================================
// + 반드시 매개변수로 ProceedingJoinPoint를 가져야 한다.
// + joinPoint의 타입이 다양할 수 있기에 Object로 해야 한다.
// ====================================================
// + @Around가 pjp로 실행을 시켜야 다른 Advice가 수행가능하다. (***)
// + @Around가 pjp로 실행하지 않으면, 다른 Advice는 수행되지 않는다.
// + @Around가 가장 먼저 시작하게 된다.
// ====================================================
@Around(value = "execution( * org.zerock.myapp.service.*Service.*(..) )")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
log.info("*****************************");
log.trace("<5> around({}) invoked.", pjp);
log.info("*****************************");
log.info("\t + 1. type : {}", pjp.getClass().getName());
log.info("\t + 2. target : {}", pjp.getTarget());
log.info("\t + 3. args : {}", Arrays.toString(pjp.getArgs()));
log.info("\t + 4. JoinPoint(메소드) : {}", pjp.getSignature());
// ==============================================================
// + 1 ) Target의 JoinPoint 실행소요시간 측정하기
// ==============================================================
// ==============================================================
// + 1. 선처리 : 시작했을 때의 시간 구하기
// ==============================================================
// long start = System.currentTimeMillis(); // 1 / 1000 초
long start = System.nanoTime(); // 1 / 10억 초
// ==============================================================
// + 2. Proceeding Target's JoinPoint : 서비스의 메소드 직접 수행 (***)
// ==============================================================
// < ----- BEFORE ADVICE ----- >
Object returnValue = pjp.proceed();
// < ----- AFTER ADVICE ----- >
// ==============================================================
// + 3. 후처리 : 끝났을 때의 시간 구해, 실행소요 시간측정
// ==============================================================
long end = System.nanoTime();
long temp = end - start;
double elapsedTime = temp / Math.pow( 10.0 , 9.0 );
// Math.pow( 10.0 , 9.0 )는 10.0의 아홉제곱이라는 의미이다.
// 나노초를 기준으로 하여, 단위가 1 / 10억 초이기에 다시 10의 9제곱을 나눠줘야 한다.
// 나노초가 초보다 작은 단위이기에 작은 단위에서 큰 단위로 바꾸기 위해서는 나눠야 한다. (**)
// ex. 10억 나노초 / 10억 = 1초
log.info("\t + elapsedTime : {}", elapsedTime);
return returnValue;
} // around
} // end class
[ 1 - 4. test하기 ]
[ + 코드 보기 ]
package org.zerock.myapp.aop;
import java.util.Objects;
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.service.SampleService;
import lombok.NoArgsConstructor;
import lombok.Setter;
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 LogAdviceTests {
@Setter(onMethod_= {@Autowired})
private SampleService service;
// =======================================================
// + 1. Service 객체 확인
// =======================================================
@BeforeAll
void beforeAll() {
log.trace("\t + beforeAll() invoked.");
Objects.requireNonNull(this.service);
log.info("\t + 1. service : {}", this.service);
// + 1. service : org.zerock.myapp.service.SampleServiceImpl@f000000
log.info("\t + 2. ****** type(proxy) ****** : {}", this.service.getClass().getName());
// + 2. ****** type(proxy) ****** : com.sun.proxy.$Proxy43 ( proxy 객체가 들어온다. )
} // beforeAll
// =======================================================
// + 2. Advice가 발생하는지 확인
// =======================================================
@Test
@Order(1)
@DisplayName("1. testDoAdd")
@Timeout(value = 3, unit = TimeUnit.SECONDS)
void testDoAdd() throws Exception {
log.trace("\t + testDoAdd() invoked.");
int result = this.service.doAdd("1000", "200");
//int result = this.service.doAdd("1000", "이백이다아아앙"); // + afterThrowing 확인
log.info("\t + result : {}", result);
} // testDoAdd
// =======================================================
// + 3. @Around가 발생하는지 확인
// =======================================================
@Test
@Order(2)
@DisplayName("2. testjoinPoint")
@Timeout(value = 3, unit = TimeUnit.SECONDS)
void testjoinPoint() throws Exception {
log.trace("\t + testjoinPoint() invoked.");
String result = this.service.joinPoint("이츠미", "마리오오오오");
log.info("\t + result : {}", result);
} // testjoinPoint
} // end class
[ 2. 트랜잭션 - @Transactional ] (***미완성****)
[ 2 - 1. Mapper Interface 생성 및 sql문 작성 ]
[ + Mapper 1 ]
[ + 코드 보기 ]
package org.zerock.myapp.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.zerock.myapp.exception.DAOException;
public interface Sample1Mapper {
// ==========================================
// 1. tbl_sample 테이블에 insert 수행 메소드
// ==========================================
@Insert("INSERT INTO tbl_sample1 (col) VALUES ( #{col} )")
public abstract int insertCol( @Param("col") String data ) throws DAOException;
} // end interface
[ + Mapper 2 ]
[ + 코드 보기 ]
package org.zerock.myapp.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.zerock.myapp.exception.DAOException;
public interface Sample2Mapper {
// ==========================================
// 2. tbl_sample 테이블에 insert 수행 메소드
// ==========================================
@Insert("INSERT INTO tbl_sample2 (col) VALUES ( #{col} )")
public abstract int insertCol( @Param("col") String data ) throws DAOException;
} // end interface
[ 2 - 2. Service Interface 생성 ]
[ + 코드 보기 ]
package org.zerock.myapp.service;
import org.zerock.myapp.exception.ServiceException;
public interface SampleTXService {
public abstract void addData(String data) throws ServiceException;
} // end interface
[ 2 - 3. Service 구현 ]
[ + 코드 보기 ]
package org.zerock.myapp.service;
import java.util.Objects;
import org.springframework.beans.factory.annotation.Autowired;
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(Exception e) {
log.info("\t + Transfer done Failure....");
throw new ServiceException (e);
} // try - catch
} // addData
// ============================================
} // end class
[ 2 - 4. root-context.xml 파일에 tx(트랜잭션) 관련 설정 추가 ]
[ + Namespaces에서 tx 열기 ]
[ + root-context.xml 파일에 트랜잭션 설정 추가 ]
[ + 코드 보기 ]
<!-- spring AOP를 위한 설정으로 아래 2개 추가 -->
<context:annotation-config/>
<aop:aspectj-autoproxy />
<!-- Spring 트랜잭션 관리자를 이용한 분산 트랜잭션을 사용하겠다는 설정 추가 -->
<tx:annotation-driven />
[ 2 - 5. root-context.xml 파일에 tx 관리자 관련 설정 추가 ]
[ + 코드 보기 ]
<!-- spring AOP를 위한 설정으로 아래 2개 추가 -->
<context:annotation-config/>
<aop:aspectj-autoproxy />
<!-- Spring-jdbc에서 제공하는 트랜잭션 관리자를 bean으로 우선 등록 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
<!-- Spring-tx 트랜잭션 관리자를 이용한 분산 트랜잭션을 사용하겠다는 설정 추가 -->
<tx:annotation-driven />
[ 2 - 6. 2PC를 지원해주는 히카리CP로 데이터 소스 변경 ]
[ 2 - 7. 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 = "123456789444444401234567890123456789012345678901234567890555555";
this.service.addData(data);
log.info("\t + ALL Insert Success!!");
} // testTransfer
} // end class
'KH 정보교육원 [ Java ]' 카테고리의 다른 글
KH 129일차 - Spring ( Open API / Dom4j / jaxen ) (*****) (0) | 2022.08.31 |
---|---|
KH 128일차 - Spring ( 트랜잭션 / 패스워드 암호화 / open API ) (******) (0) | 2022.08.30 |
KH 126일차 - Spring ( AOP ) (***) (0) | 2022.08.26 |
KH 125일차 - Spring( 인터셉터 3 - 세션 트레킹 : 자동 로그인 ) (******) (0) | 2022.08.25 |
KH 124일차 - Spring ( 인터셉터 - 세션 트레킹 : 로그인 / 로그아웃 / 인증 / 인가 / 자동 로그인 기능 ) (*********) (0) | 2022.08.24 |