본문 바로가기

TIL/트러블슈팅

[SpringBoot] failed to lazily initialize a collection of role: could not initialize proxy - no Session

👉에러 상황

먼저 에러는 Post 목록 조회할 때 발생했습니다. Post와 Like는 1:N 양방향 연간관계를 맺고 있습니다.

 

- Post 엔티티 구성

package com.duktown.domain.post.entity;

import com.duktown.domain.BaseTimeEntity;
import com.duktown.domain.comment.entity.Comment;
import com.duktown.domain.like.entity.Like;
import com.duktown.domain.user.entity.User;
import com.duktown.global.type.Category;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

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

import static javax.persistence.EnumType.STRING;
import static javax.persistence.FetchType.LAZY;
import static javax.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PRIVATE;
import static lombok.AccessLevel.PROTECTED;

@Entity
@Getter
@Builder
@AllArgsConstructor(access = PRIVATE)
@NoArgsConstructor(access = PROTECTED)
public class Post extends BaseTimeEntity {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "post_id")
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    @Enumerated(STRING)
    @Column(nullable = false)
    private Category category;

    @Column(length = 20, nullable = false)
    private String title;

    @Column(nullable = false, columnDefinition = "longtext")
    private String content;

    @Builder.Default
    @OneToMany(fetch = LAZY, mappedBy = "post")
    private List<Comment> comments = new ArrayList<>();


    @OneToMany(fetch = LAZY, mappedBy = "post")
    private List<Like> likes;


    public void update(String title, String content){
        this.title =title;
        this.content =content;
    }
}

 

- Like 엔티티 구성

package com.duktown.domain.like.entity;

import com.duktown.domain.BaseTimeEntity;
import com.duktown.domain.comment.entity.Comment;
import com.duktown.domain.post.entity.Post;
import com.duktown.domain.user.entity.User;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

import static javax.persistence.FetchType.LAZY;
import static javax.persistence.GenerationType.IDENTITY;
import static lombok.AccessLevel.PRIVATE;
import static lombok.AccessLevel.PROTECTED;

@Entity
@Getter
@Builder
@AllArgsConstructor(access = PRIVATE)
@NoArgsConstructor(access = PROTECTED)
@Table(name = "likes")
public class Like extends BaseTimeEntity {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "like_id")
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "post_id")
    private Post post;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "comment_id")
    private Comment comment;
}

 

👉로그 

Hibernate: 
    select
        user0_.user_id as user_id1_18_,
        user0_.created_at as created_2_18_,
        user0_.modified_at as modified3_18_,
        user0_.available_period as availabl4_18_,
        user0_.deleted as deleted5_18_,
        user0_.deleted_at as deleted_6_18_,
        user0_.email as email7_18_,
        user0_.login_id as login_id8_18_,
        user0_.name as name9_18_,
        user0_.password as passwor10_18_,
        user0_.refresh_token as refresh11_18_,
        user0_.role_type as role_ty12_18_ 
    from
        users user0_ 
    where
        user0_.deleted=0 
        and user0_.user_id=? 
        and user0_.login_id=?
Hibernate: 
    select
        user0_.user_id as user_id1_18_,
        user0_.created_at as created_2_18_,
        user0_.modified_at as modified3_18_,
        user0_.available_period as availabl4_18_,
        user0_.deleted as deleted5_18_,
        user0_.deleted_at as deleted_6_18_,
        user0_.email as email7_18_,
        user0_.login_id as login_id8_18_,
        user0_.name as name9_18_,
        user0_.password as passwor10_18_,
        user0_.refresh_token as refresh11_18_,
        user0_.role_type as role_ty12_18_ 
    from
        users user0_ 
    where
        user0_.deleted=0 
        and user0_.user_id=?
Hibernate: 
    select
        like0_.like_id as like_id1_9_0_,
        post1_.post_id as post_id1_11_1_,
        like0_.created_at as created_2_9_0_,
        like0_.modified_at as modified3_9_0_,
        like0_.comment_id as comment_4_9_0_,
        like0_.post_id as post_id5_9_0_,
        like0_.user_id as user_id6_9_0_,
        post1_.created_at as created_2_11_1_,
        post1_.modified_at as modified3_11_1_,
        post1_.category as category4_11_1_,
        post1_.content as content5_11_1_,
        post1_.title as title6_11_1_,
        post1_.user_id as user_id7_11_1_ 
    from
        likes like0_ 
    inner join
        post post1_ 
            on like0_.post_id=post1_.post_id 
    where
        like0_.user_id=? 
        and (
            like0_.post_id in (
                ? , ?
            )
        )
Hibernate: 
    select
        count(*) as col_0_0_ 
    from
        comment comment0_ 
    where
        comment0_.post_id=? 
        and comment0_.deleted=0

 

👉해결

지연 로딩으로 생성된 프록시를 초기화(= 연관된 데이터를 가져오기)가 불가능한 상황임으로 원인은 Post 조회 결과가 반환 되면서 트랜잭션이 이미 종료된 이후에 Like 연관된 데이터에 접근하려 했기 때문이다.

 

방안1) 데이터를 가져올 떄 한번에 가져오는 즉시로딩으로 변경

 @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Like> likes = new ArrayList<>();

 

방안2) 트랜잭션 단위로 관리되도록 수정

조회 로직 내부에 캐싱 부분이 있어 해당 캐싱 부분에서 호출 부문을 @Transactional을 통해 같은 트랜잭션 내에서 관리되도록 작성.