<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()));
    }
}

Screenshot 2023-08-09 at 5.46.21 PM.png

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>

Quiz

table의 상태와 service 코드가 밑과 같을 때, transferMoney(1, 2, 20) 코드가 실행된 후에 table의 상태는?

Screenshot 2023-08-09 at 6.51.42 PM.png