[50일차]트랜잭션,애너테이션,AOP,JTA

유태형·2022년 7월 9일
0

코드스테이츠

목록 보기
50/77

오늘의 목표

  1. 트랜잭션
  2. 에너테이션 트랜잭션
  3. AOP 트랜잭션



내용

트랜잭션

트랜잭션이란 여러개의 작업들을 하나로 뭉쳐서 하나의 단위로 생각하는 것입니다.



ACID

원자성(Atomicity)

하나의 작업단위 안의 여러 작업들을 모두 성공하든가 모두 실패하든가(All or Nothing)중 둘중 하나만 처리되는것이 보장 되어야 합니다.

일관성(Consistency)

비즈니스 로직에서 의도하는 대로 데이터 접근 계층을 거쳐 데이터베이스에 저장되어야 합니다. 즉, 비즈니스 로직에서 의도한 값이 그대로 저장되어야되며 중간에 의도치 않은 값들로 바뀌어 저장되면 안됩니다.

격리성(Isolation)

여러개의 트랜잭션이 실행될 경우 각각의 트랜잭션은 독립적으로 실행 되어야 합니다. 하나의 트랜잭션이 다른 트랜잭션에 영향을 주어서는 안됩니다.

지속성(Durability)

트랜잭션이 완료되면 그 결과는 애플리케이션이 종료되어도 영구적으로 유지되어야 합니다.



커밋과 롤백

커밋(Commit)

  • 수행해온 작업들을 데이터베이스에 반영하는 명령어 입니다. 실행시 데이터베이스에 영구 적으로 저장됩니다.
  • commit을 생략하면 데이터베이스에는 최종적으로 반영되지 않으며, commit시 하나의 트랜잭션이 종료됩니다.

롤백(Rollback)

  • 롤백은 작업중 문제가 발생했을 때 수행해온 모든 작업들을 취소 합니다.
  • 취소시 현 시점에서 가장 가까운 commit지점이나 시작 지점으로 되돌아 갑니다.
@Configuration
public class 설정정보클래스{
	private EntityManager
    private EntityTransaction tx;
    
    @Bean
    public CommandLineRunner jpaRunner(EntityManagerFactory emFactory){
    	this.em = emFactory.createEntityManager();
        this.tx = em.getTransaction();
        
        return args -> {
        	tx.begin();
            //트랜잭션
            tx.commit();
        };
    }
}

JPA에서도 트랜잭션과 커밋, 롤백을 사용 했었습니다. EntityTransaction객체를 전달 받아 begin() ~ commit()까지 하나의 트랜잭션으로 묶을 수 있었습니다. 또 commit()에서 최종 변경 사항을 데이터 베이스에 반영할 수도 있었습니다.




애너테이션 트랜잭션

Spring에서 가장 간단하게 트랜잭션을 적용할 수 있는 방법은 @Transactional에너테이션을 사용 하는 것입니다.



클래스레벨

@Service
@Transactional
public class 서비스{
	...
}

@Transsactional 에너테이션을 클래스 레벨에 추가하면 기본적으로 해당 클래스의 모든 메서드에 트랜잭션이 적용됩니다.

applicatoin.yml에 설정을 추가하면 트랜잭션이 어떻게 작동하고 있는지 로그를 출력할 수 있습니다.

logging:
	level:
    	org:
        	springframework:
            	orm:
                	jpa: DEBUG

로그 레벨을 DEBUG로 설정하면 JPA내부에서 DEBUG 로그 레벨을 지정한 부분의 로그를 확인할 수 있습니다.

주의하실 점은 logging 태그는 최상단 레벨이어야 합니다.(탭 갯수 0개)

Creating new Tranaction : 트랜잭션이 생성되는 로그입니다.
Committing JPA transaction on EntityManager : 커밋이 일어나고 있습니다.
after transaction : 트랜잭션이 종료 되었음을 알립니다.
Closing JPA EntityManager : 트랜잭션을 전달해주었던 EntityManager가 종료되었음을 출력합니다.

@Service
@Transactional
public class 서비스{
	...
    
