JPA持续变得越来越慢

时间:2016-02-29 13:08:24

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

此方案使用简单的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();
}

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

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

备注:

  • 我尝试过eclipseLink,Hibernate,OpenJPA,并且都遭受了这种放缓。
  • 如果我没有更新One的One集合,那么就不会有任何退化(请参阅许多的注释行。)
  • 如果我没有创建新的EntityManagerFactory,那么即使在50万个实体之后也没有退化。
  • 慢的部分是em.persist(many);(我测量过它)。
  • 查看https://github.com/kupsef/OneToMany并开始测试 gradle start

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

2 个答案:

答案 0 :(得分:4)

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

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

编辑&gt; 您的“Many”类重写了hashCode方法,并使用其主键引用的“One”的哈希码构建其哈希码。这会导致您在循环中持久保存的每个“Many”具有相同的哈希码,因为GenerationType.IDENTITY只能在insert语句发生时分配序列 - 这在同步(刷新/提交)期间发生。此方法可能导致缓存查找,当提供程序由于级联持久调用而在每个持久调用上遍历不断增长的对象模型时,会发生缓存查找,这需要更长时间。

答案 1 :(得分:2)

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

默认情况下,

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

如果您在每次迭代中都没有创建新的EntityManagerFactory,那么上一次迭代中的实体将保留在缓存中,因此执行的查询会少得多。