토비의 스프링

2 minute read

[6장] AOP

트랜잭션의 경계는 비즈니스 로직 전후에 설정되어야할 것 같은데 서비스 메서드에 이렇게 길고 지저분한 코드를 두고 싶지 않다.
트랜잭션 경계설정 코드와 비즈니스 로직 코드 간에 서로 주고받는 정보가 없어서 분리될 수 있을 것 같다.

private void updateTransaction() {
    user.update();
}

트랜잭션 정의

트랜잭션 전파

트랜잭션의 경계에서 이미 진행 중인 트랜잭션이 있을 때 어떻게 동작할지 결정하는 방식

  • PROPAGATION_REQUIRED
    진행 중인 트랜잭션이 없으면 새로 시작하고 있으면 합류(DefaultTransactionDefinition의 전파 속성)
  • PROPAGATION_REQUIRES_NEW
    항상 새로운 트랜잭션을 시작
  • PROPAGATION_NOT_SUPPORTED
    트랜잭션이 있든 없든 무시한다. 특정 메서드만 트랜잭션에서 제외하고 싶을 때 사용할 수 있다.
    getTransaction에서 전파속성 결정할 수 있다.

트랜잭션 격리수준

가능한 많은 트랜잭션을 동시에 진행하면서도 문제가 발생하지 않도록 하는 것
DB에 설정되어 있지만 JDBC 드라이버나 DataSource에서 재설정할 수 있다.
DefaultTransactionDefinition은 ISOLATION_DEFAULT로 DataSource에 설정된 디폴트 격리수준을 따른다는 것이다.

트랜잭션 제한시간

트랜잭션 수행 시간을 설정하는 것
DefaultTransactionDefinition은 제한시간이 없는 것이다.

읽기전용 트랜잭션

읽기 전용으로 설정하면 데이터 조작을 막을 수 있다.
성능 향상도 될 수 있다.

위 네가지 속성은 TransactionAdvice를 이용해 설정할 수 있다.
바꿔주고 싶으면 DefaultTransactionDefinition가 아닌 TrasactionDefinition의 빈을 정의해 바꿀 수 있다.

원하는 메서드만 다른 정의를 적용하려면? TransactionInterceptor를 사용할 수 있다.

TransactionInterceptor는 PlatformTransactionManager와 트랜잭션 속성을 정의한 프로퍼티인 transactionAttributes를 가진다.
아래와 같이 정의해서 특정 메서드에 적용할 수 있다.

  • 메서드 이름 패턴을 이용한 속성 지정
    ```java
PROPAGATION_REQUIRED,readOnly,timeout_30 PROPAGATION_REQUIRES_NEW,ISOLATION_SERIALIZABLE PROPAGATION_REQUIRED

- tx 네임스페이스를 이용한 설정  
*리스트 6-72 tx스키마의 전용 태그*  

## 트랜잭션 속성 적용  
트랜잭션 경계설정을 특정 계층의 경계와 일치시키는 것이 좋다.  
비즈니스 로직을 담고 있는 서비스 계층이 적절하다.  
DAO가 서비스를 거쳐야만 한다.  

## 트랜잭션 어노테이션  
`@Transactional`의 정의 코드는 아래와 같다.  
```java
@Target({ElemetType.METHOD, ElementType.TYPE})  //메서드와 타입(클래스, 인터페이스)에 어노테이션을 적용할 수 있다.
@Retention(RetentionPolicy.RUNTIME) //런타임 동안 어노테이션 정보가 유지된다.
@Inherited  //상속을 통해서도 얻을 수 있다.
@Documented
public @interface Transactional {
    //직접 지정할 수도 있고 입력하지 않고 디폴트 값을 사용할 수 도 있다.
    @AliasFor("transactionManager")
	String value() default "";
	@AliasFor("value")
	String transactionManager() default "";
	String[] label() default {};
	Propagation propagation() default Propagation.REQUIRED;
	Isolation isolation() default Isolation.DEFAULT;
	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
	String timeoutString() default "";
	boolean readOnly() default false;
	Class<? extends Throwable>[] rollbackFor() default {};
	String[] rollbackForClassName() default {};
	Class<? extends Throwable>[] noRollbackFor() default {};
	String[] noRollbackForClassName() default {};
}

메서드 -> 클래스 -> 선언 메서드 -> 선언 타입 순으로 어노테이션 적요을 확인하고 가장 먼저 발견되는 속성정보를 사용하게 한다.
어노테이션을 사용하면 아주 간단하게 원하는 곳에 적용할 수 있다.
하지만 빼먹기 쉽다.
6-87 @Transactional 어노테이션을 이용한 속성 부여에 직접 등록하는 것과 비교된 것을 볼 수 있다.

외부에서 트랜잭션 기능을 부여하고 속성 지정해주는게 선언적 트랜잭션, 직접 코드 안에서 사용하는게 프로그램에 의한 트랜잭션

트랜잭션 동기화와 테스트

AOP로 트랜잭션의 기능을 어플리케이션 전반에 적용할 수 있었던 것처럼 트랜잭션 추상화도 중요한 기술 중 하나이다.

  • 트랜잭션 매니저와 동기화
    트랜잭션 어드바이스에서도 매니저를 통해 트랜잭션을 제어하는 것이므로 TransactionManager의 빈 주입을 이용해 트랜잭션에 참여하는 것이 안될 이유가 없다.
    하지만 코드에는 선언적이 훨씬 쉬우므로 적용할 이유가 없지만 테스트이다.

트랜잭션 전파는 트랜잭션 매니저를 통해 트랜잭션 동기화 방식이 적용되기에 가능했다. 따라서 테스트에서도 트랜잭션 매니저로 시작시키고 동기화할 수 있다.

테스트에서 트랜잭션을 조작할 수 있다는 것은 JPA에서 준영속성 엔티티 동작을 확인하기도 좋고 여러 DAO 메서드를 하나로 묶기도 좋다.
롤백, 커밋 모두 설정 가능해 테스트가 용이하다.
롤백테스트는 DB에 영향을 주지 않아 장점이 많다.

  • 테스트를 위한 트랜잭션 어노테이션
    테스트 클래스나 메서드에 @Transactional을 붙이면 자동으로 트랜잭션 경계가 설정된다.
    이는 트랜잭션을 직접 만들어 적용하는 것과 동일한 효과를 낸다.
    AOP를 위한 것은 아니고 메서드 실행 전에 새로운 트랜잭션 만들고 종료되면 종료하는 역할을 한다.
    중요한 점은 테스트용 트랜잭션은 테스트가 끝나면 자동 롤백되는 것이다. 기본적으로 테스트에 적용된 @Transactional은 강제 롤백시키도록 지정되어 있다.

강제 롤백을 원하지 않으면 @Rollback(false)을 이용하면 된다.
롤백 어노테이션은 메서드 레벨에만 적용되므로 모든 메서드에 적용하고 싶으면 @TransactionConfiguration(defaultRollback=false)을 이용할 수 있다.

트랜잭션을 원치 않는 테스트에는 @NotTransactionl이나 @Transactional(propagation=Propagation.NEVER)로 지정할 수 있는데, 이럴 바에는 따로 클래스를 구성하는 것이 낫다. 심지어 낫트랜잭셔널 어노테이션은 사라졌다.