트랜잭션 어노테이션과 포인트컷

트랜잭션?

하나의 작업으로 취급하기 위해 여러 작업을 하나로 묶은 것

포인트컷?

일종의 필터 역할로, 어떤 메소드에 어드바이스를 적용할지 결정하는 기준을 정의하는 역할을 한다.

어드바이스?

어드바이스는 핵심 비즈니스 로직에 적용되는 부가 기능 사항을 정의하여 등록한 빈 객체

@Trasactional

포인트컷과 트랜잭션 속성을 이용해 트랜잭션을 일괄적으로 적용하는 방식은 세밀하게 튜닝된 트랜잭션 속성의 경우 포인트컷과 트랜잭션 속성 사용으로는 적합하지 않다.

  • 설정 파일에서 속성을 부여하는 대신,
    직접 타킷에 속성정보를 가진 어노테이션을 지정해 세밀하게 트랜잭션 속성을 적용할 수 있다.
  • @Transactional 타겟은 메소드와 타입
  • 메소드, 클래스, 인터페이스에 사용할 수 있다.
  • @Transactional이 부여된 모든 오브젝트를 자동으로 타겟 객체로 인식한다.
  • TransactionAttributeSourcePointcut을 사용하여 스스로 표현식과 같은 선정 기준을 갖지 않게 할 수 있다.
  • @Transactional이 부여된 메소드 및 객체를 모두 찾아 포인트컷 선정 결과로 반환한다.
  • 포인트컷을 통과시켜 트랜잭션 속성을 이용해 선정 메소드 및 타입을 필터링

트랜잭션 속성과 포인트컷

  • 메소드 이름 패턴을 통해 부여되는 일괄적인 트랜잭션 속성정보 대신 @Transaction 엘리먼트에서 트랜잭션 속성을 가져오는AnnotationTransactionAttributeSource를 사용할 수 있다.
  • @Transactional을 사용하면 메소드 및 타입마다 다르게 설정할 수 있어 매우 유연한 트랜잭션 속성 설정을 할 수 있으며 포인트컷과 트랜잭션 속성을 어노테이션 하나로 지정할 수 있다.
  • 메소드마다 @Transaction을 넣어 유연한 속성 제어는 가능하겠지만, 코드가 지저분해지고 동일한 속성 정보를 가진 애노테이션을 반복적으로 사용하게 된다.

4 단계 대체정책

  • 타겟 메소드 (Target Method):
    @Transactional 어노테이션을 사용하여 트랜잭션을 적용할 메소드를 지정한다.
    이 메소드에서 예외가 발생하면 예외 처리와 롤백을 관리할 수 있다.

  • 타겟 클래스 (Target Class):
    @Transactional 어노테이션을 사용하여 트랜잭션을 적용하는 대상 클래스를 지정한다.
    트랜잭션을 관리하고자 하는 비즈니스 로직이 포함된 클래스를 의미한다.

  • 선언 메소드 (Advisor Method):
    @Transactional 어노테이션의 fallback 옵션에서 지정한 메소드를 호출하는 역할을 수행한다.
    예외가 발생한 경우에 호출되며, 해당 메소드가 적절한 처리를 수행할 수 있도록 한다.

  • 선언 타입 (Advisor Type):
    선언 메소드가 속한 클래스의 타입을 지정한다.
    @Aspect 어노테이션을 사용하여 Aspect 클래스를 생성하고, 해당 클래스 내에 선언 메소드를 정의하여 예외 상황에 대한 처리를 구현한다.

	[1] // 선언 타입
public interface Service { 
	[2] // 선언 메소드
	void method1(); 
	[3]
	void method2();
}

	[4] // 타겟 클래스
