카테고리 없음

Spring Boot. JPA Repository insert multi table under the same Transaction.

neunggu 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
반응형