트랜잭션의 시작&커밋&롤백의 처리를 담당하는 건?
두개 이상의 작업을 하나로 퉁친다.
가령, 비밀번호 변경을 위해 이메일을 발송-해당계정 일시 잠금-이메일 인증시 잠금해제를 한다고 할 때,
잠금을 했는데 발송된 이메일을 인증하는 과정에서 문제가 발생하면, 잠금만 되고 해제는 영원히 되지 않는다.
이때, 트랜잭션을 이용해 하나가 잘못되면 전체가 down이 되도록 해, 성공과 실패 모두 하나가 되도록 하면 문제가 없다. 즉, 인증 과정에서 잘못되면 발송부터 전부 취소가 돼 없던것으로 하는 것이다.
@Transactional을 적용하기 위해 @EnalbeTransactionManagement를 사용시, 스프링이 @Transaction이 적용된 빈객체를 찾아 알맞은 proxy객체를 생성.
MainForCPS실행시, ChangePasswordService 빈객체를 찾은 다음, 해당 객체 내의 changePassword()를 실행하는데, 이 ChangePasswordService는 트랜잭션 객체이다. 따라서, 스프링은 트랜잭션 기능을 적용한 프록시객체를 생성한다.
즉, 실제로 getBean을 실행하면 전달되는건, ChangePsswordService의 객체 대신, 트랜잭션 처리를 위해 새엉된 프록시객체다.
이 프록시 객체는 @Transaction애노테이션이 붙은 메서드를 호출하면, PlatformTransactionManager을 이용해 트랜잭션을 시작, 실제 객체의 메서드를 호출(by chagePassword()를 ChangePasswordService클래스로), 여기서 성공적으로 리턴이 되면, 트랜잭션을 커밋한다.
이에 대한 롤백역시, WrongIdPasswordException이 발생하면, 트랜잭션이 롤백된다.
return <-> throw WrongIdPassworodException
commit() <-> rollback()
따라서, 트랜잭션의 롤백을 염두에 두고 WrongIdPasswordException은 'RuntimeException'을 상속하도록 해야한다.
Transaction에 관련된 것, 더 나아가 JdbcTemplate는 db연동과정 중 문제가 잇으면 DataAccessException을 발생하는데, 이 역시 RuntimemException을 상속하므로, Transaction이나 JdbcTemplate는 RuntimeException을 상속시켜야 한다.
단, SQLException은 RuntimeException을 상속하지 않으며, 따라서 트랜잭션을 롤밸시키지 않는다.
그러므로, 여기에 롤백속성을 추가하려면,
@Transactional(rollbackFor=SQLException.class)을 해야 한다.
@Transactional(rollbackFor= {SQLException.class, IOException.class, ....})
public void method() {
...
}
noRollbackFor로 하면, 지정한 익셉션이 발생해도 롤백시키지 않고 커밋할 익셉션을 타입을 지정하는 데 쓸 수 있다.
//EnalbeTransactionManagement애노테이션이, @Transactional이 붙은 메서드를 트랜잭션 범위에서 실행하는 기능 활성화
//등록된 PlatformTransactionManager빈을 이용해 트랜잭션 적용
//트랜잭션처리를 위한 설정 완료시, 트랜잭션범위에서 실행하고픈 스프링빈객체 메서드에 @Transaction을 붙이면 됨
//ex)ChangePasswordService의 changePasswrod()를 트랜잭션범위에서 실행하고싶다?
@Bean
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager tm= new DataSourceTransactionManager();
tm.setDataSource(dataSource());
//dataSource 프로퍼티를 이용해, 트랜잭션 연동에 쓸 DataSource를 지정
return tm;
}
@Transactional에 value값이 없으면, 등록된 빈 중 타입이 PlatformTransactionManager인 빈을 사용한다. 위는 PlatformTransactionManager이면서 DataSourceTransactoinManager을 리턴하는 것을 알 수 있다.
트랜잭션 전파
Propagation 열거타입 값 목록 중, REQUIRED는 "메서드를 수행하는 데 트랜잭션이 필요하다"는 의미. 현재 진행중인 트랜잭션이 존재하면 해당 트랜잭션을 쓰고, 존재하지 않으면 새로운 트랜잭션을 생성.
//1)
public class someService {
private Anyservice anyservice;
@Transactional
public void some() {
anyservice.any();
}
public void set(Anyserice as) {
this.anyservice= as;
}
}
public class Anyservice {
@Transactional
public void any{} {...}
}
//2)
@Configuration
@EnableTransactionManagement
public class Config {
@Bean
public Someservice some() {
Someservice some= new Someservice();
some.set(any());
return some;
}
@Bean
public AnyService any() {
return new Anyservice();
}
//DataSourceransactionMnager 빈 설정
//DataSource 설정
}
SomeService와 AnyService 두 클래스가 @Transactional 애노테이션 적용중. 즉, 두 클래스에 대해 프록시가 생성됨.
SomeService의 some()을 시작하든, AnyService의 any()를 하든 트랜잭션이 시작됨.
근데 some()에서, 내부가 다시 any()를 호출할 때는
--> @Transactional의 propagation속성은 기본값이 Propagation.REQUIRED로,
some()실행시 트랜잭션 시작 -> some()내부에서 any() 호출시, 이미 트랜잭션이 시작된 게 존재하므로, any()는 새로 트랜잭션 안 만들고, 존재하는 걸 그대로 씀. some()과 any()를 한 트랜잭션으로 묶어 사용
any()에 적용한 @Transactional의 propagation속성이 REQUIERS_NEW일 땐, 항상 새로운 트랜잭션 생성됨.
@Transactional 내에 들어간 건 어떤것이든 그 안에서 처리가 된다.
public class ChangePasswordService {
@Transactional
public void changePassword() {
memberDao.update(member);
}
}
...
public class MemberDao {
private JdbcTemplate jdbcTemplate;
//@Transactional 없는 상태
public void update(Member member) {
jdbcTemplate.update( ... )
}
}
프록시가 트랜잭션 시작. 바로 selectByEmail()이나 update에서, JdbcTemplate를 실행. 이 JdbcTemplate는 현재 진행중인 트랜잭션 범위에서 쿼리를 실행.
한 트랜잭션 범위에서 실행되므로, changePassword와 return 사이에서 익셉션 발생시, selectByEmail의 query도 롤백됨
'동방프로젝트' 카테고리의 다른 글
Model을 통해 컨트롤러에서 뷰에 데이터 전달 (1) | 2021.01.31 |
---|---|
스프링이 제공하는 폼 태그 사용이전 jsp (1) | 2021.01.31 |
8과 queryForObject() (2) | 2021.01.23 |
7과-2 (0) | 2021.01.22 |
7과 (2) | 2021.01.20 |