    public 엔티티 crate엔티티(엔티티 객체){
    	엔티티 result = 레포지토리.save(객체);
        if(true) throw new RuntimeException("rollback");
        return result;
    }
    
    ...
}

@Transactional애너테이션을 클래스레벨에서 정의하고 해당 클래스의 메서드에서 RuntimeException가 발생하고 있습니다.

로그 확인 시
Initiating transaction rollback, olling back JPA transaction on EntityManager, java.lang.RuntimeException: rollback으로 트랜잭션내 예외 발생시 롤백을 수행하고 있음을 확인할 수 있습니다.

체크예외 (checked exception)

트랜잭션을 짜고 예외를 가정하다보면 특정 예외만 다르게 회피하거나 캐치해서 따로 처리 해야할 수도 있습니다. 이러한 특별한 예외들을 체크예외라고 합니다.

Transactional에너테이션만 추가해서는 rollback이 되지 않습니다. 별도의 예외를 두고 싶다면@Transactional(rollbackFor = {예외1.class, 예외2.class, ...}) 와 같이 rollbackFor 애트리뷰트를 활용하여 체크 예외들을 지정할 수 있습니다.



메서드레벨

@Service
@Transactional
public class 서비스{
	...
    
    @Transactional(readOnly = true)
    public 엔티티 find엔티티(long Id){
    	return findVerified엔티티(Id);
    }
}

@Transactional에너테이션의 readOnly = true에트리뷰트는 읽기전용으로 트랜잭션을 실행할 수 있도록 합니다.

만약 클래스레벨의 @Transactional에너테이션은 읽고,쓰기모드로 트랜잭션을 진행하고 메서드레벨의 @Transactional은 읽기전용으로 트랜잭션으로 실행하면 읽기 전용으로 트랜잭션이 실행됩니다.

우선순위 : 클래스레벨 < 메서드레벨

readOnly = true에트리 뷰튼는 주로 조회시 따로 수정하거나 추가하는 작업등이 필요 없으므로 영속성 컨텍스트를 flush()할 필요가 없습니다. 즉 readOnly 애트리뷰트를 이용해 flush()를 생략 함으로써 성능적은 향상을 기대할 수 있습니다.



여러 트랜잭션

트랜잭션이 하나가 아니라 여러개일 때 여러가지 상황을 가정할 수 있습니다.

하나가 아니라 여러개의 트랜잭션을 처리해야하기 때문에 트랜잭션 전파(Transaction Propagation)을 고려해야합니다.

트랜잭션 전파란 트랜잭션의 경계에서 진행 중인 트랜잭션이 존재할 때 또는 존재하지 않을 때, 어떻게 동작 할 것인지 결정합니다.

하나만 있다면 트랜잭션의 경계에서 다른 트랜잭션을 신경 쓸 필요가 없지만 트랜잭션이 0개 이상 더 있을 수 있다면 경계에서 상황에 따라 다르게 대처해야 할 것입니다.

Propagation설명
Propagation.REQUIRED진행 중인 트랜잭션이 있으면 합치고, 없으면 새로운 트랜잭션을 생성합니다.(default)
Propagation.REQUIRES_NEW이미 진행 중인 트랜잭션이 있으면 중지 시키고 트랜잭션을 만들어 실행 종료 후 중지시킨 트랜잭션을 다시 실행합니다.
Propagation.MANDATORY진행 중인 트랜잭션이 있으면 합치고, 없으면 에러를 발생시킵니다.
Propagation.NOT_SUPPORTED진행 중인 트랜잭션이 있으면 중지 시키고, 트랜잭션을 사용하지 않고 메서드를 실행합니다.
Propagation.NEVER트랜잭션을 사용하지 않습니다. 진행중인 트랜잭션이 있으면 에러를 발생시킵니다.

트랜잭션은 독립적으로 실행되어야 격리성이 보장되지만 여러 트랜잭션이 실행 될 경우 격리성을 조정할 수 있도록 @Transactional에너테이션에 isloation에트리뷰트가 존재합니다.

