프로그래밍/JPA

공유된 엔티티 매니저의 트랜잭션, @Transactional 롤백

승민아 2023. 6. 29. 11:20

Maven이 아닌 SpringBoot와 Grandle로 김영한 선생님의 JPA 교재 예제를 따라해보는데 아래의 에러가 발생했다.

No Persistence provider for EntityManager named

 

 

아래와 같이 EntityManagerFactory를 주입시켜 매니저를 생성하여 사용하면 된다.

public class JpastudyApplication {

    static EntityManagerFactory emf;

    public JpastudyApplication(EntityManagerFactory emf) {
        this.emf = emf;
    }

    public static void main(String[] args) {
        SpringApplication.run(JpastudyApplication.class, args);

        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득

        try {
            tx.begin(); //트랜잭션 시작
            logic(em);  //비즈니스 로직
            tx.commit();//트랜잭션 커밋

        } catch (Exception e) {
            e.printStackTrace();
            tx.rollback(); //트랜잭션 롤백
        } finally {
            em.close(); //엔티티 매니저 종료
        }

        emf.close(); //엔티티 매니저 팩토리 종료
    }

    public static void logic(EntityManager em) {
    	...
    }
}

 

 

다른 방법으로 MVC 패턴으로 Member를 관리해봤다.

MemberController

@Controller
public class MemberController {

    private MemberService memberService;

    @Autowired
    public MemberController(MemberService memberService) {
        this.memberService = memberService;
    }

    @GetMapping("/hello")
    public String test() {
        memberService.test();
        return "redirect:/";
    }
}

"/hello"로 요청이 오면 교재 예제 코드를 실행하게 작성했다.

 

MemberRepository

@Repository
public class MemberRepository {

    EntityManagerFactory emf;

    public MemberRepository(EntityManagerFactory emf) {
        this.emf=emf;
    }


    public void test() {
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        try {
            tx.begin();
            logic(em);
            tx.commit();
        } catch (Exception e) {
            e.printStackTrace();
            tx.rollback();
        } finally {
            em.close();
        }
    }

    public void logic(EntityManager entityManager) {
    	...
    }

}

 

동작 결과

 

만약 아래와 같이 엔티티매니저를 주입 받는 방식으로 코드를 직성하면 에러가 발생한다.

Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
@Repository
public class MemberRepository {

    EntityManager em;

    public MemberRepository(EntityManager em) {
        this.em = em;
    }

    public void test() {
        EntityTransaction tx = em.getTransaction();
        try {
            tx.begin();
            logic(em);
            tx.commit();
        } catch (Exception e) {
            e.printStackTrace();
            tx.rollback();
        } finally {
            em.close();
        }
    }

    public void logic(EntityManager entityManager) {
    	...
    }

}

공유된 EntityManager 트랜잭션을 생성할 수 없다는 내용이다.

그래서 이전의 MemberRepository처럼 EntityManagerFactory에서 생성했다.

 

 

아래처럼 EntityManager를 바로 생성해 사용해도 된다. 단, 공유된 EntityManager이므로

트랜잭션을 생성하지 않고 @Transactional를 서비스 계층에 추가해 사용해야한다.

 

MemberRepository

@Repository
public class MemberRepository {

    EntityManager em;

    public MemberRepository(EntityManager em) {
        this.em = em;
    }
    
    public void logic(){
    	...
    }

}

 

MemberService

@Service
@Transactional
public class MemberService {

    private MemberRepository memberRepository;

    @Autowired
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    public void test() {
        memberRepository.test();
    }
}

 

 

 

 

Rollback이 안된다?

@Transactional 어노테이션은 Unchecked Exception 발생에는 트랜잭션이 롤백이 된다.

하지만, Checked Exception에는 롤백되지 않는다.

 

try catch문으로 Unchecked Exception을 감싸도

이것은 CheckedException로 롤백되지 않는다.

@Repository
public class MemberRepository {

    public void test(){
        try {
            logic();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public void logic(){

        String id = "id1";
        Member member = new Member();
        member.setId(id);
        member.setUsername("승민");
        member.setAge(1);
        em.persist(member);

        String id2 = "id2";
        Member member2 = new Member();
        member2.setId(id2);
        member2.setUsername("승민2");
        member2.setAge(2);
        em.persist(member2);

        throw new RuntimeException("RUNTIME ERROR");

    }

}

member와 member2가 롤백되지 않고 저장된다.

 

롤백되게 하고싶다면

Unchecked Exception을 Spring이 인지할 수 있게 catch 부분에서 다시 예외를 던져주자..

@Repository
public class MemberRepository {
    public void test(){
        try {
            logic();
        } catch(Exception e) {
            e.printStackTrace();
            throw new RuntimeException("RUNTIME ERR"); // 예외 다시 던짐
        }
    }

    public void logic(){
    	...
        throw new RuntimeException("RUNTIME ERROR");
    }

}