SpringBoot의 JPA

금은체리·2023년 11월 14일
1

Spring

목록 보기
4/49

SpringBoot 환경에서의 JPA

  • SpringBoot 환경에서는 EntityManagerFactory와 EntityManager를 자동으로 생성해줌
    • 메모장 프로젝트 JPA 설정
      • build.gradle
        			// JPA 설정
        			implementation 'org.springframework.boot:springboot-starter-data-jpa'
        	
        • application.properties : Hibernate 설정
          - show_sql, format_sql, use_sql_comments 옵션
          : Hibernate가 DB에 요청하는 모든 SQl을 보기좋게 출력 해줌
        • ddl-auto

          - create : 기존 테이블 삭제 후 다시 생성(DROP + CREATE)
          - create-drop : create와 같으나 종료 시점에 테이블 DROP
          - update : 변경된 부분만 반영
          - validate : Entity와 테이블이 정상 매핑되었는지만 확인
          - none: 아무것도 하지 않음
    • application.properties에 DB 정보를 전달해 주면 이를 토대로 EntityManagerFactory가 생성됨
@PersistenceContext
EntityManager em;
  • @PersistenceContext 애너테이션을 사용하면 자동으로 생성된 EntityManager를 주입받아 사용 가능

Spring의 트랜잭션

  • 트랜잭션: DB의 상태를 변화시키기 위해 하나의 논리적 기능을 수행하기 위한 작업의 단위
    • 상황에 따라 여러개 만들어 질 수 있음
      • A가 B에게 만 원을 송금한다고 가정
        1. A의 통장에서 만 원 인출
        2. B의 통장에 만 원 입금
          = 인출(기능) + 입금(기능) ➡️ "송금"이라는 하나의 기능으로 됨
    • 여러개의 쿼리가 한 번에 처리되는 상황에서 문제가 생기면 안되기 때문에 사용
  • 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 해당 클래스 내부의 모든 메서드에 트랜잭션 기능을 부여함
    • 이때, save 메서드는 @Transactional 애너테이션이 추가되어있기 때문에 readOnly = true 옵션인 @Transactional을 덮어쓰게 되어 readOnly = false 옵션으로 적용됨
      • readOnly = true 옵션
        • 트랜잭션에서 데이터를 읽기만 할 때 사용
        • 이 속성을 사용하면 읽기 작업에 대한 최적화 수행 가능
        • 만약, 해당 트랜잭션에서 데이터를 수정하려고 하면 예외가 발생하기 때문에 주의해야함

@Transactional

  • 트랜잭션 테스트
@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 작업이 성공함
@Test
@DisplayName("메모 생성 실패")
void test2() {
    Memo memo = new Memo();
    memo.setUsername("Robbie");
    memo.setContents("@Transactional 테스트 중!");

    em.persist(memo);  // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
}
  • 트랜잭션이 적용되지 못해 작업이 취소됨
  • 즉, JPA를 사용하여 DB에 데이터를 저장, 수정, 삭제 하려면 트랜잭션 적용이 반드시 필요
    • 조회 작업은 단순하게 데이터를 읽기만 하기 때문에 트랜잭션 적용이 필수는 아님
      • 다만 조회의 경우에도 트랜잭션 환경이 필요한 경우가 있을 수 있기 떄문에
      • 조회 작업 기능만 존재하는 메서드일 경우 앞서 본 예시처럼 readOnly = true 옵션이 설정된 @Transactional을 적용하면 👍🏻

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

  • 스프링 컨테이너 환경에서는 영속성 컨텍스트와 트랜잭션의 생명주기가 일치
  • 쉽게 설명하자면, 트랜잭션이 유지되는 동안은 영속성 컨텍스트도 계속 유지가 되기 때문에 영속성 컨텍스트의 기능 사용 가능
  • 따라서 앞에서 작성한 테스트 코드 메소드에 트랜잭션이 적용되지 않았기 때문에 컨텍스트가 유지되지 못 해 오류가 발생
  • Inser, Update, Delete 하려면 트랜잭션이 꼭 필요하다❗️

트랜잭션 전파

  • @Transactional에서 트랜잭션 전파 옵션 지정 가능
  • 트랜잭션 전파의 기본 옵션은 REQUIRED
  • REQUIRED 옵션은 부모 메서드에 트랜잭션이 존재하면 자식 메서드의 트랜잭션은 부모의 트랜잭션에 합류
  • createMemo : 자식 메서드
@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;
}
  • test3 : 부모 메서드
package com.sparta.memo;

import com.sparta.memo.entity.Memo;
import com.sparta.memo.repository.MemoRepository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;

@SpringBootTest
public class TransactionTest {

    @PersistenceContext
    EntityManager em;

    @Autowired
    MemoRepository memoRepository;

    @Test
    @Transactional
    @Rollback(value = false) // 테스트 코드에서 @Transactional 를 사용하면 테스트가 완료된 후 롤백하기 때문에 false 옵션 추가
    @DisplayName("메모 생성 성공")
    void test1() {
        Memo memo = new Memo();
        memo.setUsername("Robbert");
        memo.setContents("@Transactional 테스트 중!");

        em.persist(memo);  // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
    }

    @Test
    @Disabled
    @DisplayName("메모 생성 실패")
    void test2() {
        Memo memo = new Memo();
        memo.setUsername("Robbie");
        memo.setContents("@Transactional 테스트 중!");

        em.persist(memo);  // 영속성 컨텍스트에 메모 Entity 객체를 저장합니다.
    }

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

  • 실행 결과 자식 메서드 createMemo가 종료될 때 update가 실행되는 것이 아니라 부모 메서드에 트랜잭션이 합류되면서 부모 메서드가 종료된 후 트랜잭션이 커밋될 때 update가 실행된 것을 확인 가능
profile
전 체리 알러지가 있어요!

0개의 댓글