public class Servicelmpl implements Service {
	[5] // 타겟 메소드
	public void method1() (
	[6]
	public void method2() {
}
  • 타깃 클래스, 선언 타입에만 @Transactional을 붙이면 해당 클래스 및 타입에 있는 메소드는 공통적으로 클래스 및 선언 레벨에 있는 @Transactional 엘리먼트를 공유한다.
  • 인터페이스에 @Transactional 을 두면 구현 클래스가 바뀌더라도 트랜잭션 속성을 유지할 수 있다는 장점이 있다.
  • 프록시 방식이 아닌 트랜잭션을 적용하면 인터페이스의 @Transactional은 무시되기 때문에, 타깃 클래스에 @Transactional을 넣는 방식을 권장한다.

@Transactional 적용

  • @Transactional 사용은 포인트컷과 트랜잭션 속성 지정하는 것보다 단순하고 직관적이기 때문에 @Transactional을 주로 사용한다.
  • @Transactional을 사용하지 않아도 컴파일에 아무런 문제가 없고, 롤백이 발생하고서야 빼 먹은 사실을 알 수 있다.
  • 일부 데이터 접근 기술은 트랜잭션이 시작되지 않으면 DAO에서 예외가 발생하나 JDBC의 경우 트랜잭션 없이도 DAO를 동작시킬 수 있다.
@Transactional
public interface UserService {
    void add(User user);
    void deleteAll();
    void update(User user1);
    void upgradeLevels();
    // 메소드 레벨에 @Trasactional이 없으므로 타입레벨에 부여된 디폴트 속성이 부여된다.

    @Transactional(readOnly = true)
    User get(String id);

    @Transactional(readOnly = true)
    List<User> getAll();
    // 메소드 레벨에 부여됐기 때문에 메소드 마다 반복된다.
}

트랜잭션 지원 테스트

선언적 트랜잭션과 트랜잭션 전파 속성

트랜잭션 전파속성?

  • 트랜잭션 전파 속성은 여러 트랜잭션 메소드들이 서로 상호작용할 때 어떻게 동작할지를 정의하는 설정
  • 트랜잭션 메소드에서 다른 트랜잭션 메소드를 호출할 때, 어떤 방식으로 트랜잭션이 전파되고 관리될지 결정

REQUIRED로 전파속성 지정

  • 현재 실행 중인 트랜잭션이 없을 경우, 새로운 트랜잭션을 시작
  • 현재 실행 중인 트랜잭션이 있을 경우, 해당 트랜잭션에 참여하여 작업을 수행

add 메소드에 트랜잭션 전파속성을 지정

  • 다른 메소드에서 만들어진 트랜잭션의 경계에 포함되게 한다.
  • 덕분에 불필요한 중복 (add()메소드가 하나만 존재하면 됨)을 피할 수 있다.

선언적 트랜잭션 : AOP를 이용해 코드 외부에서 트랜잭션 기능을 부여해주는 방법
프로그램에 의한 트랜잭션 : TransactionTemplate나 개별 데이터 트랜잭션 API를 사용해 직접 코드 안에서 사용하는 방법

특수한 상황을 제외하고, 선언적 트랜잭션 방법을 사용하는 것을 추천함

트랜잭션 동기화

  • AOP 덕에 트랜잭션 부가기능을 간단히 애플리케이션 전반에 적용할 수 있다.
  • 트랜잭션 매니저를 (PlatformTransactionManager 인터페이스를 구현) 통해 구체적인 트랜잭션 기술 종류에 상관없이 일관된 트랜잭션 제어를 할 수 있다.
  • TransactionSynchronization을 사용하는 트랜잭션 동기화는 같은 DB Connection을 공유하게 한다.

트랜잭션 동기화 기술은 같은 DB Connection을 공유하는 특성을 이용하여 트랜잭션 전파 속성에 따라 참여할 수 있도록 한다.

트랜잭션 매니저를 이용한 트랜잭션 제어

트랜잭션 매니저를 사용해 테스트 데이터를 조작하는 등의 작업을 수행할 수 있다.
트랜잭션 매니저를 사용하면 테스트에서 트랜잭션의 시작, 커밋, 롤백 등을 조작하여 테스트 환경을 조정할 수 있다.

public class UserServiceTest {
    ...
    @Test
    public void transactionSync(){
        DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition();
        TransactionStatus txStatus = transactionManager.getTransaction(txDefinition);

        userService.deleteAll();
        userService.add(users.get(0));
        userService.add(users.get(1));

        transactionManager.commit(txStatus);
    }
}

실행되는 트랜잭션은 3개

  • 각 메소드마다 트랜잭션이 독립적으로 작용함
  • 트랜잭션 매니저를 사용하여 세 개의 메소드를 하나로 묶을 수 있다.
  • 기존의 트랜잭션이 없으니 새로운 트랜잭션을 실행하고 트랜잭션 정보를 반환한다.
  • 만들어진 트랜잭션을 다른 곳에서도 사용할 수 있게 동기화 한다.

트랜잭션 동기화 검증

스프링 트랜잭션 추상화가 제공하는 트랜잭션 동기화 기술과 트랜잭션 전파 속성 덕에 테스트도 트랜잭션으로 묶을 수 있다.
이를 통해 효과적인 테스트를 만들 수 있다.

public class UserServiceTest {
    ...
    @Test
    public void transactionSync(){
        userDao.deleteAll(); // 초기화
        assertThat(userDao.getCount(), is(0));

        DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition();
        TransactionStatus txStatus = transactionManager.getTransaction(txDefinition);

        userService.add(users.get(0));
        userService.add(users.get(1));
        assertThat(userDao.getCount(), is(2));

        transactionManager.rollback(txStatus);

        assertThat(userDao.getCount(), is(0));
    }
}

롤백이 잘 되는 지 테스트 할 수 있다.

  • 테스트 코드에서 트랜잭션을 시작해 놓으면 직접 호출하는 DAO 메소드도 하나의 트랜잭션으로 묶을 수 있다.
  • 트랜잭션 결과나 상태를 조작하면서 테스트하는 것도 가능하다.
  • 트랜잭션 속성에 따라 여러 메소드를 조합해 어떤 결과가 나오는 지도 미리 검증 가능하다.

롤백 테스트

테스트가 끝나면 롤백 하는 테스트

  • 복잡한 데이터를 바탕으로 동작하는 통합 테스트에서는 DB의 상태가 중요하다.
  • 테스트 수행으로 데이터 추가, 삭제 등이 발생 해 다른 테스트에 영향을 줄 수 있다.
  • 테스트 수행 시 마지막에 롤백을 해서 테스트 실행 전 상태로 되돌릴 수 있다.
public class UserServiceTest {
    ...
    @Test
    public void transactionSync() {
        DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition();
        TransactionStatus txStatus = transactionManager.getTransaction(txDefinition);
        try {
            userService.deleteAll();
            userService.add(users.get(0));
            userService.add(users.get(1));
        } finally {
            transactionManager.rollback(txStatus);
        }
    }
}
  • 롤백테스트는 테스트를 진행하며 조작한 데이터를 모두 롤백 해준다.
  • 여러 개발자가 하나의 DB를 공유할 수 있게 해준다.
  • 적절한 격리 수준만 보장하면 동시에 여러 개의 통합 테스트를 진행할 수 있다.

테스트에서 사용하는 트랜잭션 어노테이션

스프링은 어노테이션을 통해 테스트를 편리하게 만들 수 있는 여러 기능을 제공한다.

@Transactional

  • @Transactional 을 사용해 트랜잭션 경계설정을 자동으로 설정 할 수 있다.
    ( 디폴트는 REQUIRED )
  • 테스트에서 진행하는 작업을 하나의 트랜잭션으로 묶어 줄 수 있다.

@Rollback

  • @Transactional 을 사용하는 애플리케이션 클래스와 테스트 클래스의 디폴트 속성은 동일
  • 테스트 클래스는 테스트 수행 후 자동으로 롤백된다.
  • 테스트 수행 후 롤백을 원하지 않을 때 @Rollback 사용할 수 있다.
    ( @Rollback의 기본값은 true이기 때문에 false로 선언해야 롤백 되지 않는다. )

@TransactionConfiguration

  • @Rollback은 메소드 레벨에서만 사용 가능하다.
  • 클래스 레벨에서 적용하기 위해서는 @TransactionConfiguration을 사용해야 한다.
  • @Rollback과 같이 false로 선언해야 롤백되지 않는다.
  • @TransactionConfiguration을 사용한 클래스에서, 특정 메소드는 롤백하고 싶다면 해당 메소드에만 @Rollback 사용하면 된다.
    ( 클래스보다 메소드가 우선순위가 더 높다. )
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/applicationContext.xml")
@Transactional
@TransactionConfiguration(defaultRollback = false)
public class UserServiceTest {
    @Test
    @Rollback //롤백
    public void add(){}
}

6.8.10 @NotTransactional과 Propagation.NEVER

  • @Transactional이 있는 클래스에서 특정 메소드를 트랜잭션 동작하고 싶지 않을 때 사용
@Transactional(propagation = Propagation.NEVER)

효과적인 DB 테스트

  • 테스트 내에서 트랜잭션을 제어할 수 있는 네 가지 애노테이션을 활용해 편리하게 통합 테스트 코드를 만들 수 있다.
  • DB가 사용되는 통합 테스트는 롤백 테스트로 만드는 것을 권장 한다.
profile
hello human

0개의 댓글

Powered by GraphCDN, the GraphQL CDN