• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

LazyInitializationException에 대해 질문드립니다!

24.05.01 16:57 작성 24.05.01 16:58 수정 조회수 71

0

배운것을 바탕으로 개인적으로 게시판을 만들어보는 중입니다. 게시물을 추가할 때 다음과 같은 에러가 발생해서 몇시간째 애를 먹고있는 중입니다.

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.myproject.jpaboard.domain.Member.posts: could not initialize proxy - no Session

아무래도 Post(게시물) 엔티티의 연관관계 편의 메서드에서 문제가 발생하는 것 같습니다. 서비스 계층에 @Transactional도 붙여줬음에도 계속해서 문제가 발생해서 질문 남깁니다. 유일한 해결책은 Member의 posts에 EAGER을 붙이는 방법이었습니다. 하지만 이는 적절치 않아보입니다. 어떻게 해야 해결할 수 있을까요.

핵심이 되는 코드들을 아래에 추가했습니다.

 

package com.myproject.jpaboard.web.controller;

import com.myproject.jpaboard.domain.Member;
import com.myproject.jpaboard.domain.Post;
import com.myproject.jpaboard.web.SessionConst;
import com.myproject.jpaboard.web.form.PostForm;
import com.myproject.jpaboard.web.repository.BoardRepository;
import com.myproject.jpaboard.web.service.BoardService;
import com.myproject.jpaboard.web.service.PostService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/post")
@RequiredArgsConstructor
@Slf4j
public class PostController {

    private final BoardService boardService;
    private final BoardRepository boardRepository;
    private final PostService postService;

  

    /**
     * 게시물 추가
     */
    @PostMapping("/new")
    public String addPost(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, @Validated PostForm postForm, BindingResult bindingResult) {

        log.info("postForm={}", postForm);

        if (bindingResult.hasErrors()) {
            log.info("errors={}", bindingResult);
            return "newPost";
        }
        postService.addPost(postForm, loginMember);

        return "redirect:/board/list";
    }

  
}


// Post.java
@Entity
@Getter @Setter
public class Post {

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

    private String title;
    private String writer;

    @Lob
    @Column(columnDefinition="LONGTEXT")
    private String content;

    @Column(updatable = false)
    private LocalDateTime createdTime;
    private LocalDateTime lastModifiedTime;
    private Long viewCount;

    @ManyToOne(fetch = FetchType.LAZY) // 다쪽이 연관관계의 주인
    @JoinColumn(name = "member_id")
    private Member member;

    @Enumerated(EnumType.STRING)
    private CategoryType category;

    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Comment> comments = new ArrayList<>();

    public Post() {}

    // 연관관계 편의 메서드
    public void setMember(Member member) {
        this.member = member;
        member.getPosts().add(this);
    }

    @Override
    public String toString() {
        return "Post{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", writer='" + writer + '\'' +
                ", content='" + content + '\'' +
                ", createdTime=" + createdTime +
                ", lastModifiedTime=" + lastModifiedTime +
                ", viewCount=" + viewCount +
                ", category=" + category +
                '}';
    }
}

// Member.java
@Entity
@Getter @Setter
public class Member {

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

    private String email;
    private String password;

    private String name;

    @Embedded
    private Address address;

    @OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
    private List<Post> posts = new ArrayList<>();


    /**
     * POST 추가 로직 (도메인 모델 패턴)
     */
//    public void addPost(Post post) {
//
//
//    }


    @Override
    public String toString() {
        return "Member{" +
                "address=" + address +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                ", id=" + id +
                '}';
    }
}

// PostService.java
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PostService {

    private final BoardRepository boardRepository;
    private final PostRepository postRepository;

    /**
     * 게시물 추가
     */
    @Transactional
    public void addPost(PostForm postForm, Member loginMember) {

        Post post = new Post();
        postForm.setCreatedTime(LocalDateTime.now());

        // setter로 세팅
        post.setTitle(postForm.getTitle());
        post.setWriter(postForm.getWriter());
        post.setContent(postForm.getContent());
        post.setCreatedTime(postForm.getCreatedTime());
        post.setCategory(postForm.getCategory());
        post.setViewCount(0L);
        post.setWriter(loginMember.getName());

        post.setMember(loginMember);
        postRepository.save(post);
    }
}

답변 2

·

답변을 작성해보세요.

0

David님의 프로필

David

2024.05.02

안녕하세요. Jaesang Yoon님, 공식 서포터즈 David입니다.

도움을 드리고 싶지만 전체가 아닌 일부 코드만으로는 답변을 드리기 어렵습니다.

실제 동작하는 전체 프로젝트를 압축해서 구글 드라이브로 공유해서 링크를 남겨주세요.

구글 드라이브 업로드 방법은 다음을 참고해주세요.

https://bit.ly/3fX6ygx


주의: 업로드시 링크에 있는 권한 문제 꼭 확인해주세요


추가로 다음 내용도 코멘트 부탁드립니다.

1. 문제 영역을 실행할 수 있는 방법

2. 문제가 어떻게 나타나는지에 대한 상세한 설명


링크: 공식 서포터즈

링크: 자주하는 질문

감사합니다.

0

인프런 AI 인턴님의 프로필

인프런 AI 인턴

2024.05.01

안녕하세요, 인프런 AI 인턴입니다. Jaesang Yoon님의 문제에 대하여 관련된 내용을 찾았습니다.

LazyInitializationException은 Hibernate 세션이 이미 종료된 이후에 지연 로딩을 하려고 할 때 자주 발생하는 오류입니다. 즉, 엔티티를 로딩한 세션의 생명주기가 끝나고 난 뒤에 연관된 엔티티를 불러오려 할 때 나타납니다.

Jaesang Yoon님의 경우, Member 엔티티 내부에 있는 posts 컬렉션에 접근하려 할 때 해당 오류가 발생한 것으로 보입니다. 오류 메시지에서 “could not initialize proxy - no Session”이라는 부분이 바로 그 핵심입니다.

이 문제를 해결하기 위한 일반적인 방법은 두 가지가 있습니다:
1. @Transactional 어노테이션 사용: Jaesang Yoon님께서는 이미 서비스 계층에서 사용하고 계신 것으로 보입니다. 그러나 이 오류가 계속 발생한다면, 해당 데이터에 접근하는 로직이 모두 @Transactional 어노테이션으로 묶여있는지 확인해보세요. 특히, Memberposts 컬렉션에 접근하는 과정이 해당 어노테이션 내에서 이루어져야 합니다.

  1. Fetch Join을 이용한 쿼리 최적화: JPA의 Fetch Join을 이용하여 연관된 엔티티를 한번에 로딩하는 방식입니다. 이 방식을 사용하면, 연관된 엔티티에 대한 추가적인 쿼리 없이 한 번의 쿼리로 필요한 모든 데이터를 로딩할 수 있습니다. 예를 들어, Member를 조회할 때, posts도 함께 조회하도록 쿼리를 작성할 수 있습니다.

관련하여 링크를 첨부해 드립니다:
- failed to lazily initialize a collection of role 오류 관련 문의

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

지식공유자님의 답변을 기다립니다.