You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hibernate问题:@OneToMany关联查询返回重复结果

解决Hibernate获取子分类时的重复结果问题

嘿,我刚看完你的问题,这其实是Hibernate里EAGER关联加载导致的笛卡尔积问题,刚好你提到子分类下有9个产品,和重复的次数完全对应,这就验证了我的判断!

问题原因

你的Category类里,children(子分类)和products两个集合都设置了fetch = FetchType.EAGER。当Hibernate加载父分类时,它会执行一个多表连接查询,把父分类、子分类、子分类的产品全部关联起来。这就会形成笛卡尔积:每个子分类会和它的每一个产品匹配一次,所以子分类就重复出现了等于产品数量的次数——也就是你看到的9次重复。

最佳实践解决方案

我不建议在查询后过滤重复数据,这不仅麻烦,还浪费性能。下面是几种更优雅的解决方式:

1. 优先使用懒加载(LAZY)——最推荐的方案

EAGER加载通常不是最佳实践,除非你确定每次加载Category时都必须用到它的所有子分类和产品。把两个集合的加载方式改成FetchType.LAZY,这样Hibernate只会在你实际调用getChildren()或getProducts()时才去加载对应的关联数据,从根源避免笛卡尔积。

修改Category.java的代码:

@OneToMany(mappedBy = "parentCategory", cascade = { CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
private List<Category> children;

@OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
private List<Product> products;

注意:懒加载需要在事务范围内访问,否则会抛出LazyInitializationException。不过你的getCategory方法已经被@Transactional注解标记了,所以在方法内部访问children和products是完全没问题的。

2. 若必须使用EAGER,用@Fetch(FetchMode.SUBSELECT)避免连接查询

如果你因为业务需求必须用EAGER加载,可以给children集合添加@Fetch(FetchMode.SUBSELECT)注解。这样Hibernate会分两步查询:先查出父分类,再用子查询一次性加载所有关联的子分类,而不是做多表连接,自然就不会产生笛卡尔积了。

代码示例:

@OneToMany(mappedBy = "parentCategory", cascade = { CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
@Fetch(FetchMode.SUBSELECT)
private List<Category> children;

3. 自定义JPQL查询,指定Fetch Join

你也可以在Repository里写JPQL查询,明确只Fetch你需要的关联集合(注意:一次只能Fetch一个集合,否则还是会出现笛卡尔积)。比如先Fetch子分类,产品用懒加载处理:

修改CategoryRepository.java的getCategory方法:

@Override
public Category getCategory(Long id) {
    if (id == null) {
        return null;
    }
    try {
        TypedQuery<Category> query = em.createQuery(
            "SELECT c FROM CATEGORIES c LEFT JOIN FETCH c.children WHERE c.id = :id", Category.class);
        query.setParameter("id", id);
        Category category = query.getSingleResult();
        if(category != null){
            // products是懒加载,这里访问时会触发查询,因为当前在事务内
            category.setProducts(this.filterFlaggedOrHiddenUserProducts(category.getProducts()));
        }
        return category;
    } catch (IllegalArgumentException | NoResultException ex){
        return null;
    }
}

总结

尽量用懒加载(LAZY)是Hibernate关联查询的最佳实践,它既解决了重复数据的问题,也能提升应用性能——毕竟不需要每次加载分类都把所有子分类和产品一起查出来。如果特殊场景必须用EAGER,再考虑用@Fetch或者自定义JPQL的方式。

内容的提问来源于stack exchange,提问作者Mavelouscello

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 15:43:11