@Transactional 고찰

Spring의 @Transactional은 기본적으로 AOP Proxy를 통해 이루어 진다.
아래 그림 참조.


중요한 점은 AOP Proxy는 class 단위로 이루어 진다. 그러므로 아래와 같이 하나의 class 내부의 method call은 해당 class 외부의 최초 call인 addUser()@Transactional만 의미가 있고 proxyAddUser()@Transactional은 의미가 없다.(안붙인거와 같다)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import test.jpa.demo.domain.User;
import test.jpa.demo.domain.UserData;
import test.jpa.demo.domain.UserDataRepository;
import test.jpa.demo.domain.UserRepository;

@Service
public class UserService {
    @Autowired private UserRepository userRepository;
    @Autowired private UserDataRepository userDataRepository;
    @Autowired private OtherUserService otherUserService;

    @Transactional
    public long addUser(String name) {
        User user = new User();
        user.setName(name);
        user = userRepository.save(user);

        proxyAddUser(name);

        return user.getId();
    }

    @Transactional
    public long proxyAddUser(String name) {
        UserData userData = new UserData();
        userData.setName(name);
        userData = userDataRepository.save(userData);

        return userData.getId();
    }
}

아래와 같이 RuntimeException을 잡아보면 transaction 내부에서의 try-catch 임으로 commit이 이루어 진다.(@Transactional을 붙였을 때와 안붙였을 때의 결과가 동일하다)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Service
public class UserService {
    @Autowired private UserRepository userRepository;
    @Autowired private UserDataRepository userDataRepository;
    @Autowired private OtherUserService otherUserService;

    @Transactional
    public long addUser(String name) {
        User user = new User();
        user.setName(name);
        user = userRepository.save(user);

        try {
            proxyAddUser(name);
        } catch (RuntimeException e) {
            System.out.println("RuntimeException..!!!");
        }

        return user.getId();
    }

    @Transactional
    public long proxyAddUser(String name) {
        UserData userData = new UserData();
        userData.setName(name);
        userData = userDataRepository.save(userData);

        throw new RuntimeException();
    }
}



그러나, 아래처럼 해당 오브젝트 외부의 method call 상황에서 RuntimeException이 발생하면, caller에서 exception을 잡아도 이미 @Transactional로 확장된 곳에서 발생한 exception이 global roll-back을 체크해 두기 때문에 caller와 callee 모두 roll-back 되어 버린다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class UserService {
    @Autowired private UserRepository userRepository;
    @Autowired private UserDataRepository userDataRepository;
    @Autowired private OtherUserService otherUserService;

    @Transactional
    public long addUser(String name) {
        User user = new User();
        user.setName(name);
        user = userRepository.save(user);

        try {
            otherUserService.proxyAddUser(name);
        } catch (RuntimeException e) {
            System.out.println("RuntimeException..!!!");
        }

        return user.getId();
    }
}


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Service
public class OtherUserService {
    @Autowired private UserDataRepository userDataRepository;

    @Transactional
    public long proxyAddUser(String name) {
        UserData userData = new UserData();
        userData.setName(name);
        userData = userDataRepository.save(userData);

        throw new RuntimeException();
    }
}

댓글

이 블로그의 인기 게시물

[Protocol] WIEGAND 통신

Orange for Oracle에서 한글 깨짐 해결책

[UI] GNB·LNB·SNB·FNB 용어 설명