【问题标题】:JPA persist becomes slower and slowerJPA坚持变得越来越慢
【发布时间】:2016-06-12 13:01:08
【问题描述】:

此方案使用简单的 oneToMany 关系,并在两个方向上保持级联。

很多:

@javax.persistence.Entity(name="Many")
public class Many {
    @javax.persistence.ManyToOne(cascade = CascadeType.PERSIST)
    protected One one;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long primaryKey;

    public void setM(One one) {
        this.one = one;
        // comment out this line and performance becomes stable
        this.one.getMany().add(this);
    }

    // other setters, getters, etc...
}

一个:

@javax.persistence.Entity(name="One")
public class One {
    @javax.persistence.OneToMany(mappedBy="m", cascade = CascadeType.PERSIST)
    protected java.util.Set<Many> many = com.google.common.collect.Sets.newHashSet();

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long primaryKey;

    private String name;

    // setters, getters, etc... 
}

测试:

public static void main(String[] args) {
    while(true) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("test-pu");
        EntityManager em = emf.createEntityManager();

        for (int i = 0; i < 100; i++) {
            sw.reset();
            sw.start();
            persistMVs(emf, em);
            System.err.println("Elapsed: " + sw.elapsed(TimeUnit.MILLISECONDS) + " ms");
        }

        em.close();
        emf.close();
    }
}

private static void persistMVs(EntityManagerFactory emf, EntityManager em) {
    em.getTransaction().begin();
    One one = getOrCreateOne(em);

    for (int i = 0; i < 200; i++) {
        Many many = new Many();
        many.setM(one);
        em.persist(many);
    }
    em.getTransaction().commit();
}

测试是一个无限循环,它尝试插入与单个 One 实体关联的 20000 个 Many 实体。每个循环都从创建一个新的EntityManagerFactory 开始,以显示不断增加的数据库对性能的负面影响。

预期的行为是,实体的插入时间不会急剧增加,但是在每个 WHILE CYCLE 之后会有一个数量级的增加。

注意事项:

  • 我尝试过 eclipseLink、Hibernate、OpenJPA 并且都遇到过这种减速。
  • 如果我不更新 One 的 Many 集合,则不会出现降级(请参阅 Many 的注释行)。
  • 如果我不创建新的 EntityManagerFactory,那么即使在 50 万个实体之后也不会降级。
  • 慢的部分是em.persist(many);(我测了一下)。
  • 查看https://github.com/kupsef/OneToMany 并使用以下命令开始测试 gradle start

为什么在这种情况下数据库的初始大小很重要?我应该将此行为视为错误吗?

【问题讨论】:

  • 为什么不看看日志就明白了?
  • 您会建议哪些日志? sql 日志仅在第一个周期(内部 for)中有所不同,它还包含许多实体的获取。这并不能解释降级,因为随后的循环没有获取它们(很可能是因为它们被缓存以供以后使用,正如预期的那样)。
  • 您使用的 JPA 实现的日志。我使用的实现(DataNucleus)总是显示大量信息来追踪潜在问题,所以我认为其他实现同样有用
  • persist 操作发出的唯一日志条目只是调用persist 的注释。没什么用。

标签: hibernate jpa eclipselink one-to-many persist


【解决方案1】:

我认为问题出在this.one.getMany(),因为在每次迭代中,越来越多的实体需要从这种关系中加载。

@OneToMany 关系默认是惰性的,所以当你调用getMany() 时,JPA 提供者必须初始化集合中的每个实体,随着它的大小增加,这需要更多时间。

如果您不在每次迭代中创建新的EntityManagerFactory,则上次迭代中的实体会保留在缓存中,因此执行的查询会少很多。

【讨论】:

  • 那么我希望在第一次调用 getMany() 以填充缓存时会有一个延迟,但它在所有实体(所有 20000 多个)中仍然很慢。
  • 我检查过了,只有第一次调用 getMany() 获取条目:/
【解决方案2】:

只是为了扩展 Predrag 的答案 - 遍历 1:M 关系不仅需要引入实体和任何扩展对象图的成本,而且这些实体仍然在持久单元内进行管理。因为您的测试为重复事务重用相同的 EntityManager,所以托管实体的缓存会随着每次迭代而继续增长。每次上下文与数据库同步时,都必须遍历托管实体的缓存并检查更改 - 这发生在刷新、事务提交甚至查询时。

如果您必须引入大型对象图,可以采取哪些措施来缓解这种情况,即为每个事务边界释放并获取新的 EntityManager,或者偶尔刷新并清除 EntityManager。任何一个选项都允许它释放一些托管实体,因此它不需要在每次提交时都检查它们是否有更改。

编辑> 您的“Many”类已经覆盖了 hashCode 方法,并且正在使用其引用的“One”的哈希码及其主键来构建其哈希码。这会导致您在循环中坚持的每个“许多”都具有相同的哈希码,因为 GenerationType.IDENTITY 只能在插入语句发生时分配序列 - 这发生在同步期间(刷新/提交)。此方法可能会导致缓存查找(由于级联持久调用而导致提供程序遍历每个持久调用上不断增长的对象模型时发生)花费越来越长的时间。

【讨论】:

  • 缓存的大小会增加,我知道,但如果没有重新创建 EMF,性能不会下降,即使在这种情况下,所有实体都将被管理并存在于缓存中。另请注意,内部 for 循环在 while 循环中花费相同的时间,但在每次 while 迭代后急剧增加。
  • 尝试删除关系上的级联持久化选项,这样持久性就不必通过不断增长的对象模型进行级联,但我不确定我是否理解你的说法。 EM 有自己的高速缓存,它会不断增长,并且您传递给持久化的每个对象都必须根据它进行检查。您还覆盖了等号和哈希码 - 尝试删除您的实现,看看这是否会影响您的结果。
  • 确实,问题出在 equals/hashCode 上,但不是您所期望的:) 没有它们,插入时间会在每个内部 for 循环之后增加(在一段时间内是恒定的)。因此,实施不当的 equals/hashCode 以某种方式暂时隐藏了降级。
猜你喜欢
  • 2016-11-03
  • 2021-08-11
  • 1970-01-01
  • 2012-03-27
  • 1970-01-01
  • 1970-01-01
  • 2018-04-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多