【问题标题】:Bi-direction becomes much slower than uni-direction [closed]双向变得比单向慢得多[关闭]
【发布时间】:2017-07-21 16:38:56
【问题描述】:

在 Java/Hibernate 应用程序中,我有两个类 CatKitten 处于双向关系中,如下所示:

public class Cat {
  ...
  @OneToMany(mappedBy="cat", fetch = FetchType.LAZY)
  @OnDelete(action = OnDeleteAction.CASCADE)
  @LazyCollection(LazyCollectionOption.EXTRA)
  @Getter
  @Setter
  private List<Kitten> kittens = new LinkedList();
  public void addKitten(Kitten k) {
    kittens.add(k);
  }
  ...
}  

public class Kitten {
  ...   
  @ManyToOne(fetch=FetchType.LAZY)
  @Getter
  @Setter
  private Cat cat;
  ...
}

在一个巨大的 for 循环中,20000 个Kitten 被添加到之前创建的不同Cat 实体中。 for循环中的重要代码如下所示:

....
Kitten k = new Kitten();
k.setAttribut("foo");
k.setCat(currentCat);     // (a) line
currentCat.addKitten(k);  // (b) line
daoFactory.getKittenDao().save(k);
...

代码正在运行,但不知何故,性能非常缓慢。在以前的迭代(单向)中,应用程序要快得多。由于最终版本应该可以在 1000000 Kitten 上运行,因此必须有改进的方法。如果我对上述代码的时间进行基准测试,大约需要 40 秒。如果我通过删除行 (a) 或 (b) 来模拟单向,则在这两种情况下都需要 10 秒(但如果我访问对象则会稍后崩溃)。

所以我的问题是: 我错过了什么,或者在 Hibernate 中双向关系的内部维护非常慢?由于模拟单向要快得多,我预计双向的运行时间约为 15 秒。

更新:

保存实体的代码在 SAX-Parser DefaultHandler 实现中。因此,在解析 XML 结构时,首先将 Cat 保存在 startElement() 函数中,然后将 Kitten 保存在另一个 startElement() 调用中。 关于@Bartun 的建议/问题:我研究了批处理。问题是,通过使用 DAO 和 startElement() 函数,很难判断何时保存了 50 个实体以刷新会话。不过,柜台可以解决问题。但是,它并没有通过建立双向来解释性能影响。 作为事务管理 Spring@Transactional 用于启动 XML 解析的函数。

【问题讨论】:

  • 你有没有分析过你流血的地方?
  • 只是一个猜测,尝试 persist() 而不是 save()。
  • @Steve11235 persist 使情况变得更糟。那么运行时间是 50 秒。
  • @efekctive 我如何分析它?我启用了 Hibernate 的 SQL 输出,但没有看到不必要的调用或类似的东西。
  • 您必须使用一些探查器来分析运行或使用任何数据库功能。就像mysql中的“解释”。我将从数据库开始。

标签: java performance hibernate bidirectional


【解决方案1】:

我已将处理时间减少到我可以接受的值。它并没有真正解决我的问题,但大大减少了时间。如果其他人有同样的问题,这里是当前代码和简短的解释。

public class Cat {
  ...
  @OneToMany(mappedBy="cat", fetch = FetchType.LAZY)
  @OnDelete(action = OnDeleteAction.CASCADE)
  @LazyCollection(LazyCollectionOption.EXTRA)
  @Getter
  @Setter
  private Set<Kitten> kittens = new HashSet();
  public void addKitten(Kitten k) {
     k.setCat(this);
     if (Hibernate.isInitialized(kittens)) kittens.add(k); //line X
  }
  ...
}  

public class Kitten {
  ...   
  @ManyToOne(fetch=FetchType.LAZY)
  @OnDelete(action = OnDeleteAction.CASCADE)
  @Getter
  @Setter
  private Cat cat;
  ...
}
  • 最重要的是line X。在原始代码中,每次添加一只小猫时,都会从数据库中加载整个小猫列表。正如我发现的here,如果列表尚未初始化,则不能添加小猫。我知道,这必须小心完成,因为必须在首次初始化列表之前将每只小猫保存在数据库中。否则一切都会不同步。
  • 在上面的代码中没有说明,我更改了小猫持久性的结构以启用批量插入(Thx Bartun 链接)。现在,所有小猫都在解析过程结束时使用批处理保存,而不是单独保存每只小猫。
  • 还有一个小的改进是从List 更改为Set,以便在代码后面启用多个fetch joins

【讨论】:

    【解决方案2】:

    你在哪里开启交易?确保您使用的是一笔交易

    你也应该对这样的数量使用批处理查询

    更多关于批处理的信息https://vladmihalcea.com/how-to-batch-insert-and-update-statements-with-hibernate/

    您还可以使用休眠静态检查刷新计数和其他内容 https://www.thoughts-on-java.org/how-to-activate-hibernate-statistics-to-analyze-performance-issues/

    当您处理如此数量的插入时,您需要完全控制刷新/批处理

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-09-27
      • 1970-01-01
      • 2011-10-27
      • 1970-01-01
      • 2013-08-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多