Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple collection fetch join #16

Open
Minuooooo opened this issue Jul 26, 2023 · 3 comments
Open

Multiple collection fetch join #16

Minuooooo opened this issue Jul 26, 2023 · 3 comments
Assignees
Labels
documentation Improvements or additions to documentation

Comments

@Minuooooo
Copy link
Contributor

Minuooooo commented Jul 26, 2023

Store 목록을 반환할 때 제공해야 하는 데이터 중 해당 Store에 대한 찜한 사람들의 수, 리뷰 수가 있었습니다
Collection fetch join이 불가피한 상황이었습니다. 이에 따라 진행하다가 오류를 맞닥뜨렸습니다

Store entity 에서 storeReviews, storeBookmarks 이렇게 두 개의 컬렉션을 가지고 있습니다

@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Store extends AuditEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "store_id")
    private Long id;
    @OneToMany(mappedBy = "store", cascade = ALL, orphanRemoval = true)
    private List<Product> products = new ArrayList<>();
    @OneToMany(mappedBy = "store", cascade = ALL, orphanRemoval = true)
    private List<StoreReview> storeReviews = new ArrayList<>();
    @OneToMany(mappedBy = "store", cascade = ALL, orphanRemoval = true)
    private List<StoreBookmark> storeBookmarks = new ArrayList<>();

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Category category;
    @Column(nullable = false)
    private String name;
    private String webUrl;
    @Column(nullable = false)
    private String location;
    private String phone;
    private String imageUrl;
    private String runningTime;
}

StoreRepository에서 Collection fetch join 시 두 개의 Collection을 가지고 옵니다

public interface StoreRepository extends JpaRepository<Store, Long> {
    @EntityGraph(attributePaths = {"storeReviews", "storeBookmarks"})
    @NotNull
    List<Store> findAll();
}

하지만 두 개의 Collection 모두 List<> 타입입니다
이 상태로 Store 객체를 가지고 오게 되면 이런 오류가 발생합니다

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags

이 오류는 Collection fetch join을 통해 다중 Collection을 가지고 올 때 발생하는데,
여기서 두 개 이상의 List<> 타입이 존재할 때 발생합니다 List<> 타입은 Set<>과 달리 중복을 허용합니다
중복을 허용하기 때문에 List<> 타입의 Collection을 두 개 이상 fetch join으로 가지고 오게 되면 JPA 입장에서 Query를 생성하는데 크나큰 어려움이 있습니다.

그래서 현재 해결 방법으로는 List<> 타입이 두 개 이상이 되지 않도록 Set<> 타입으로 변환해주었습니다

@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Store extends AuditEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "store_id")
    private Long id;
    @OneToMany(mappedBy = "store", cascade = ALL, orphanRemoval = true)
    private List<Product> products = new ArrayList<>();
    @OneToMany(mappedBy = "store", cascade = ALL, orphanRemoval = true)
    private List<StoreReview> storeReviews = new ArrayList<>();
    @OneToMany(mappedBy = "store", cascade = ALL, orphanRemoval = true)
    private Set<StoreBookmark> storeBookmarks = new HashSet<>();

    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private Category category;
    @Column(nullable = false)
    private String name;
    private String webUrl;
    @Column(nullable = false)
    private String location;
    private String phone;
    private String imageUrl;
    private String runningTime;
}

Reference: https://perfectacle.github.io/2019/05/01/hibernate-multiple-bag-fetch-exception/

다른 해결 방법을 아시는 분은 코멘트 부탁드리겠습니다!

@Minuooooo Minuooooo added the documentation Improvements or additions to documentation label Jul 26, 2023
@Minuooooo Minuooooo self-assigned this Jul 26, 2023
@dtd1614
Copy link
Member

dtd1614 commented Jul 26, 2023

자바 콜렉션의 List 는 정렬되지 않은 상태이기 때문에 Hibernate 에서는 Bag 타입으로 처리하는데, Hibernate 에서 List타입으로 처리하도록 해주면 오류가 해결된다고 합니다. 엔티티에서 List 타입 변수 위에 @OrderColumn을 붙이면 Hibernate에서 List타입으로 처리해준다고 하네요. 저도 테스트 해봤는데 오류가 해결됐습니다.

참고 : https://pasudo123.tistory.com/473

블로그 글에서 Set과 List를 섞으면 카디시안 곱이라는 문제가 생긴다고 하는데, 쿼리dsl 예제라서 저희한테 해당하는지 모르겠네요. 테스트 해봤는데 저는 저런 문제를 발견하지 못했습니다.

@Minuooooo
Copy link
Contributor Author

와우 이런 레퍼런스가 있었군요
Set<> 타입으로 지정하는 것은 잘못된 해결 방식일 확률이 높군요
@OrderColumn이라는 어노테이션은 처음 알게되었는데 이런 처리를 해주는 녀석이었네요

Set을 사용하게 되면 영속화 되기 전에 중복되는 데이터가 있으면 삭제되는 위험성이 있는 것 같습니다 이 부분에 대해서는 더 알아봐야겠군요
좋은 레퍼런스 감사합니다!

@Minuooooo
Copy link
Contributor Author

앞서 언급한, MultipleBagFetchException은 중복을 허용하는 List 타입의 여러 컬렉션이 조인되면 중복된 컬럼에 대해서도 카다시안 곱이 적용되어 이걸 방지하기 위해 발생하는 오류라고 합니다
해결이라기 보다는 구현할 수 있는 여러가지 방법이 있는 것 같습니다

일단, Set<> 타입의 사용은 잘못된 것은 아니고 Hibernate에서 래퍼 클래스를 PersistentSet 타입으로 하여 인식한다고 합니다
다중 컬렉션 조인을 하게 되면 카다시안 곱이라는 문제가 발생합니다 각각의 컬럼이 n, m개 이면 n * m개의 컬럼이 되는 거죠
하지만, 테이블에 대해 순서 보장의 필요성이 없어도 되고 중복을 허용하지 않는다는 것에 문제가 일어나지 않는다면 해결 방법의 한 가지가 될 수 있다고 합니다

한 번에 조인하지 않고 Query를 두 개로 쪼개서 하나의 컬렉션과 조인한 결과를 다른 컬렉션과 조인을 하는 방법도 있는 것 같습니다 이 경우에는 카다시안 곱 문제가 발생되지 않습니다

앞서 말씀해주신, @OrderColumn에 대한 사용은 적용된 테이블에 위치 값을 저장하는 컬럼이 하나 더 생긴다고 합니다 이 방식은 단점이 꽤 많아서 실무에서는 권장하지 않는다고 하네요

결론은, 여러 방식이 있지만 성능은 결국 테스트를 거쳐서 향상 시켜야 할 것 같습니다
정해져 있는 답은 없지만 점점 개선시키다 보면 문제가 완화되지 않을까 싶습니다

Reference: https://ttl-blog.tistory.com/189

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

2 participants