문제
- @Transactional을 애노테이션을 선언해도 this를 통한 내부 메서드를 호출시 롤백이 되지 않았다.
아래는 문제의 코드이다.
같은 클래스안에 INSERT할 수 있는 서로 다른 메서드를 생성 뒤
이 두개의 INSERT 메서드를 this로 내부의 메서드들을 한번에 호출하는 메서드를 생성하였다.
(참고로 insert1 메서드를 실행 시 'INSERT INTO BOOK VALUES ('18', 'TEST1', 'TEST_WRITER1', 100, 1)' 라는 SQL 쿼리가 실행 )
insert2() 메서드는 예외를 던지고 allInsert() 메서드는 insert1()와 insert2() 내부 메서드를 한번에 호출하는 메서드다.
그 후 테스트 클래스를 생성뒤 한번에 Insert 하는 메서드 실행
아래의 사진에서 볼 수 있듯이 정상적으로 에러가 발생한 모습이다.
하지만 데이터베이스를 조회하면 ROLLBACK이 되어야할 insert1이 실행이 된 후
COMMIT까지 되어 데이터가 조회가 되는 문제가 발생했다.
원인
Spring의 트랜잭션 관리가 AOP 프록시를 통해 이루어지기 때문에,
프록시는 외부에서 메서드 호출이 이루어질 때만 적용된다.
같은 클래스 내에서 메서드를 호출할 경우, 프록시를 우회하여 직접 호출하게 되어 트랜잭션 관리가 제대로 이루어지지 않는다. 이는 다음과 같은 이유 때문이다.
- 프록시 패턴: Spring은 @Transactional 애노테이션이 붙은 메서드를 프록시 객체를 통해 호출한다. 이 프록시는 원래 객체를 감싸고 있으며, 메서드 호출 시 트랜잭션을 관리한다.
- 내부 호출: 클래스 내부에서 다른 메서드를 호출하면, 이 호출은 프록시를 거치지 않고 직접 호출이 된다. 따라서 트랜잭션 처리가 적용되지 않는다.
따라서 내부 호출로 메서드를 실행하면 실제 객체를 호출하기 때문에 트랜잭션을 인식하지 못하거나, 관리가 안된다.
해결 방안
이를 해결하기 위해 트랜잭션을 관리하는 메서드들을 서로 다른 클래스에서 호출하거나,
AOP 프록시를 통해 메서드를 호출하도록 설정해야 한다.
다시 TEST1이라는 데이터를 지우고, 저는 별도의 클래스를 생성 후 insert1() 메서드와, insert2() 메서드를 실행했다.
그리고 테스트를 진행해봤다.
정상적으로 에러를 발생하는 모습이다.
그러자 더이상 insert1() 메서드가 COMMIT이 되지 않고 ROLLBACK이 되어 데이터가 조회되지 않았다.
또는
@Autowired private ApplicationContext applicationContext;를 클래스에 선언, 주입 후 applicationContext.getBean() 메서드를 통해 해당 클래스의 Bean을 갖고와 메서드를 호출해도 된다.
이렇게 하면 내부 호출이 아닌 프록시를 통해 호출되므로 트랜잭션이 적용된다.
물론 Service - DAO를 계층 분리하여 내부호출을 하지 않을 시 발생하지 않는 이슈겠지만
지금 하고 있는 프로젝트에서 분리하기 어렵다면 별도의 클래스를 생성 후 각각 호출해 하나의 트랜잭션으로 관리하면 될 것 같다.
'Spring Framework' 카테고리의 다른 글
[Spring] Bean Validation (검증) - 1 (4) | 2024.10.31 |
---|---|
[Spring] 스프링 메시지, 국제화 (1) | 2024.10.29 |
[Spring] Argument Resolver 란? (0) | 2024.10.24 |
(실습)객체지향 설계 원칙를 지키면서 설계하기 (0) | 2024.03.30 |
객체 지향 설계의 5가지 원칙(SOLID) (0) | 2024.03.30 |