티스토리 뷰

스프링은 AOP안에서 자기 자신의 메서드를 호출하면 AOP가 적용되지 않음.

정말 기본적인 상식?이지만 깜빡하는 경우가 있음.

외부에서 호출해야 해당 @Transactional이 존재하는 빈을 감싼 proxy bean이 호출되어 Transaction이 적용됨.

실제로 적용이 안되는 지 간단한 테스트를 해보자.

import jpabook.jpashop.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HelloController {
    @Autowired
    TestService testService;

    @GetMapping("/test")
    public String test() {
        testService.join();
        return "hello";
    }
}

 위는 간단한 컨트롤러로 hello로 들어오게 되면 testService의 join메소드를 호출하게 됨.

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TestService {
    @Autowired
    MemberRepository memberRepository;

    @Transactional
    public void join() {
        Member member = new Member();
        member.setName("join");
        memberRepository.save(member);
        test();
        throw new RuntimeException();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void test() {
        Member member = new Member();
        member.setName("requires_new");
        memberRepository.save(member);
    }
}

TestService의 join 메소드를 보면 Member 인스턴스를 생성해서 db에 insert를 하는데, 중간에 동일한 TestService의 내부 함수인 test 메소드를 호출한 뒤, RuntimeException을 던지면 어떻게 될까?

test 메소드의 경우 Propagation required_new로 무조건 새로운 트랜잭션을 생성하도록 처리하였기 때문에, 만약 트랜잭션이 정상적으로 모두 적용되었다면 (join과 test 둘 다) db에는 requires_new의 이름을 가진 member 데이터는 정상적으로 insert되었을 것이고, join의 이름을 가진 member 데이터는 rollback되었을 것임.

test url을 호출하여 Exception이 발생시킨 뒤 데이터를 확인해보면, 모두 롤백되어 아무 데이터도 없는 것을 확인할 수 있음.

즉, test 메소드는 내부에서 호출하였기 때문에 트랜잭션이 따로 적용되지 않고, 그대로 join 메소드의 트랜잭션에 포함되어 적용이 된 것.

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class Test2Service {
    @Autowired
    MemberRepository memberRepository;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void test() {
        Member member = new Member();
        member.setName("requires_new");
        memberRepository.save(member);
    }
}
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TestService {
    @Autowired
    MemberRepository memberRepository;

    @Autowired
    Test2Service test2Service;

    @Transactional
    public void join() {
        Member member = new Member();
        member.setName("join");
        memberRepository.save(member);
        // test2Service의 test를 호출하도록 수정
        test2Service.test();
        throw new RuntimeException();
    }
}

이를 수정해서 test 메소드를 다른 서비스가 갖도록 하고, 그 서비스를 통해서 test 메소드를 호출하게 되면, 이전에 기대했던 결과 그대로 데이터가 생성됨을 알 수 있음. 

join member는 rollback되고, requires_new는 그대로 commit된 것을 볼 수 있음.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
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
글 보관함