====== Spring Transaction ====== * [[springframework:javaconfig:enabletransactionmanagement|Spring Framework @EnableTransactionManagement]] * [[http://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html|SpringFramework Transaction Management Reference]] * [[http://www.ibm.com/developerworks/java/library/j-ts1/index.html|Transaction strategies: Understanding transaction pitfalls]] * [[https://www.baeldung.com/spring-transactional-propagation-isolation|Transaction Propagation and Isolation in Spring @Transactional | Baeldung]] ===== 내부 트랜잭션 메소드 호출 금지 ===== * 기본 프록시 모드에서는 클래스의 메소드에서 동일 클래스의 ''@Transactional'' 걸린 메소드를 호출하면 트랜잭션이 무시된다. * 이는 AOP의 기본작동이 그렇기 때문이다. * 따라서 항상 트랜잭션을 올바로 적용하려면 **현재 클래스의 메소드가 아닌 다른 클래스의 메소드에 트랜잭션을 걸어야만 한다.** > In proxy mode (which is the default), **only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction** at runtime even if the invoked method is marked with @Transactional. Also, the proxy must be fully initialized to provide the expected behaviour so you should not rely on this feature in your initialization code, i.e. @PostConstruct. ===== Circular (Cyclic) Dependency 금지 ===== * Circular Dependency 인 Bean에 ''@Transactional'' 애노테이션을 줘도 트랜잭션이 먹지 않는 현상이 발견됨(Spring 3.x). * 이는 대부분의 AOP에서 동일하게 적용될 것으로 보임. Circular Dependency 를 항상 피하도록 한다. ===== interface or class? ===== * ''@Transactional'' 어노테이션을 어디에 걸어야 하는가? * **Spring 문서는 class에 트랜잭션 애노테이션 걸기를 권장**하고 있다. > Spring recommends that you **only annotate concrete classes (and methods of 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 works only as you would expect it to if you are using interface-based proxies. The fact that Java annotations are not inherited from interfaces means that if you are using class-based proxies ( proxy-target-class="true") or the weaving-based aspect ( mode="aspectj"), then the transaction settings are not recognized by the proxying and weaving infrastructure, and the object will not be wrapped in a transactional proxy, which would be decidedly bad. ===== Read Only ===== * [[http://akaroice.tistory.com/20|hibernate flush VS commit :: 비상]] ''@Transactional(readOnly=true)''는 JDBC의 ''[[https://docs.oracle.com/javase/7/docs/api/java/sql/Connection.html#setReadOnly(boolean)|Connection.setReadOnly(true)]]''를 호출한다. 이는 단지 **"힌트"일 뿐이며 실제 데이터베이스 자체를 read-only 트랜잭션으로 생성할지 여부는 JDBC 드라이버에 따라 달라지고 행위도 데이터 계층 프레임워크의 구현여부에 따라 달라질 수 있다.** 이 힌트로 정말로 read only 트랜잭션이 생성될 수도 있고, MySQL 리플리케이션 JDBC 드라이버의 경우 Write가 있을 경우에는 Master로 read only일 경우에는 Slave로 커넥션을 맺는 식의 선택을 하는 정도의 역할만 할 수도 있다. Oracle JDBC 드라이버는 Read-Only 트랜잭션을 지원하며, MySQL의 경우 5.6.5 버전이 되어서야 이를 지원하게 되었다. Spring-Hibernate 연동시에는 Session의 Flush Mode를 ''FLUSH_NEVER''로 변경한다. 그러나 데이터가 수정되면 수정된 상태로 세션의 1차 캐시에는 수정된 상태로 데이터가 남아있게 되면, 명시적으로 ''flush()''를 호출해야 쓰기 기능이 작동하게 된다. 단, 읽기 작업만 하더라도 트랜잭션을 걸어주는 것이 좋다. 트랜잭션을 걸지 않으면 모든 SELECT 쿼리마다 commit을 하기 때문에 성능이 떨어진다. 명시적으로 트랜잭션을 걸어주면 마지막에 명시적으로 commit을 해주면 되며, commit 횟수가 줄어서 성능이 좋아진다. ===== Chained Transaction Manager ===== * [[http://docs.spring.io/spring-data/commons/docs/1.6.2.RELEASE/api/org/springframework/data/transaction/ChainedTransactionManager.html|ChainedTransactionManager]] Spring Data 에 추가된 다중 트랜잭션을 연결해서 관리하는 트랜잭션 매니저. * JTA 처럼 트랜잭션이 완전히 동기화 되는 것은 아니고, 여러 트랜잭션을 묶어서 순서대로 커밋/롤백을 진행하는 방식. * JTA 수준은 아니더라도 여러 트랜잭션이 생성되는 애플리케이션에서 각자 ''@Transactional''을 지정하지 않고 하나로 통일하고자 하는 경우에 괜찮을 듯. * Chained Transaction Manager로 묶이는 **각 트랜잭션의 Connection Pool 의 min/max 갯수는 동일하게** 가져가야한다. * 경험상, 서로 다른 동기화 시점을 가지는 트랜잭션은 묶지 않는 것이 좋다. * MQ 사용시, MQ로 받은 데이터를 저장하는데 DB 접속이 끊겼을 경우 retry 를 하고자 한다면 MQ 트랜잭션과 DB 트랜잭션을 함께 묶으면 이 작업이 안된다. DB 트랜잭션이 롤백 될때 MQ 트랜잭션까지 롤백 되어 버려서 재시도할 트랜잭션이 없어지기 때문이다. ===== Transaction 정보 처리 ===== * [[http://docs.spring.io/spring/docs/3.2.10.RELEASE/javadoc-api/org/springframework/transaction/interceptor/TransactionAspectSupport.html|org.springframework.transaction.interceptor.TransactionAspectSupport]] * [[http://docs.spring.io/spring/docs/3.2.10.RELEASE/javadoc-api/org/springframework/transaction/support/TransactionSynchronizationManager.html|TransactionSynchronizationManager]] : 현재 쓰레드의 트랜잭션에 관한 많은 사항을 확인할 수 있다. * [[http://docs.spring.io/spring/docs/3.2.10.RELEASE/javadoc-api/org/springframework/transaction/support/TransactionSynchronizationUtils.html|TransactionSynchronizationUtils]] * [[http://stackoverflow.com/questions/13395794/how-do-i-get-transaction-info-in-spring-whether-transaction-is-commited-or-rollb|hibernate - how do i get transaction info in spring whether transaction is commited or rollback in a declartive transaction management]] if (TransactionSynchronizationManager.isActualTransactionActive()) { TransactionStatus status = TransactionAspectSupport.currentTransactionStatus(); ... } ===== 익명 Anonymous Class에서 현재 서비스의 메소드 호출 ===== * 트랜잭션이 걸린 서비스메소드에서 익명 클래스를 리턴하여 서비스 클래스의 다른 메소드를 호출하게 하면 트랜잭션이 올바르게 작동하지 않는다. @Override @Transactional(readOnly = true) public Iterable findAll() { // true log.info("Outside : readOnly {}", TransactionSynchronizationManager.isCurrentTransactionReadOnly()); return new Iterable() { @Override public Iterator iterator() { // false log.info("In iterable : readOnly {}", TransactionSynchronizationManager.isCurrentTransactionReadOnly()); return findAllReal().iterator(); } }; } @Override @Transactional(readOnly = true) public List findAllReal() { // false log.info("Inside : readOnly {}", TransactionSynchronizationManager.isCurrentTransactionReadOnly()); return bookRepository.findAll(); } * 익명 클래스에서 트랜잭션을 올바로 걸고자 한다면 트랜잭션이 없는 외부에서 익명 클래스를 생성하면서 트랜잭션이 있는 서비스 Bean을 그 안으로 넘겨주어야 한다. // BookAnotherService @Autowired private BookService bookService; @Transactional(readOnly = true) public Iterable findAll() { // true log.info("Outside : readOnly {}", TransactionSynchronizationManager.isCurrentTransactionReadOnly()); return new Iterable() { @Override public Iterator iterator() { // false -> Transaction 영역이 아니므로 false가 맞음. log.info("In iterable : readOnly {}", TransactionSynchronizationManager.isCurrentTransactionReadOnly()); return bookService.findAllReal().iterator(); } }; } // BookService @Override @Transactional(readOnly = true) public List findAllReal() { // true log.info("Inside : readOnly {}", TransactionSynchronizationManager.isCurrentTransactionReadOnly()); return bookRepository.findAll(); } ===== Timeout ===== * ''PlatformTransactionManager#setDefaultTimeout'' 을 통해 기본 timeout 시간을 지정할 수 있다. 기본값은 ''-1''로 JDBC Driver 기본을 따르거나 무제한을 의미한다. * ''@Transactional(timeout="")''을 통해서 각 단위별로 지정가능하다. ===== Programatic Transaction Managemen - TransactionTemplate ===== * Progmatic transaction demarcation. * API와 DB 호출이 한 트랜잭션으로 엮이거나 하면 커넥션 점유 시간이 증가하면서 커넥션 부족에 시달릴 수 있다. * 또한 하나의 Bean 에서 트랜잭션이 없는 메소드가 트랜잭션이 있는 메소드를 호출 할 경우 작동하지 않는다. * 이를 ''@Transactional'' 애노테이션으로 해결하려 들면 복잡도만 더 증가한다. * [[https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/support/TransactionTemplate.html|TransactionTemplate]] 객체를 생성해서 해결하는게 더 깔끔하다. * [[https://www.baeldung.com/spring-programmatic-transaction-management|Programmatic Transaction Management in Spring | Baeldung]] * [[http://www.simplespringtutorial.com/springProgrammaticTransactions.html|Spring Programmatic Transactions]] * [[https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/support/TransactionTemplate.html|TransactionTemplate]] * [[https://www.logicbig.com/tutorials/spring-framework/spring-data-access-with-jdbc/programmatic-transaction.html|Spring - Programmatic Transaction]] * [[http://wikibook.co.kr/article/transaction-management-using-spring/|스프링을 이용한 트랜잭션 관리]] * ''TransactionTemplate''은 속성이 동일할 경우 재사용 가능하므로 동일 속성에 대해 하나의 객체만 만들어서 재사용하면 된다. * [[https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/interceptor/RuleBasedTransactionAttribute.html|RuleBasedTransactionAttribute]] 와 조합하면 복잡한 트랜잭션 규칙을 만들어 낼 수 있을 것 같지만 사실은 아니다. 해당 클래스의 복잡한 설정값을 모두 제거한 [[https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/TransactionDefinition.html|TransactionDefinition]]을 사용해서 처리한다. ===== 참여 중인 트랜잭션이 실패하면 기본정책이 전역롤백 ===== * [[http://woowabros.github.io/experience/2019/01/29/exception-in-transaction.html|응? 이게 왜 롤백되는거지? - 우아한형제들 기술 블로그]] * A -> B 메소드 호출되고 **A** 메소드에서 트랜잭션 시작되고 B 도 ''@Transactional(propagation=REQUIRED)''일 경우, B에서 예외가 발생하면 A에서 비록 예외를 잡아서 먹어버리더라도 트랜잭션은 롤백 된다. * Hibernate의 경우 Hibernate 메소드 호출시 롤백 되면 무조건 롤백 마킹해버린다. * [[https://github.com/spring-projects/spring-framework/blob/4560dc2818ae1d5e1bc5ceef89f1b6870700eb1f/spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java#L265|globalRollbackOnParticipationFailure]] * [[https://dzone.com/articles/most-common-spring-transactional-mistakes|Spring @Transactional Mistakes Everyone Makes - DZone Java]]