해결된 질문
작성
·
441
1
안녕하세요 강사님. 항상 훌륭한 강의 감사드립니다.
스프링 MVC 1, 2편에서 사용했던 프로젝트에 JPA를 적용시키는 도중 궁금한점이 하나 생겨서 질문드립니다.
TestInitData 클래스에 @PostConstruct로 데이터베이스에 초기 데이터들을 넣어두려고 합니다.
package com.myservice.web.test;
import com.myservice.domain.item.Item;
import com.myservice.domain.item.ItemRepository;
import com.myservice.domain.member.Grade;
import com.myservice.domain.member.Member;
import com.myservice.domain.member.MemberRepository;
import com.myservice.domain.member.MemberService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
@Slf4j
@Component
@RequiredArgsConstructor
@Transactional
public class TestDataInit {
private final ItemRepository itemRepository;
private final MemberService memberService;
private final MemberRepository memberRepository;
/**
* 테스트용 데이터 추가
*/
@PostConstruct
public void init() {
itemRepository.save(new Item("itemA", 10000, 10));
itemRepository.save(new Item("itemB", 20000, 20));
itemRepository.save(new Item("itemC", 15000, 15));
Member member1 = new Member();
member1.setLoginId("manager");
member1.setPassword("manager");
member1.setUsername("최한슬");
member1.setGrade(Grade.MANAGER);
Member member2 = new Member();
member2.setLoginId("user");
member2.setPassword("user");
member2.setUsername("USER");
//바로 memberRepository.save로 접근하면 현재 스레드에서 사용할 수 있는 EntityManager가 없다고 오류 발생
memberRepository.save(member1);
memberRepository.save(member2);
//memberService.save -> memberRepository.save로 접근하면 정상적으로 작동
memberService.save(member1);
memberService.save(member2);
}
}
또한, memberRepository와 memberService는 다음과 같습니다.
[memberRepository]
package com.myservice.domain.member;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface MemberRepository {
Long save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByLoginId(String loginId);
List<Member> findAll();
}
package com.myservice.domain.member;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
@Repository
@RequiredArgsConstructor
@Primary
public class JpaMemberRepository implements MemberRepository {
private final EntityManager em;
@Override
public Long save(Member member) {
em.persist(member);
return member.getId();
}
@Override
public Optional<Member> findById(Long id) {
Member member = em.find(Member.class, id);
return Optional.ofNullable(member);
}
@Override
public Optional<Member> findByLoginId(String loginId) {
Member member = em.createQuery("select m from Member m where m.loginId = :loginId", Member.class)
.setParameter("loginId", loginId)
.getResultStream()
.findAny()
.orElse(null);
return Optional.ofNullable(member);
}
@Override
public List<Member> findAll() {
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
}
[memberService]
package com.myservice.domain.member;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
public void save(Member member) {
memberRepository.save(member);
}
}
현재 MemberService에는 @Transactional이 걸려있고, memberRepository에는 @Transactional 걸려있지 않습니다.
궁금한점은 TestDataInit 클래스의 init() 메서드에서 바로 memberRepository로 접근하게되면 사용할 수 있는 EntityManager가 없다고 나오며,
memberService->memberRepository로 접근하게 되면 정상적으로 처리되는 것을 확인하였습니다.
두 방식 모두 결국 memberRepository를 통해 save를 수행하게 되는데 바로 memberRepository의 접근은 오류가 발생하고 memberService를 통한 접근은 정상적으로 처리되는 이유를 모르겠습니다.
답변 4
1
2021. 07. 30. 21:41
아 한슬님 무엇이 문제인지 알겠네요.
@Transactional이 @PostConstruct에 함께 걸려있으면 스프링 라이프사이클 문제때문에 @Transactional이 정상 동작하지 않을 수 있습니다.
@PostConstruct가 먼저 실행되고 이후에 @Transactional AOP가 적용되기 때문입니다.
간단한 해결방안은 제가 강의에서 한 것 처럼 초기화 하는 메서드와 초기화를 실행하는 메서드를 분리해주세요.
그러면 라이프사이클 문제가 해결됩니다.
감사합니다.
1
2021. 07. 29. 23:25
안녕하세요. 최한슬님
다음을 참고해주세요.
https://www.inflearn.com/questions/158967
https://www.inflearn.com/questions/159466
감사합니다.
0
2021. 07. 30. 23:43
감사합니다. 강사님 ㅠㅠ, 몇일동안 많이 고민했었는데 강사님 덕분에 속이 뚫린 느낌입니다!
AOP는 모든 스프링 빈이 처리되고 나서 적용되기 때문에 TestDataInit 클래스가 빈에 등록될때 호출되는 @PostConstruct 시점에는 AOP 중 하나인 @Transactional이 적용되는 것을 보장해주니 않는 것이군요.
그렇다면 MemberService로 접근해서 성공한 것도 정확히 말한다면 @Transactional이 보장되지 않지만 운이 좋아서 성공한 것이라고 생각하면 되는 것일까요?
0
2021. 07. 30. 00:13
안녕하세요 강사님, 올려주신 두 링크를 확인하고 많은 고민을 해봤습니다.
제가 이해한 바는 다음과 같습니다.
[첫번째 링크]
Repository에는 @Transactionl이 없기 때문에 EntityManger에 우선 프록시 객체를 주입해준 다음 다른 트랜잭션에서 해당 Repository에 접근하게 되면 그 EntityManger를 사용하겠다.
[두번쨰링크]
메서드 A에 @Transactional을 걸었다면 메서드 내부에 트랜잭션이 전파되기 때문에 A에서 호출되는 다른 기능들은 동일한 트랜잭션이다.
두 링크를 다음과 같이 이해하였습니다.
그렇다면 init() 메서드를 트랜잭션 A라고 가정하겠습니다.
[MemberService]
init()에서 memberService.save 호출 => 트랜잭션 A
memberService에 @Transactional이 있음, 따라서 memberService.save => 트랜잭션 B
memberRepository.save, Repository는 @Transactional이 없으니 EntityManager에는 프록시 객체가 있음. memberService로 인해 호출되었으니 meberService의 EntityManger를 사용해서 디비에 저장 => 트랜잭션 B
[MemberRepository]
init()에서 memberRepository 호출 => 트랜잭션 A
memberRepository.save, Repository는 @Transactional이 없으니 EntityManager에는 프록시 객체가 있음. init()로 인해 호출되었으니 init()의 EntityManger를 사용해서 디비에 저장 => 트랜잭션 A
그렇다면 두 방법 모두 EntityManger에는 프록시객체가 아닌 트랜잭션 A or B의 EntityManger가 존재하는 것이 아닌건가요?
둘다 존재한다면 MemberService를 거치지 않고 바로 MemberRepository.save()로 처리했을때 오류가 발생하는지 모르겠습니다.
2021. 07. 30. 23:47
MemberService로 접근하는 것은 @PostConstruct 본인이 아니라 다른 외부 객체를 호출한 것이기 때문에 이때는 @Transactional이 잘 적용됩니다.
의존관계 주입을 받을 때는 AOP가 이미 적용된 부분을 받게 됩니다.
감사합니다.