이번 시간에는 @EnableTransactionManagement 애노테이션에이 제공하는 속성에 대해 알아봅시다.
@EnableTransactionManagement 애노테이션의 속성
속성 | 설명 |
proxyTargetClass | 클래스를 이용해서 프록시를 생성할지 여부를 지정한다. 기본값은 false로서 인터페이스를 이용해서 프록시를 생성한다. |
order | AOP 적용 순서를 지정한다. 기본값은 가장 낮은 우선순위에 해당하는 int의 최댓값이다. |
트랜잭션 전파
public class SomeService{
private AnyService anyService;
@Transactional
public void some(){
anyService.any();
}
public void setAnyService(AnyService as){
this.anyService = as;
}
}
public class AnyService{
@Transactional
public void very(){...}
}
@Configuration
@EnableTransactionManagement
public class Config {
@Bean
public SomeService some(){
SomeService some = new SomeService();
some.setAnyService(any());
return some;
}
@Bean
public AnyService any(){
return new AnyService();
}
// DataSourceTransactionManager 빈 설정
// DataSource 설정
}
SomeService 클래스와 AnyService 클래스는 둘 다 @Transactional 애노테이션을 적용하고 있습니다. 위 설정클래스에 따르면 두 클래스에 대해 프록시가 생성됩니다. 즉 SomeService의 some() 메서드를 호출하면 트랜잭션이 시작되고 AnyService의 any() 메서드를 호출해도 트랜잭션이 시작됩니다. some() 메서드 안에 any() 메서드를 호출하는데 이 경우 트랜잭션 처리에 대해서 알아봅시다.
@Transactional의 propagation 속성은 기본값이 Propagation.REQURIED입니다. REQUIRED는 현재 진행 중인 트랜잭션이 존재하면 해당 트랜잭션을 사용하고 존재하지 않으면 새로운 트랜잭션을 생성합니다.
처음 some() 메서드 호출 시 트랜잭션을 새로 시작하지만 some() 메서드 내부에서 any() 메서드를 호출하면 이미 some() 메서드에 의해 시작된 트랜잭션이 존재하므로 any() 메서드를 호출하는 시점에는 트랜잭션을 새로 생성하지 않고 존재하는 트랜잭션을 그대로 사용합니다. 즉 some() 메서드와 any() 메서드를 한 트랜잭션으로 묶어서 실행하는 것입니다.
만약 any() 메서드에 적용한 @Transactional의 propagation 속성값이 REQUIRES_NEW 라고 한다면, 기존 트랜잭션 존재 여부에 상관없이 항상 새로운 트랜잭션을 시작합니다. 따라서 이 경우에는 some() 메서드에 의해 트랜잭션이 생성되고 다시 any() 메서드에 의해 트랜잭션이 생성됩니다.
public class ChangePasswordService {
@Autowired
private MemberDao memberDao;
@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);
}
public void setMemberDao(MemberDao memberDao) {
this.memberDao = memberDao;
}
}
public class MemberDao {
private JdbcTemplate jdbcTempalte;
...
//@Transactional 제거
public void update(Member member){
jdbcTemplate.update(
"update MEMBER set NAME = ?, PASSWORD = ? where EMAIL =?",
member.getName(), member.getPassword(), member.getEmail());
}
}
changePassword() 메서드는 MemberDao()의 update()메서드를 호출합니다. 그런데 MemberDao.update() 메서드는 @Transactional 애노테이션이 적용되어 있지 않습니다. 이런 경우 어떻게 트랜잭션 처리가 될까요?
update() 메서드에 @Transactional 붙어 있지 않지만 JdbcTemplate 클래스로 트랜잭션 범위에서 쿼리를 실행할 수 있게 됩니다. JdbcTemplate는 진행 중인 트랜잭션이 존재하면 해당 트랜잭션 범위에서 쿼리를 실행하게 됩니다.
[JdbcTemplate 는 트랜잭션이 진행 중이면 트랜잭션 범위에서 쿼리를 실행합니다.]
1: 트랜잭션 시작을 하게 되면 ChangePasswordService의 @Transactional이 붙은 메서드를 실행하므로 프로시가 트랜잭션을 시작합니다. 2.1.1: query와 2.2.1ㅣ update는 JdbcTemplate를 실행합니다. 이 과정을 실행하는 시점에서 트랜잭션이 진행 중입니다.(트랜잭션은 커밋 시점인 3: commit에서 끝이 납니다.)
이러한 경우는 JdbcTemplate는 이미 진행 중인 트랜잭션 범위에서 쿼리를 실행합니다. 따라서 changePassword() 메서드에서 실행하는 모든 쿼리는 하나의 트랜잭션 범위에서 실행됩니다. 한 트랜잭션 범위에서 실행되므로 2:changePassword 부터 2.3: return 사이에 익셉션이 발생해서 트랜잭션이 롤백되면 2.2.1:에서 update 한 쿼리도 롤백됩니다.
'Spring' 카테고리의 다른 글
[Spring] 스프링 MVC 설정 (0) | 2024.03.25 |
---|---|
[Spring] Spring MVC (0) | 2024.03.20 |
[Spring] @Transactional 속성 (0) | 2024.03.04 |
[Spring] @Transactional 적용 메서드 롤백 처리 (0) | 2024.03.04 |
[Spring] @Transacational과 프록시 (1) | 2024.03.01 |