공유된 엔티티 매니저의 트랜잭션, @Transactional 롤백
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");
}
}