isolation설명
Isolation.DEFAULT데이터베이스 기본 값
Isolation.READ_UNCOMMITTED다른 트랜잭션에서 커밋하지 않은 데이터를 읽는것을 허용합니다.
Isolation.READ_COMMITTED다른 트랜잭션에 커밋된 데이터를 읽는 것을 허용합니다.
Isolation.REPEATEABLE_READ트랜잭션 내에서 한번 조회한 데이터를 반복해서 조회해도 같은 데이터가 조회되도록 합니다.
Isolation.SERIALIZABLE동일한 데이터에 대해서 동시에 두개 이상의 트랜잭션이 수행되지 못하도록 합니다.



AOP 방식 트랜잭션

@Transactional에너테이션으로 간편하게 Spring에서 트랜잭션을 적용할 수 있지만, 비즈니스 로직에 적용하지 않고 AOP를 이용하여 트랜잭션을 적용할 수 있습니다.

@Configuration
public class 설정정보클래스{
	private final TransactionManager transactionManager;
    
    public 설정정보클래스(TransactionManager transactionManager){
    	this.transactionManager = transactionManager;
    }
    
	@Bean
    public TransactionInterceptor toAdvice(){
    	NameMatchTransactionAttributeSource txAttributeSource =
        	new NameMatchTransactionAttributeSource();
            
        RuleBasedTransactionAttribute txAttribute =
        	new RuleBasedTransactionAttribute();
       	txAttribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        
        RuleBasedTransactionAttribute txFindAttribute =
        	new RuleBasedTransactionAttribute();
        txFindAttribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        txAttribute.setReadOnly(true);
        
        Map<String,TransactionAttribute> txMethods = new HashMap<>();
        txMehtods.put("find",txFindAttribute);
        txMethods.put("*",txAttribute);
        
        txAttributeSource.setNameMap(txMethods);
        
        return new TransactionInterceptor(transactionManager,txAttributeSource);
    }
    
    @Bean
    public Advisor txAdvisor(){
    	AspectJExpressionPointcut pointcut = new AspectJExpressionPoitncut();
        pointcut.setExpression("execution(* com.codestates.coffee.service." + "CoffeeService.*(..))");
        
        return new DefaultPointcutAdvisor(poiuntcut,txAdvice());
    }
}

AOP에서 는 3가지 구성으로 이루어져 있습니다.

  • 어드바이저 : 클래스이며, 어드바이스포인트컷을 포함하고 있습니다.
  • 포인트컷 : 비즈니스 로직의 어느 메서드, 언제 실행될지를 정보를 가집니다.
  • 아드바이스 : 포인트컷에서 실행될 공통관심사 입니다.

TransactionInterceptor 구현체인 어드바이스 + AspectJExpressionPointcut 포인트컷 = txAdvisor()라고 생각하시면 될것 같습니다.

세세하게는

NameMatchTransactionAttributeSource : 이름 패턴으로 메서드를 트랜잭션을 적용할 메서드를 구분합니다.
RuleBasedTransactionAttribute : 역할을 기준으로 트랜잭션을 수행합니다.
TransactionDefinition.PROPAGATION_REQUIRED : @TransactionalPropagation.REQUIRED 애트리뷰트와 동일합니다.

작은 단위부터 보면

  1. RuleBasedTransactionAttributeTransactionDefinition.PROPAGATION_REQUIRED를 넣고
  2. Map메서드 이름패턴RuleBasedTransactionAttribute를 넣고
  3. NameMatchTransactionAttributeSourceMap을 넣고
  4. TransactionInterceptorTransactionManager, NameMatchTransactionAttributeSource를 넣고
  5. DefaultPointcutAdvisorAspectJExpressionPointcut, TransactionInterceptor를 넣어 AOP 어드바이저를 만듭니다.



후기

에너테이션으 쉬우나 새로운 클래스를 정의하여 AOP로 사용하는 것은 어려웠습니다. 또 JTA를 진행하였는데 새로운 데이터베이스르 만드는것은 더더욱 어려웠습니다. 더 열심히 달려야 겠습니다.




Github

private!

profile
오늘도 내일도 화이팅!

0개의 댓글