@Transactional 주의점checked Exception vs unchecked Exceptionmethod안에서 @Transactional method 부를때flush@Transactional 메서드 내부에서 예외 발생 시@TransactionalPropagationREQUIRED (이거 주로사용)REQUIRES_NEW (이거 주로사용)NESTEDSUPPORTSNOT_SUPPORTEDMANDATORYNEVER
- Transaction : db 명령 논리적 묶음
- eg) 결제와 주문은 하나의 Transaction에서 일어나야함
- Transaction의 속성 : ACID
- Atomicity : 부분적 성공을 허용x. 다 성공하거나 다 실패하거나(all or nothing)
- Consistency : 데이터간의 정합성
- Isolation : 다른 Transaction으로 부터 독립적으로 작동해야 함
- Durability : 다른 시스템 고장 요인이 발생하더라도 transaction의 변경은 성공적으로 일어나야 함. 데이터는 영구적으로 보관
@Transactional 주의점
checked Exception vs unchecked Exception
// unchecked(runtime) Exception -> 일때는 transaction이 rollback 됨 @Transactional public void test(){ User user = new User(); userRepository.save(user); throw new RuntimeException(); } // checked exception -> 해당 경우는 Exception을 받아서 밖에서 처리를 하기때문에 // transaction rollback이 안됨 // checked exception은 항상 try catch로 잡아줘야함. @Transactional public void test() throws Exception{ User user = new User(); userRepository.save(user); throw new Exception(); }
- 이는 OpenSource TransactionAspectSupport.java 의 completeTransactionAfterThrowing() 를 확인해보면 됨
- @Transactional의 옵션으로 rollback 되도록 예외 추가해줄 수 있음
method안에서 @Transactional method 부를때
@Service public class BookService{ public void put(){ this.putBookAndAuthor(); } @Transactional public void putBookAndAuthor(){ Book book = new Book(); book.setName("JPA 시작하기"); bookRepository.save(book); Author author = new Author(); author.setName("martin"); authorRepository.save(author); } }
- @Transactional 이라는 annotation이 spring container에서 aop로 실행이 되는데 함수 안에서 실행이 되면 그 부분은 무시됨 ( 이유는 Spring이 해당 어노테이션을 보고 Proxy로 해당 메서드를 실행시키기에, class 내부에서 호출하게 되면 Proxy를 거칠수가 없음)
- bean 클래스 내부에서 함수 안에서 @Transactional이 붙은 함수를 부르면 annotation 효과가 없음
flush
@Transactional
을 붙이면 해당 함수 블럭이 종료될 때 flush가 되지만,@Transactional
이 없다면 각각의 repository 메서드별로 flush가 되게 됨(@Transactional
이 해당 메서드에 붙어있다면)
//SimpleJpaRepository.class @Transactional @Override public <S extends T> S save(S entity) { Assert.notNull(entity, "Entity must not be null."); if (entityInformation.isNew(entity)) { em.persist(entity); return entity; } else { return em.merge(entity); } }
@Transactional 메서드 내부에서 예외 발생 시
해당 예외는 proxy 에서 처리되기 때문에 메서드 내부에서 해당 예외를 잡을 수가 없음 ( 트랜잭션이 메서드 호출이 끝나고 나서 commit 을 하든 rollback을 하든 진행을 하기 때문에 )
메서드 내부에서 예외를 잡고 싶을 때는, saveAndFlush 와 같은 메서드를 사용함으로써 트랜잭션을 바로 종료함으로써 예외를 잡을 수 있음
try catch 구문 잘 잡힘. JDK 21 에서. InvocationTargetException 으로 wrapping 되어 exception 못잡는다고 했었던것 같은데..
@Transactional
- docs에 따르면 구체 클래스에만 붙이라고 함
- interface에 붙일 때는 interface-based proxy(eg : JpaRepository) 여야 한다고함
- The Spring team's recommendation is that you only annotate concrete classes with the @Transactional annotation, as opposed to annotating interfaces. You certainly can place the @Transactional annotation on an interface (or an interface method), but this will only work as you would expect it to if you are using interface-based proxies. The fact that annotations are not inherited means that if you are using class-based proxies then the transaction settings will not be recognised by the class-based proxying infrastructure and the object will not be wrapped in a transactional proxy (which would be decidedly bad )
Propagation
- 하나의 트랜젝션에서 다른 트랜젝션을 불렀을때 어떻게 처리되는지에 대한 문제가 Propagation임
@Test @Transactional void putBookAndAuthor(){ Book book = new Book(); book.setName("JPA 강의"); bookRepository.save(book); try{ authorService.putAuthor(); } catch(RuntimeException e){ } System.out.println(">>> " + bookRepository.findAll()); } public class AuthorService { private final AuthorRepository authorRepository; @Transactional(propagation = Propagation.REQUIRED) void putAuthor(){ Author author = new Author(); author.setName("martin"); authorRepository.save(author); throw new RuntimeException("오류 발생."); } }
REQUIRED (이거 주로사용)
- putAuthor()에서의 Transaction이 위 단계에서의 Transaction을 재활용 하여, 안에서 실패 한것 바깥에서도 실패 ⇒ 전체 rollback
- 바깥에서 Transaction 있으면 안에도 Transaction 붙여서 진행함
REQUIRES_NEW (이거 주로사용)
- 매 Transaction 마다 새로 만듦. 안에서 실패하더라도 바깥에 Transaction과는 달리 안에만 rollback함
NESTED
- 호출하는 쪽에 Transaction이 있고 호출 당하는 쪽에도 Transaction이 있을 때에는 Transaction을 새로 생성하는(REQUIRES_NEW) 것처럼 행동함. 그 이외에는 REQUIRED와 마찬가지로 작동
SUPPORTS
- 밖에도 Transaction이 있고, 안에도 Transaction이 있으면 REQUIRED와 똑같이 동작함
- REQUIRED와 다른 점은 밖에 Transaction이 있으면 안에 Transaction을 안붙여준다는것.
NOT_SUPPORTED
- 밖에 Transaction이 있을때, 안에 Transaction 만들어주지 않음
- 안의 method가 끝날때까지 바깥에 Transaction이 멈춰있음
MANDATORY
- MANDATORY는 Transaction이 반드시 존재해야함. 없으면 오류
NEVER
- Transaction이 존재하지 않아야 함. 이미 존재한다면 오류