【问题标题】:Hibernate: Criteria query taking a long time with three-level relationshipHibernate:三级关系的条件查询需要很长时间
【发布时间】:2025-12-23 21:15:11
【问题描述】:

我在使用 Criteria 查找属于某个实体的所有对象时遇到问题。我的模型如下(仅显示相关代码):

@Entity...
Class A {
    @ManyToOne(fetch = FetchType.EAGER)
    @Fetch(FetchMode.JOIN)
    @JoinColumn(name = "ide_b", referencedColumnName = "ide_b", nullable = false)
    private B b;
}

@Entity...
class B {
    @ManyToOne(fetch = FetchType.EAGER)
    @Fetch(FetchMode.JOIN)
    @JoinColumn(name = "ide_c", referencedColumnName = "ide_c", nullable = false)
    private C c;
}

@Entity...
class C {
   ...
}

我的 Criteria 查询就是这么简单(实际上,会有一些过滤器,但它们没有被使用):

Criteria criteria = getSession().createCriteria(A.class);
criteria.list(); // MY SYSTEM STAYS HERE FOREVER WHEN RUNNING AGAINST A REAL DATABASE

有人知道这个问题吗?系统只是永远停留在“criteria.list()”这一行,永远不会返回。

  • 我已经测试了它直接在数据库上生成的 SQL,它运行良好。

  • 我已经用代码测试了这个查询,代码只涉及到 A 类引用 B 和 A 类引用 C(直接)。他们都工作。关联中的第三级似乎引起了问题...注意:我的 Hibernate 版本是旧版本,例如 3.0.0

【问题讨论】:

  • 删除@Fetch(和@JoinColumn)注释怎么样?
  • 我无法删除@JoinColumn,因为我必须为 Hibernate 提供要映射的列的名称。尝试从关系 B -> C 中删除 @Fetch,但没有成功。
  • fetch eager 非常激进,您将收到 X * Y * Z 元素,其中 X 和 Y 复制了任意多次。 Eager 仅适用于具有少量行的 1->n 关系,否则有利于 Lazy getters。 Lazy 会发送更多查询,但总体而言会更轻松。
  • 我尝试将关系设置为 Lazy,但也没有成功。此外,包含 B 和 C 记录的两个表都非常小(B 有 8 行,C 有 3 行)。 A 有 14018 行。
  • Lazy 到底有什么不适合的?因为当你知道如何使用 Lazy 时,它就会起作用。另外:14018 * 8 * 3 = 336432。要处理的记录很多。

标签: java hibernate criteria relationships


【解决方案1】:

您需要删除提取类型连接。 Eager 适合您的数据布局,但您真的需要一次这么多数据吗?

使用默认 fetch,hibernate 将只查询表 A。然后对于每个 B 的外键,每个键只查询 B 一次。对 C 做同样的事情。

即,一旦从 B 获取 b_id=1,即使它与 A 的百万行一起使用,也不会再次获取它。Hibernate 的 2 级缓存处理它。

使用连接类型 fetch,对于 A 中的每一行,您将获得 1 行,其中包含所有 3 个表的列。
如果您的关系是 OneToMany,那么您将返回 A x B x C 行。但是自从ManyToOne之后,就不存在这样的问题了。
您的问题是即使此查询为 A 处的每个项目返回 1 个大行,B 和 C 复制太多。所以 DB 响应很大,因此 DB 和您的应用程序也很难处理。

【讨论】:

    【解决方案2】:

    嗯,实际上我认为问题出在其他地方。我已将两种关系(A -> B 和 B -> C)都更改为惰性(并删除了@Fetch)。然后我查询了一个特定的 A 对象。到现在为止还挺好。我能够得到对象,并且能够成功调用 a.getB()。

    尽管如此,当我调用 b.getC() 时,Hibernate 不会将 C 对象返回给我(我的意思是,Hibernate 卡在这一行)。

    当我调用 b.getC() 时,Hibernate 创建以获取 C 对象的查询如下:

     select
        myCtable0_.id as id1_85_0_,
        myCtable0_.name as nam2_85_0_ 
    from
        MyTableC myCtable0_ 
    where
        myCtable0_.id in (?, ?)
    

    C 表有一个 id 字段(主键)和一个名称(varchar)。

    【讨论】:

    • 如果该语句没有被执行数千次,那么没有理由没有数据库查询应该花费任何明显的时间。 (会不会是你在不知不觉中碰到了某个断点?)
    • 很遗憾没有。我同意,没有理由不返回 :( 看起来我正在使用的版本有一个错误......
    • 注意 Hibernate 正在使用“in (?, ?)”。我不明白为什么。查询应该只使用主键(“myCtable0_.id = ?”对我来说更有意义)。而且,是的,C 类中的映射是正确的。
    【解决方案3】:

    另一种选择是单独获取所有数据。然后休眠缓存将拥有每个实体,然后我认为条件查询会更快。

    【讨论】: