본문 바로가기
이직준비/기술면접

[JPA] 영속성 컨텍스트에 따른 엔티티 조회.

by Geunny 2022. 5. 17.
반응형

JPA 에서 Entity를 관리하는 영속성 컨텍스트는 정말 중요한 개념이다.

 

이번 이직을 준비하면서 기술면접으로 물어본 질문중

 

"@Transactional 이 있는 서비스를 한단계 위 서비스에서 다른 트랜잭션으로 묶었을때

해당 객체에 연관된 객체를 불러오게 되면 어떻게 되는가?"

 

대충 이런식의 질문 이었다.

 

[퍼사드패턴]으로 상위 트랜잭션에서 하위 서비스에 새로운 트랜잭션으로 엔티티를 불러오고

해당 서비스에서 이미 트랜잭션이 종료되고

해당 엔티티는 비영속 상태가 되어 더이상 엔티티로 관리되지 않고 

그 상태에서 해당 엔티티와 연관관계가 있는 객체를 불러오면

해당 연관 객체를 가져올수 없다고 대답은 했지만..

실제로 어떻게 동작하는지 확인하기 위해 직접 코드를 작성해 보았다.

 

 

구성

아주 간단한 구성으로

 

회원(Member) 와 제품(Product) 의 관계를 1:N의 연관관계를 주어 코드를 실행해 보았다.

 

1. Member 객체

@Entity
@Getter
@NoArgsConstructor
@Table(name = "member")
@Builder
@ToString
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long memberId;

    @Column(name = "member_name")
    private String name;

    @OneToMany(mappedBy = "member")
    @JsonBackReference
    List<Product> products;

    public void buyProduct(Product product) {
        if(this.products == null) products = new ArrayList<>();
        this.products.add(product);
    }
}

2. Product 객체

@Entity
@Getter
@NoArgsConstructor
@Builder
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long productId;

    private String productName;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    @JsonManagedReference
    private Member member;
}

 

순환참조 이슈때문에 Dto를 사용해도 되지만

불필요한 시간을 아끼기 위해 @JsonManagedReference 와 @JsonBackReference 어노테이션을 이용하였다.

더보기

@JsonManagedReference와 @JsonBackReference 어노테이션 사용

 

Jackson 2.0 버전 이전에 순환 참조를 해결하기 위해서 사용했던 어노테이션입니다. 

 

  • @JsonManagedReference
    • 양방향 관계에서 정방향 참조할 변수에 어노테이션을 추가하면 직렬화에 포함된다
  • @JsonBackReference
    • 양방향 관계에서 역방향 참조로 어노테이션을 추가하면 직렬화에서 제외된다

출처 : advenoh님 블로그 [https://advenoh.tistory.com/53] 

 

각 디비에 저장된 데이터는 아래와 같다.

1. Member

2. Product

 

이제 여기서 테스트 해볼 내용은

 

퍼사드패턴을 이용하여 해당 서비스에 유저서비스를 위치시키고

유저서비스로 해당 유저조회후

트랜잭션을 종료시킨 다음

유저의 제품을 가져오는 메서드를 실행해 볼 것이다.

 

1. MemberService

 

@Service
@RequiredArgsConstructor
public class MemberService {
    private final MemberRepository memberRepository;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Optional<List<Member>> findAll() {
        return Optional.ofNullable(memberRepository.findAll());
    }
}

2. MemberFacade

@Service
@RequiredArgsConstructor
@Slf4j
public class MemberFacade {
    private final MemberService memberService;
    private final ProductService productService;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Object test(){
        Member first = memberService.findAll().get().get(0);
        log.info(""+first.getProducts());
        return first;
    }

 

Repository 는 따로 구현한 것이 없고 단순히 JPARepositroy<Member, Long> 을 상속받은 인터페이스로 사용하였다.

 

면접에서의 나는... 단순히 개념만 알기에 트랜잭션 종료후 연관객체를 가져오면

null 이 나올것 같다고 추측했으나

 

실제 실행해 보니 스프링은 역시나 에러처리가 뛰어나단 것을 알게 되었다.

 

해당 소스를 실행해 보면

 

해당 이미지 처럼 LazyInitializationException 을 발생하게 된다.

 

만약 여러 트랜잭션으로 엔티티를 관리하게 될시 해당 익셉션이 발생할수 있어 매우 조심해야한다.

 

이를 해결하기 위한 방법으로는

 

JPA Entity 조회시 Fetch join 을 이용하여 연관 엔티티를 가져오거나 @EntityGraph 어노테이션으로

JPARepository 메서드를 선언해 주면 해결이 가능하다.

 

위 방식으로 해결하면 순환참조 또한 해결이 가능하다.


 

저도 공부하는 중이라 혹시라도 잘못된 부분 있으면 댓글로 알려주시면 감사하겠습니다..하하

댓글