🤍 전 강의 25% 할인 중 🤍

2024년 상반기를 돌아보고 하반기에도 함께 성장해요!
인프런이 준비한 25% 할인 받으러 가기 >>

  • 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

Transactional 과 Lazy Loading

24.05.23 21:22 작성 조회수 54

0

안녕하세요 강의 잘 듣고 있습니다.

 

다름이 아니라 프로젝트를 업그레이드 하는 과정에서 오류가 생겨 질문 드립니다.

 

package ShopProject.myShopProject.Domain;
import ShopProject.myShopProject.Domain.Item.Item;
import ShopProject.myShopProject.Domain.Order.Order;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;

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

@Entity
@Getter @Setter
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String name;

    @Embedded
    private Address address;

    @OneToMany(mappedBy = "member",cascade = CascadeType.REMOVE)
    private List<Order> orders = new ArrayList<>();


    private String loginId;
    private String password;

    @OneToMany(mappedBy = "member")
    private List<LikedItem> likedItems = new ArrayList<>();


}
package ShopProject.myShopProject.Domain;

import ShopProject.myShopProject.Domain.Item.Item;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
public class LikedItem {

    @Id
    @GeneratedValue
    @Column(name = "likedItem_id")
    private Long id;

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

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "item_id")
    private Item item;



}

 

보시는 것처럼 멤버와 좋아요 상품은 Lazy 관계에 있습니다.

OneToMany는 기본이 Lazy이므로 명시하지 않았습니다.

따라서 좋아요 상품은 직접 조회될 때만 DB에서 조회되고, 다른 경우는 프록시 형태로 존재합니다.

 

//아이템 상세 정보
    @GetMapping("item")
    public String itemDetail(@RequestParam("itemId") Long itemId,
                             @SessionAttribute(name = "loginMember") Member loginMember,
                             Model model) {
        log.info("item 호출");
        Item item = itemService.findOne(itemId);
        log.info(loginMember.getLoginId() + " " + loginMember.getName());
        // 좋아요 여부 추가
        model.addAttribute("isLiked", memberService.isliked(loginMember, item)); //이 부분입니다!!

        model.addAttribute("item", item);
        model.addAttribute("member", loginMember);


        if (item.getCategory().equals("Book")) {
            return "items/item/book";
        }
        if (item.getCategory().equals("Album")) {
            return "items/item/album";
        }
        if (item.getCategory().equals("Movie")) {
            return "items/item/movie";
        }
        return "items/item";
    }

위 컨트롤러를 호출하면, isliked라는 매소드를 실행하게 됩니다. 좋아요 여부를 확인하는 메소드입니다.

 

 //좋아요 아이템이 아니면 false 반환
    @Transactional
    public Boolean isliked(Member member, Item item) {
        log.info("좋아요 확인 메서드 실행");
//
        log.info("멤버 정보" + member.getName() + " " + member.getLoginId());

        for (LikedItem likeditem: member.getLikedItems()) {
//여기에서 lazyinitializationError 발생
            if (likeditem.getItem().getName().equals(item.getName())) {
                log.info("좋아요인 경우");
                return true;
            }

        }
        log.info("좋아요 아닌 경우");
        return false;
    }

 

그런데 여기에서 맴버의 좋아요 목록을 불러오면 lazyinitialization 에러가 발생합니다...

 

정말 많이 원인도 구글링해보았는데, 영속성 컨텍스트가 종료 된 후 접근할 때 발생하는 것을 알았습니다.

영속성 컨텍스트의 생명주기는 트렌잭션과 거의 동일하다고 알고 있습니다.

 

만약 위 메소드에 트렌젝션 어노테이션이 없다면 member.getLikedItems() 의 결과를 초기화해주지 않으면 오류가 발생할 것입니다.

 

따라서 트렌젝션 어노테이션을 넣으므로서, member.getLikedItems()를 가져오고, 직접 DB에서 불러오는 과정을 한 트랜젝션 안에서 수행하도록 했습니다.

 

그런데 이 메서드에서 계속 LazyinitializatoinError가 발생합니다. 이 부분의 원인을 전혀 못 찾겠습니다..

 

해결책으로는 DTO를 사용하거나, fetch Join을 사용해서 멤버와 같이 조인하여 한번에 가져옴으로 해결할 수있을 거라 생각합니다.

 

하지만 해결책은 알았지만, 위 코드의 무슨 오류가 있는지 정말 궁금합니다.

 

따라서 이 코드의 오류를 알고 싶습니다. 또한 저의 이해에 오류가 있다면 그 부분도 알려주시면 감사하겠습니다.

 

답변 1

답변을 작성해보세요.

0

y2gcoder님의 프로필

y2gcoder

2024.05.24

안녕하세요. ml505050님, 공식 서포터즈 y2gcoder입니다.

예외 트레이스를 전부 올려주시거나 코드를 올려주시면 더 정확한 답변이 될 것이라 생각하나, 일단 올려주신 코드와 상황만을 봤을 때는 member를 준영속 상태로 서비스단으로 넘겨주기 때문이라고 생각합니다.

  • 서비스단에서 member의 id로 member를 다시 조회해옴으로써 영속화된 member를 찾아오셔서 다시 시도해보시거나,

  • 아니면 세션에 로그인된 멤버 객체를 저장할 때 fetch join이든, 프록시 초기화를 사용하시든 하여 likedItems를 미리 초기화한 후 저장하시거나,

  • 혹은(이건 다른 얘기일 수도 있으나 영한님도 권장하는 방법입니다) DTO를 만들어 필요한 정보만 넣어 세션에 저장 후 불러오시는 것을 추천합니다.

 

 

감사합니다.

채널톡 아이콘