1 분 소요

1. 개요

JPA 를 이용한 테스트 프로젝트를 진행하던 중 초기 데이터를 대량으로 INSERT 해주는 빈을 생성하려 하였다.

@PostConstruct 를 통해 Spring Boot 애플리케이션이 기동되는 시점에 작성해둔 로직에 의해 테스트 데이터들이 persist 되는 방식이었다.

하지만 JPA 를 통해 데이터를 Insert 하기 위해서는 트랜잭션이 필요하고, 뭔가 찝찝한 느낌은 들었지만 대충 @Transactional 애노테이션만 붙이고 작동을 시켜보았다.

코드를 보면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
@Transactional
public class TestDataInit {

    @PersistenceContext
    private EntityManager em;

    @PostConstruct
    public void init() {
        for (int i = 0; i < 50; i++) {
            Member member = new Member("member" + i);
            Reservation reservation = new Reservation(member);
            em.persist(reservation);
        }
    }
}

2. 문제 발생

하지만 역시나 다음과 같은 에러메시지가 나타나면서 기동에 실패하였다.

1
Error creating bean with name 'testDataInit': Invocation of init method failed; nested exception is javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call

문제는 AOP 실행과 스프링 컨테이너 초기화의 순서 차이때문에 발생했는데, 스프링 컨테이너가 전부 초기화 되어야 AOP가 적용되기 때문에 @PostConstruct 애노테이션 (컨테이너 초기화 시점) 에는 선언적 트랜잭션 관리를 사용할 수 없었던 것이다.

3. 해결

가장 간단한 방법은 선언적 트랜잭션 관리 (AOP 사용) 에서 프로그래밍 방식 트랜잭션 관리로 전환하는 것이다.

PlatformTransactionManager 인터페이스는 스프링 부트에서 자동으로 주입해주므로 TrnasactionTemplate 을 생성하여 트랜잭션을 다음과 같이 수동으로 시작해주면 스프링 컨테이너 시작 시점에 트랜잭션을 획득하여 jpa 를 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
@PostConstruct
public void init() {

    TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
    transactionTemplate.execute(status -> {
        for (int i = 0; i < 50; i++) {
            Member member = new Member("member" + i);
            Reservation reservation = new Reservation(member);
            em.persist(reservation);
        }
        return null;
    });
}