<aside> 💡 스프링은 Proxy 패턴을 사용하여 AOP를 구현한다.
</aside>
package jaejung.springprac.controller;
// ...
@RestController
@RequestMapping("/api/account")
@RequiredArgsConstructor
public class AccountController {
final private AccountService accountService;
// ...
@PostConstruct
public void init() throws Exception
{
System.out.println(
"---------------0> %s "
.formatted(this.getClass().getName()));
System.out.println(
"---------------1> %s "
.formatted(this.accountService.getClass().getName()));
}
}
package jaejung.springprac.domain.account.service;
// ...
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class AccountService {
private final AccountRepository accountRepository;
// ...
@PostConstruct
public void init() throws Exception
{
System.out.println(
"---------------2> %s "
.formatted(this.getClass().getName()));
}
}
1번과 2번의 클래스 이름이 다른 것을 확인할 수 있다.
즉, 스프링은 우리 몰래 동적으로 Proxy 객체를 생성하고 있으며,
그 이유는 어노테이션에 해당하는 Method 혹은 Class에 추가적인 코드를 덧붙여 주기 위해서이다.
그러므로 밑 예제 코드와 같이 Service 객체 내에 @Transaction이 걸려있는 Method들은 Proxy 객체에서 로직들이 추가된 형태로 존재할 것이다.
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
@Transactional
public void createUserListWithTrans(){
for (int i = 0; i < 10; i++) {
if(i==5) throw new RuntimeException();
createUser(i);
}
}
public void createUserListWithoutTrans(){
for (int i = 0; i < 10; i++) {
if(i==5) throw new RuntimeException();
createUser(i);
}
}
@Transactional
public User createUser(int index){
User user = User.builder()
.name("testname::"+index)
.email("testemail::"+index)
.build();
userRepository.save(user);
return user;
}
}
@RequiredArgsConstructor
@Service
public class UserServiceProxy extends UserService {
private final EntityManager em;
public void createUserListWithTrans(){
EntityTransaction tx = em.getTransaction();
tx.begin();
super.createUserListWithTrans();
tx.commit();
}
public void createUserListWithoutTrans(){
super.createUserListWithoutTrans();
}
public User createUser(int index){
EntityTransaction tx = em.getTransaction();
tx.begin();
User user = super.createUser(index);
tx.commit();
return user;
}
}
그리고 이 경우 우리는
createUserListWithTrans Method는 하나의 트랜잭션으로 실행되는 것을 알 수 있고,
createUserListWithoutTrans Method는 트랜잭션 어노테이션을 걸어놓지 않았기에 당연히 트랜잭션으로 실행되지 않지만 이 Method 안에 있는 createUser Method 또한 (트랜잭션 어노테이션을 붙였음에도 불구하고) 트랜잭션으로 실행되지 않는 다는 것을 알 수 있다.
<aside> 💡 즉, 하위 Method가 @Transactional 어노테이션을 가지고 있어도, 이 메서드를 사용하는 상위 Method가 트랜잭션 어노테이션을 가지고 있지 않으면 상위, 하위 할 것없이 두 Method 모두 트랜잭션으로 실행되지 않는다.
</aside>
table의 상태와 service 코드가 밑과 같을 때, transferMoney(1, 2, 20) 코드가 실행된 후에 table의 상태는?