인프런 커뮤니티 질문&답변

@Todo님의 프로필 이미지

작성한 질문수

실전! 스프링 데이터 JPA

Auditing

@Transactional관련 질문 입니다.

해결된 질문

작성

·

1.2K

1

package study.querydsl.repository;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
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;
import study.querydsl.entity.Member;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.ArrayList;
import java.util.List;

@SpringBootTest
@Transactional
@Rollback(false)
class MemberRepositoryTest {
@PersistenceContext
private EntityManager em;

@Autowired
private MemberRepository memberRepository;


@Test
public void test() throws InterruptedException {
List<Member> members = makeList();
for (Member member : members) {
test1(member.getId());
Thread.sleep(1000);
}
}

private List<Member> makeList() throws InterruptedException {
List<Member> members = new ArrayList<>();
for (int i = 0 ; i < 20 ; i++) {
Member member = new Member("gon " + i, i);
members.add(member);
}
memberRepository.saveAll(members);
em.flush(); //<--- insert query는 생성되나 db 에는 아직 없음.

Thread.sleep(5000);
return members;
}

protected void test1(Long id) {
Member member = memberRepository.findById(id).get();
member.setUsername("Park");
memberRepository.save(member);
em.flush(); //<--- update query는 생성되나 db 에는 아직 없음.
}
}

안녕하세요 

위와 같은 예제에서 makeList 메소드에서 생성된 리스트를 실 db에 insert를 하고 

각 리스트 아이템을 처리하는 test1 메소드에서 처리된 아이템을 insert 를 하고 싶은데 em.flush를 해도 insert, update 쿼리만 발생되고 db에는 아직 결과가 들어오지 않고 test가 완전히 종료가 되는 순간 db에 값이 들어온것으로 확인 됩니다. 

이를 해결하기 위해서 class에 @transactional 을 제거하고 test1 메소드에 @Transactional을 붙이니 의도한대로 makelist에서 리스트 결과가 한번 db에 저장되고 

test1 메소드에서 처리된 결과도 순차적으로 db에 인서트 되는것으로 확인 했으나 연관관계가 존재하는 경우 fetch lazy 동작이 no session으로 안되던데 이를 해결하는 방법이 있는지요. 

질문을 정리하면 

1. Transactional 중간에 db에 값을 쓰는 방법이 있는지. 

2. class에서 @Transactional을 제거 하고 test1에 @Transactional을 붙이면 연관관계 fetch lazy가 동작을 하지 않는 이유가 무엇인지 

(제가 이해를 했을땐 test1 부터 영속성 컨텍스트가 관리되어 이후 메소드 호출부터는 transaction이 걸려서 fetch lazy가 동작될거라고 예상했습니다. )

친절하고 자세한 답변 늘 감사드립니다. 

답변 4

2

김영한님의 프로필 이미지
김영한
지식공유자

스프링의 @Transactional은 AOP를 사용하는데, 같은 클래스(인스턴스)의 메서드 안에서 호출할 때는 AOP가 적용이 안됩니다.

그래서 test1()을 호출해도 @transactional이 소용이 없었던 것이지요.

별도의 클래스를 만들고, 거기에 test1을 옮기시면 원하시는데로 동작할꺼에요^^

0

@Todo님의 프로필 이미지
@Todo
질문자

확인되었습니다. 감사합니다. 

0

@Todo님의 프로필 이미지
@Todo
질문자

답변 감사합니다. 

우선 class에서 @Transactional을 제거하고 Method에서도 @Transactional이 없으면 em.flush가 있는곳에서 

하기와 같은 예외를 발생시킵니다. 이것은 Transaction 밖에서 em.flush를 사용하면 안된다는 의미로 생각됩니다. 

javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'flush' call

질문1를 확인하기 위해 우선 em.flush를 모두 삭제하고 확인을 해보니

A, B의 동작이 같았는데 makelist()에서 saveAll을 하고 db에서 값을 확인할수 있었는데 

이는 saveAll 구현체에서 @Transactional을 사용해서 save 가 완료되면 트랜잭션이 종료되어 commit이 되었기 때문인것 같은데 

문제는 test1에 @Transactional이 붙어 있으나 없으나 save가 되는 순간 값이 업데이트 됨을 확인하였습니다. 

제가 잘 이해가 안되는게 test1 메소드에 @Transactional을 붙이면 메소드가 실행되는 순간 트랜잭션이 걸리고

메소드가 종료되는 순간 커밋이 이루어져서 db에 반영되어야 할것 같은데 트랜잭션이 안걸리고 있습니다.

(save를 빼도 변경감지로 db가 자동으로 업데이트 되지도 않습니다. )

package study.querydsl.repository;

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;
import study.querydsl.entity.Member;

import java.util.ArrayList;
import java.util.List;

@SpringBootTest
@Rollback(false)
class MemberRepositoryTest {
// @PersistenceContext
// private EntityManager em;

@Autowired
private MemberRepository memberRepository;


@Test
public void test() throws InterruptedException {
List<Member> members = makeList();
for (Member member : members) {
test1(member.getId());
Thread.sleep(1000);
}
}

private List<Member> makeList() throws InterruptedException {
List<Member> members = new ArrayList<>();
for (int i = 0 ; i < 20 ; i++) {
Member member = new Member("gon " + i, i);
members.add(member);
}
memberRepository.saveAll(members);


Thread.sleep(5000);
return members;
}

@Transactional
protected void test1(Long id) {
Member member = memberRepository.findById(id).get();
member.setUsername("Park");
memberRepository.save(member);

System.out.println("temp");
}
}

왜 그런건가요 ㅠ

0

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. Yeonggon Ha님^^ 좋은 질문입니다.

먼저 1번 질문에 대한 답은 다음과 같습니다.

1. Transactional 중간에 db에 값을 쓰는 방법이 있는지. 

-> 없습니다. 왜냐하면 그게 바로 트랜잭션이기 때문입니다.

제가 바로 정답을 말씀드릴 수도 있지만! 그러면 얻어가시는 것이 얼마 안되기에, Yeonggon Ha님을 위해 반대로 질문을 드려보겠습니다.

질문1

A. class에서 @Transactional을 제거 하고 test1에 @Transactional 붙이기

B. class에서 @Transactional을 제거 하고 test1에도 @Transactional 제거하기

이 둘을 실제로 돌려보고, 차이점을 찾아주세요.

질문2

질문1을 이해한 다음에, 스프링 데이터 JPA 구현체 분석편을 다시 봐주세요. 그리고 작성하신 현재 코드를 다시 분석해주세요.

그러면 답글 기다릴께요^^

@Todo님의 프로필 이미지

작성한 질문수

질문하기