티스토리 뷰

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

 

 

 

728x90
댓글
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
최근에 올라온 글
Total
Today
Yesterday
공지사항