如何加速一对多关联的 Hibernate DML 语句

How to speedup Hibernate DML statements for one-to-many associations

我一直在尝试优化一个 Hibernate 应用程序,而我遇到的 Hibernates 效率的最大问题之一是它倾向于执行 n+1 个查询来对子实体进行简单的 crud 操作。我已经能够通过在子实体(多对一端)上使用 @Fetch(FetchMode.JOIN) 来防止对 select 操作的 n+1 查询,但这不会影响 update/insert/delete 查询。以下是相关实体和属性的示例:

// parent entity for Mean and Covariance entities
@Entity
@DynamicInsert
@Table(name = "belief")
public class Belief implements Serializable, Cloneable {

    // surrogate key
    @Id
    @GeneratedValue
    @Column(name = "belief_id", unique = true, insertable = false, updatable = false)
    private Integer id;

    // other properties...

    @Cascade(org.hibernate.annotations.CascadeType.ALL)
    @OneToMany(mappedBy = "pk.beliefId", orphanRemoval = true, fetch = FetchType.LAZY)
    private List<Mean> means = new ArrayList<>();

    @Cascade(org.hibernate.annotations.CascadeType.ALL)
    @OneToMany(mappedBy = "pk.beliefId", orphanRemoval = true, fetch = FetchType.LAZY)
    private List<Covariance> covariances = new ArrayList<>();

}

@Entity
@DynamicInsert
@Table(name = "mean")
public class Mean implements Serializable, Cloneable {

    // composite key
    @EmbeddedId
    private MeanPK pk = new MeanPK(this);

    @Fetch(FetchMode.JOIN)
    @ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH })
    @JoinColumn(name = "belief_id", insertable = false, nullable = false, updatable = false)
    private Belief belief;

    // other properties...
}

@Entity
@DynamicInsert
@Table(name = "covariance")
public class Covariance implements Serializable, Cloneable {

    // composite key
    @EmbeddedId
    private CovariancePK pk = new CovariancePK(this);

    @Fetch(FetchMode.JOIN)
    @ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH })
    @JoinColumn(name = "belief_id", insertable = false, nullable = false, updatable = false)
    private Belief belief;

    // other properties...
}

所以当我执行 session.delete(belief); 时,日志显示对每个协方差执行了一个单独的删除语句,这意味着信念实体可能已经引用。这是日志的示例:

Hibernate: select belief0_.belief_id as belief_i1_0_0_, belief0_.after_comb as after_co2_0_0_, belief0_.description as descript3_0_0_, belief0_.name as name4_0_0_, belief0_.project_id as project_7_0_0_, belief0_.type as type5_0_0_, belief0_.version as version6_0_0_ from belief belief0_ where belief0_.belief_id=?
Hibernate: select covariance0_.belief_id as belief_i1_0_0_, covariance0_.belief_id as belief_i1_1_0_, covariance0_.col_variable_id as col_vari2_1_0_, covariance0_.row_variable_id as row_vari3_1_0_, covariance0_.belief_id as belief_i1_1_1_, covariance0_.col_variable_id as col_vari2_1_1_, covariance0_.row_variable_id as row_vari3_1_1_, covariance0_.variance as variance4_1_1_, covariance0_.version as version5_1_1_, variable1_.variable_id as variable1_5_2_, variable1_.definition as definiti2_5_2_, variable1_.description as descript3_5_2_, variable1_.name as name4_5_2_, variable1_.project_id as project_6_5_2_, variable1_.version as version5_5_2_, variable2_.variable_id as variable1_5_3_, variable2_.definition as definiti2_5_3_, variable2_.description as descript3_5_3_, variable2_.name as name4_5_3_, variable2_.project_id as project_6_5_3_, variable2_.version as version5_5_3_ from covariance covariance0_ inner join variable variable1_ on covariance0_.col_variable_id=variable1_.variable_id inner join variable variable2_ on covariance0_.row_variable_id=variable2_.variable_id where covariance0_.belief_id=?
Hibernate: select means0_.belief_id as belief_i1_0_0_, means0_.belief_id as belief_i1_2_0_, means0_.variable_id as variable2_2_0_, means0_.belief_id as belief_i1_2_1_, means0_.variable_id as variable2_2_1_, means0_.mean as mean3_2_1_, means0_.swept as swept4_2_1_, means0_.version as version5_2_1_, variable1_.variable_id as variable1_5_2_, variable1_.definition as definiti2_5_2_, variable1_.description as descript3_5_2_, variable1_.name as name4_5_2_, variable1_.project_id as project_6_5_2_, variable1_.version as version5_5_2_ from mean means0_ inner join variable variable1_ on means0_.variable_id=variable1_.variable_id where means0_.belief_id=?
Hibernate: delete from covariance where belief_id=? and col_variable_id=? and row_variable_id=? and version=?
Hibernate: delete from covariance where belief_id=? and col_variable_id=? and row_variable_id=? and version=?
Hibernate: delete from mean where belief_id=? and variable_id=? and version=?
Hibernate: delete from mean where belief_id=? and variable_id=? and version=?
Hibernate: delete from mean where belief_id=? and variable_id=? and version=?
Hibernate: delete from mean where belief_id=? and variable_id=? and version=?
Hibernate: delete from mean where belief_id=? and variable_id=? and version=?
Hibernate: delete from belief where belief_id=? and version=?

我已经为这个 n+1 问题进行了大量的谷歌搜索,但我只找到了 select 操作的解决方案,而不是级联 insert/update/delete 操作。 有没有人知道如何解决和优化这个问题? 谢谢!

您基本上处于预期的 Hibernate 魔法已经结束并在 OR-Mapping 的现实中结束的地步。

您可以从 OneToMany 中删除 FetchType.Lazy,因为这是默认值。 FetchType.Eager 将是克服 select 语句的 n+1 问题的方法。你也没有这样做(或者说你混合了 hibernate 和 jpa 的东西,不确定在那种情况下是什么)。问题为什么要批量删除?如果是,那么就这样做,但不要指望 hibernate 会为您做决定。请记住,通过一组 id 进行删除可能不会触发级联和孤立删除。 如果您希望该列表中有大量条目,那么是时候重构您的映射了。

!!!但最重要!!!

你的映射中有多个东西,它们似乎对性能有更高的负面影响,并且在某些情况下会导致意外行为。在我看来,您想对某件事进行性能优化,这不是最大的问题。只要删除操作没有性能问题,请不要执行任何操作。

  • 混合两个抓取系统
  • 高级联使用率 - 为什么做出此决定。他们真的是背后的思考过程吗?或者为您解决了一个问题,可以通过手动处理以更简洁的方式解决。
  • 简单的 Long id 无法处理的 EmbeddedId 内容是否有特定原因?对我来说似乎有点过头了。
  • 我不质疑 JoinColumn 可插入等内容。我只是希望这背后有一个原因。

Hibernate 将 entity state transitions 转换为 DML 语句,这就是为什么每个被删除的实体都有一个 DELETE 语句。

shouldn't use EAGER fetching,因为基于查询的抓取策略总是更灵活并产生最佳性能。关联应该是 LAZY,您应该使用 JOIN FETCH 指令来检索每个特定业务案例所需的关系。

您还应该为以下各项启用语句批处理:

如果您有一个写密集型应用程序,您可以使用批量 UPDATE/DELETE HQL/JPQL 支持的批量语句。