본문 바로가기
카테고리 없음

[스터디] 트랜잭션 AOP

by foreverever 2022. 8. 15.
반응형

1) @Transactional 이용한 트랜잭션 처리

- 트랜잭션 범위에서 실행하고 싶은 메서드에 @Transactional 애노테이션만 붙이면 된다.

@Transactional
public void changePassword(String email, String oldPwd, String newPwd) {
   Member member = memberDao.selectByEmail(email);
   if (member == null)
      throw new MemberNotFoundException();

   member.changePassword(oldPwd, newPwd);

   memberDao.update(member);
}

이때 @Transactional 애노테이션을 제대로 동작시키고 싶으면 다음 두가지 스프링 설정이 필요하다.

1) PlatformTransactionManager 빈 설정

2) @Transactional  애노테이션 활성화 설정

@Configuration
@EnableTransactionManagement	//2)
static class InternalCallV1Config {
    @Bean
    UserService userService() {
        return new UserService();
    }

    @Bean(destroyMethod = "close")
    public DataSource dataSource() {
        DataSource ds = new DataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost/spring5fs?characterEncoding=utf8");
        ds.setUsername("spring5");
        ds.setPassword("spring5");
        return ds;
    }

    @Bean	//1)
    public PlatformTransactionManager transactionManager() {
        DataSourceTransactionManager tm = new DataSourceTransactionManager();
        tm.setDataSource(dataSource());
        return tm;
    }
}

 

위 설정 이후, memberDao.update() 호출 시, 트랜잭션 활성화가 true로 트랜잭션이 잘 적용됨을 볼 수 있음

2) @Transactional과 프록시

- 트랜잭션 AOP는 기본적으로 프록시 방식을 사용한다.

- @Transactional 애노테이션을 적용하면, 프록시 객체가 요청을 받아 트랜잭션을 처리 및 실제 객체를 호출하는 방식이다.

즉, 트랜잭션을 적용하려면 항상 프록시를 통해 실제 대상 객체를 호출해야 한다.

이렇게 해야 프록시객체에서 먼저 트랜잭션을 적용하고, 이후 대상 객체를 호출하게 된다.

 

- AOP를 적용하면 스프링은 대상 객체 대신에 프록시를 스프링 빈으로 등록한다. (참고로, @Transactional 애노테이션이 메서드에 하나라도 붙어있다면 트랜잭션 AOP는 프록시를 만들어서 스프링 컨테이너에 등록한다.)

 

따라서, 스프링은 의존관계 주입시에 항상 실제 객체 대신에 프록시 객체를 주입한다. 프록시 객체가 주입되기 때문에 대상 객체를 직접 호출하는 문제는 일반적으로 발생하지 않는다. 하지만 대상 객체의 내부에서 메서드 호출이 발생하면 프록시를 거치지 않고 대상 객체를 직접 호출하는 문제가 발생한다. 이렇게 되면 @Transactional 이 있어도 트랜잭션이 적용되지 않는다.

 

이유는 아래 그림처럼 자기 자신의 내부 메서드를 호출하는 경우는 프록시 객체가 아닌 실제 객체의 메서드를 호출하기 때문에 트랜잭션이 적용되지 않는다.

출처 : https://wwlee94.github.io/category/study/toby-spring/aop/transaction-property/

 

테스트 코드로 확인해보자

delete 메서드에서 update 메서드를 호출하나 트랜잭션 활성화가 false임을 확인 할 수 있다

해당 문제를 해결하기 위해서는 보통 별도의 클래스로 분리하여 외부에서 호출하도록 한다.

트랜잭션이 묶인 update메서드를 가지는 별도의 클래스를 만든 후, 외부에서 호출하면 정상적인 트랜잭션이 수행된다.

3) @Transactional 적용 메서드의 롤백 처리

- 별도 설정을 추가하지 않으면 발생한 익셉션이 RuntimeException일 때, 트랜잭션을 롤백한다.

- 만약 SQLException같은 RuntimeException을 상속하고 있지 않는 경우는 롤백처리가 되지 않기 때문에

롤백처리를 하고 싶다면, @Transactional의 rollbackFor 속성을 사용하도록 한다.

@Transactional(rollbackFor = SQLException.class)
public void update() {
    log.info("call update");
    printTxInfo();
}

- 롤백처리를 여러개 지정할때는 배열 형식을 사용한다.

@Transactional(rollbackFor = {SQLException.class, OtherException.class})

4) 격리(Isoltation)

 - DB에 동시에 접근하는 경우, 그 접근에 대한 제어 방법 혹은 설정

 - 모든 DB 트랜잭션은 격리수준(Isolation Level)을 갖고 있어야 한다.

5) 트랜잭션 전파 (propagation)

- 트랜잭션이 이미 진행중인 상황에서 다른 트랜잭션을 실행할 경우 어떻게 처리할 것인가에 대한 개념

 

외부 트랜잭션 -> 내부 트랜잭션

둘 이상의 논리 트랜잭션이 모두 커밋이 되어야 전체가 커밋됨 하나라도 롤백되면 전체 롤백

 

다양한 전파 옵션

 

1) REQUIRED

@Transactional의 기본 설정값이다.

- 기존 트랜잭션 있음: 기존 트랜잭션에 참여한다.

- 기존 트랜잭션 없음: 새로운 트랜잭션을 생성한다.

 

 

2) REQUIRES_NEW

항상 새로운 트랜잭션을 생성한다.

- 기존 트랜잭션 있음: 새로운 트랜잭션을 생성한다. (새로운 트랜잭션이 종료된 뒤에 기존 트랜잭션이 계속된다)

- 기존 트랜잭션 없음: 새로운 트랜잭션을 생성한다.

 

REQUIRES_NEW 를 사용하면 하나의 HTTP 요청에 동시에 2개의 데이터베이스 커넥션을 사용하게 된다.

따라서, 성능이 중요한 곳에서는 이런 부분을 주의해서 사용해야 한다. 

 

3) SUPPORT

트랜잭션을 지원한다는 의미이다.

- 기존 트랜잭션 있음: 기존 트랜잭션에 참여한다.

- 기존 트랜잭션 없음: 트랜잭션 없이 진행한다.

 

4) NOT_SUPPORT

트랜잭션을 지원하지 않는다는 의미이다.

- 기존 트랜잭션 있음: 트랜잭션 없이 진행한다. (기존 트랜잭션은 보류한다)

- 기존 트랜잭션 없음: 트랜잭션 없이 진행한다.

 

5) MANDATORY

의무사항이다. 트랜잭션이 반드시 있어야 한다. 기존 트랜잭션이 없으면 예외가 발생한다.

- 기존 트랜잭션 있음: 기존 트랜잭션에 참여한다.

- 기존 트랜잭션 없음: IllegalTransactionStateException 예외 발생

 

6) NEVER

트랜잭션을 사용하지 않는다는 의미이다. 기존 트랜잭션이 있으면 예외가 발생한다.

- 기존 트랜잭션 있음: IllegalTransactionStateException 예외 발생

- 기존 트랜잭션 없음: 트랜잭션 없이 진행한다. 

 

7) NESTED

- 기존 트랜잭션 있음: 중첩 트랜잭션을 만든다. 중첩 트랜잭션은 외부 트랜잭션의 영향을 받지만, 중첩 트랜잭션은 외부에 영향을 주지 않는다. 중첩 트랜잭션이 롤백 되어도 외부 트랜잭션은 커밋할 수 있다. 외부 트랜잭션이 롤백 되면 중첩 트랜잭션도 함께 롤백된다.

- 기존 트랜잭션 없음: 새로운 트랜잭션을 생성한다. (REQUIRED와 동일하게 동작)

 

 

- 참고 -

스프링 트랜잭션 처리의 중심이 되는 인터페이스 : PlatformTransactionManager

스프링 프레임워크는 다양한 환경에 대응하는 PlatformTransactionManager의 구현 클래스를 제공한다.

그중 하나는, 실습에서 다루었던 DataSourceTransactionManager로 JDBC 및 마이바티스 등의 JDBC 기반 라이브러리로 데이터베이스에 접근하는 경우에 이용한다.

 

스프링 부트에서는 AutoConfig를 통해 DataSourceTransactionManager를 자동으로 생성하여 만들어 줌

따라서, 스프링에서 @EnableTransactionManagement 설정을 해줄 필요가 없다

출처 : https://spring.io/guides/gs/managing-transactions/

DataSourceTransactionManagerAutoConfiguration 클래스 안에 DataSourceTransactionManager 빈 등록

 

 

반응형