[JPA]SpringBoot의 JPA와 @Transactional

Sunghun Kim·2024년 11월 5일

Jpa

목록 보기
3/10

SpringBoot 환경의 Jpa

  • SpringBoot 환경에서는 EntityManagerFactory와 EntityManager를 자동으로 생성해줍니다.
    • application.properties에 DB 정보를 전달해 주면 이를 토대로 EntityManagerFactory가 생성됩니다 그리고 EntityManager 조차도 자동으로 생성해준다.
@PersistenceContext
EntityManager em;
  • @PersistenceConext 애너테이션을 사용하면 자동으로 생성된 EntityManager를 주입받아 사용할 수 있습니다.

Spring의 Transaction

  • Spring의 트랜잭션
    • Spring 프레임워크에서는 DB의 트랜잭션 개념을 애플리케이션에 적용할 수 있도록 트랜잭션 관리자를 제공합니다.

      @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);
      			}
      		}
      
      						...
      }
    • 예시 코드 처럼 @Transactional 애너테이션을 클래스 가장상단이나 메서드에 추가하면 쉽게 트랜잭션 개념을 적용(트랜잭션 환경 설정) 할 수 있습니다.

      • 메서드가 호출되면, 해당 메서드 내에서 수행되는 모든 DB 연산 내용은 하나의 트랜잭션으로 묶입니다.
      • 이때, 해당 메서드가 정상적으로 수행되면 트랜잭션을 커밋하고, 예외가 발생하면 롤백합니다.
      • 클래스에 선언한 @Transactional은 해당 클래스 내부의 모든 메서드에 트랜잭션 기능을 부여합니다.
      • 클래스에 @Transactional이(readOnly = true)로 걸려있고 + save 메서드는 @Transactional 어노테이션이 추가되어있기 때문에
        readOnly = true 옵션인 @Transactional을 덮어쓰게 되어 readOnly = false 옵션으로 적용됩니다.
        • readOnly = true 옵션(최적화)
          - 트랜잭션에서 데이터를 읽기만 할 때 사용됩니다.
          - 이 속성을 사용하면 읽기 작업에 대한 최적화를 수행할 수 있습니다.
          - 만약, 해당 트랜잭션에서 데이터를 수정하려고 하면 예외가 발생하기 때문에 주의해야합니다.

Transaction 테스트

  • 트랜잭션 테스트
  • (원래 테스트 할때는 너 테스트 하는거지? 하고 Rollback을 자동으로 해버려 @Rollback(value = false) 옵션을 줬다.)
    @Test
    @Transactional 
    @Rollback(value = false) // 테스트 코드에서 @Transactional 를 사용하면 테스트가 완료된 후 롤백하기 때문에 false 옵션 추가
    @DisplayName("메모 생성 성공")
    void test1() {
        Memo memo = new Memo();
        memo.setUsername("Robbert");
        memo.setContents("@Transactional 테스트 중!");
    
        em.persist(memo);  // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
    }
    • 트랜잭션이 적용되어 DB 작업이 성공했습니다.

      💡

      @Disabled를 test메서드에 사용하면 이 Test는 더이상 하지 않겠다는 것으로 오류가 나는 테스트라도 테스트가 수행이 되지 않아 초록색으로 나온다.

      @Test
      @DisplayName("메모 생성 실패")
      void test2() {
          Memo memo = new Memo();
          memo.setUsername("Robbie");
          memo.setContents("@Transactional 테스트 중!");
      
          em.persist(memo);  // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
      }
    • 트랜잭션이 적용되지 못해 작업이 취소되었습니다.(오류 발생)

    • 즉, JPA를 사용하여 DB에 데이터를 저장, 수정, 삭제 하려면 트랜잭션 적용이 반드시 필요합니다.

    • 조회 작업은 단순하게 데이터를 읽기만 하기 때문에 트랜잭션 적용이 필수는 아닙니다.

    • 다만 조회의 경우에도 트랜잭션 환경이 필요한 경우가 있을 수 있기 때문에 조회 작업 기능만 존재하는 메서드일 경우에만 앞서 본 예시처럼 readOnly = true 옵션이 설정된 @Transactional을 적용하면 좋습니다.

영속성 컨텍스트와 트랜잭션의 생명주기

  • 스프링 컨테이너 환경에서는 영속성 컨텍스트와 트랜잭션의 생명주기가 일치합니다.

    • 쉽게 설명하자면 트랜잭션이 유지되는 동안은 영속성 컨텍스트도 계속 유지가 되기 때문에 영속성 컨텍스트의 기능을 사용할 수 있습니다.

    • 따라서 앞에서 작성한 테스트 코드 메서드에 트랜잭션이 적용되지 않았기 때문에 영속성 컨텍스트가 유지되지 못해 오류가 발생했었습니다.

    ⚠️ Spring은 어떻게 Service 부터 Repository 까지 Transaction을 유지할 수 있는 걸까요?

    • Service의 트랜잭션이 적용된 메서드에서 Repository의 메서드를 호출할 때 무언가 처리되고 있는 것이 있는 걸까요?
    • Spring에서는 이러한 상황에서 트랜잭션을 제어할 수 있도록 트랜잭션 전파 기능을 제공하고 있습니다.

트랜잭션 전파

  • @Transactional에서 트랜잭션 전파 옵션을 지정할 수 있습니다.

  • 트랜잭션 전파의 기본 옵션은 REQUIRED 입니다.

  • REQUIRED 옵션은 부모 메서드에 트랜잭션이 존재하면 자식 메서드의 트랜잭션은 부모의 트랜잭션에 합류하게됩니다.

트랜잭션 전파 테스트(커밋, DirtyChecking 어디서 되는지가 다르다)

MemoRepository

@Transactional
public Memo createMemo(EntityManager em) {
    Memo memo = em.find(Memo.class, 1);
    memo.setUsername("Robbie");
    memo.setContents("@Transactional 전파 테스트 중!");

    System.out.println("createMemo 메서드 종료");
    return memo;
}

Test

@SpringBootTest
public class TransactionTest {

    @PersistenceContext
    EntityManager em;

    @Autowired
    MemoRepository memoRepository;

    @Test
    @Transactional
    @Rollback(value = false)
    @DisplayName("트랜잭션 전파 테스트")
    void test3() {
        memoRepository.createMemo(em);
        System.out.println("테스트 test3 메서드 종료");
    }
}

  • 실행 결과 자식 메서드 createMemo가 종료될 때 update가 실행되는 것이 아니라 부모 메서드에 트랜잭션이 합류되면서 부모 메서드가 종료된 후 트랜잭션이 커밋될 때 update가 실행된 것을 확인할 수 있습니다.

부모 메서드 test3의 @Transactional @Rollback(value = false)을 주석 처리하고 다시 한번 update를 시도

  • 합류할 부모 트랜잭션이 없기 때문에 이처럼 자식 메서드가 종료된 후 트랜잭션이 커밋되면서 update가 실행된 것을 확인할 수 있습니다.
profile
BackEnd Developer!!

0개의 댓글