ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot. JPA Repository insert multi table under the same Transaction.
    카테고리 없음 2023. 7. 20. 12:46
    728x90

    공부할 겸 JPA를 한번 사용해 보려고 한다.

    환경.

    java 17

    spring boot 3.1.1

     

    목표.

      한 트랜젝션에서 여러개의 테이블에 insert나 update 작업이 필요하면 어떡하지?

     

    문제.

    JPA의 레파지토리를 보면 CrudRepository 인터페이스부터 save()를 제공한다.

    이 save 메소드는 SimpleJpaRepository에서 overriding해서 구현되어진다.

    @Repository
    @Transactional(readOnly = true)
    public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
        ...
        @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);
            }
        }
        ...
    }

    문제는 save()에 @Transactional이 붙어있어 서비스단에서 save()를 사용하면 바로 db에 적용된다는 것이다.

    해결.

    해결 방법은 간단하다. 늘 그랬듯 필요한 곳에서 트랜젝션을 걸어주면 된다.

    @Service
    @RequiredArgsConstructor
    public class UserService {
        private final UserRepository repository;
    
        @Transactional
        public User signUp(SignUp signUp){
            SignUp signUp1 = new SignUp("abc");
            SignUp signUp2 = new SignUp("abc");
    
            SignUp result1 = repository.save(signUp1);
            SignUp result2 = repository.save(signUp2);
           
          	return null;
        }
    }

    형태에서 알 수 있듯 @Transactional은 AOP이기 때문에 메소드가 끝나면 트랜젝션에 정의된 로직을 실행한다.

    • 대표적으로 커밋 이나 롤백
    • AOP 이기 때문에 private 메소드에서는 작동 안 할 것이다.

     

    여기까지 오면 한가지 의문이 생긴다.

    SimpleJpaRepository의 save()에도 @Transactional이 붙어 있다는 것이다. 그러면 save()가 끝났을 때 바로 커밋이 되어야 하는 것 아닌가?  => 아니다!!!

    하나 더 생각해야한다. 지금 처럼 @Transactional 안에 @Transactional이 붙어있는 메소드를 호출하면 어떻게 될까?

    // 대략 이런 형태
    @Transactional
    method(){
        @Transactional
        save();
    }

    가장 바깥에 있는 @Transactional만 실행한다.
        == 처음 만난 @Transactional만 실행한다.
        == 안쪽의 @Transactional은 무시한다.

     

    @Transactional을 따라가보자.

    save()를 호출하는 메소드에 @Transactional을 붙인 것과 안붙인 것을 비교했다.

    똑같이 진행 되다가 처음 차이가 발생한 곳은 TransactionAspectSupport.java 에서 TransactionInfo가 생성되고 값이 셋팅 될 때이다. 트랜젝션 실행에 영향을 가장 크게 미친다고 생각되는 것은 trasactionStatus.newTransaction과 oldTransactionInfo이다.

    @Transactional 붙였을 때 @Transactional 안붙였을 때

    마치 한 쌍처럼 움직이는데 oldTransactionInfo에 값이 있으면 newTransaction은 false이고 반대의 경우는 true이다.

    java 코드
    @Transactional1 <- oldTransaction:null
    	@Transactional2 <- oldTransaction:Transactional1
    		@Transactional3 <- oldTransaction:Transactional2
    
    txInfo
    TransactionInfo3
        > newTransaction: false
        > oldTransactionInfo: TransactionInfo2
            	> newTransaction: false
        		> oldTransactionInfo: TransactionInfo1
                    	> newTransaction: true
        				> oldTransactionInfo: null

    method 실행 순서에 따라 스택에 쌓이고 마지막에 가장 바깥에 있는 @Transactional만 실행하는 구조인 듯 하다.

    커밋은 마지막 실행전에 newTransaction를 확인한다.

    AbstractPlatformTransactionManager.java		
        processCommit(){
        	...
        	else if (status.isNewTransaction()) {
               if (status.isDebug()) {
                  logger.debug("Initiating transaction commit");
               }
               unexpectedRollback = status.isGlobalRollbackOnly();
               doCommit(status);
            }
            ...
        }

     

    결론.

      DB 작업을 한 트랜젝션 아래에서 하고 싶으면 그냥 @Transactional을 쓰자. 단 AOP이기 때문에 private 메소드는 안된다.  

     

    -----------------------------------

    추가적인 질문이 생길 수 있다. 다음에 시간이 나면 정리해보겠다. (우선 키워드만 남긴다)

    • jpa에서 flush는 무슨 일을 하나?
      - 영속성 컨텍스트(persistence context), flush
    • AOP는 왜 private에서 작동 안하나?
      - AOP proxy
    728x90
    반응형

    댓글

Designed by Tistory.