JPA使用对象图表插入速度慢

时间:2010-06-23 22:56:23

标签: jpa toplink toplink-essentials

我正在尝试使用JPA对大型对象图进行级联保存。例如(我的对象图有点大但足够接近):

@Entity
@Table(name="a")
public class A {
  private long id;
  @OneToMany(cascade = CascadeType.ALL, mappedBy = "a")
  private Collection<B> bs;
}

@Entity
@Table(name="b")
public class B {
  private long id;
  @ManyToOne
  private A a;
}

所以我试图坚持拥有100多个B的集合。代码只是

em.persist(a);

问题是,它很慢。我的节省大约需要1300毫秒。我查看了生成的SQL,它非常低效。像这样:

select a_seq.nextval from dual;
select b_seq.nextval from dual;
select b_seq.nextval from dual;
select b_seq.nextval from dual;
...
insert into a (id) values (1);
insert into b (id, fk) values (1, 1);
insert into b (id, fk) values (2, 1);
insert into b (id, fk) values (3, 1);
...

目前使用toplink作为持久性提供程序,但我也尝试过eclipselink和hibernate。后端是oracle 11g。问题实际上是如何将sql放在一起。这些操作中的每一个都是分散完成而不是批量完成,因此如果我的appserver和db服务器之间的网络延迟甚至达到5ms,那么执行200次离散操作会增加1秒。我已经尝试过增加我的序列的allocationSize,但这只会有所帮助。我也尝试过直接JDBC作为批处理语句:

for...{
  statement = connection.prepareStatement(sql);
  statement.addBatch();
}
statement.executeBatch();

对于我的datamodel,直接JDBC批处理大约需要33ms。对于100多个插入,Oracle本身需要5毫秒。

无论如何制作JPA(我现在都坚持使用1.0),如果不深入研究像hibernate批量插入这样的供应商特定内容会更快吗?

谢谢!

3 个答案:

答案 0 :(得分:2)

解决方案是启用JDBC批处理并定期刷新和清除EntityManager(与批处理大小相同),但我不知道供应商中立的方法:

  • 使用Hibernate,您必须设置hibernate.jdbc.batch_size配置选项。请参阅Chapter 13. Batch processing

  • 使用EclipseLink,看起来有批处理写入模式。参见Jeff Sutherland在this thread中的帖子(也应该可以指定大小)。

  • 根据this blog post的评论,TopLink Essentials中没有批量写作:(

答案 1 :(得分:2)

好奇为什么你发现增加INCREMENT BY是脏的?这是一种优化,它减少了对数据库的调用次数以检索下一个序列值,并且是在INSERT之前在客户端中分配了id值的数据库客户端中使用的常见模式。我不认为这是一个JPA或ORM问题,并且在JDBC比较中应该是相同的成本,因为它还必须为INSERT之前的每个新行检索新的序列号。如果在JDBC案例中有不同的方法,那么我们应该能够让EclipseLink JPA遵循相同的方法。

在隔离的INSERT场景中,JPA的成本可能是最明显的,因为您没有从事务或共享缓存上的重复读取中获得任何好处,并且根据您的缓存配置,您需要付出代价将这些新实体放入刷新/提交中的缓存。

请注意,创建第一个EntityManager还需要付出代价,其中包括所有元数据处理,类加载,可能编织和元模型初始化。确保你把这段时间用在比较之外。在您的实际应用程序中,这会发生一次,所有后续EntityManager都会从共享元数据中受益。

如果您有其他需要阅读这些实体的方案,那么将它们放入缓存中的成本可以降低检索它们的成本。根据我的经验,我可以使一个应用程序整体上比一个典型的手写JDBC解决方案快得多,但它在整个并发用户集中是平衡的,而不是在一个孤立的测试用例上。

我希望这会有所帮助。很高兴提供更多指导和EclipseLink JPA及其性能和可扩展性选项。

道格

答案 2 :(得分:1)

感谢Pascal的回复。我做了一些测试,我能够显着提高性能。

没有优化我插入大约1100毫秒。使用eclipselink我添加到persistence.xml:

   <property name="eclipselink.jdbc.batch-writing" value="JDBC"/>
   <property name="eclipselink.jdbc.batch-writing.size" value="1000"/>

我尝试了其他属性(Oracle-JDBC等),但JDBC似乎提供了最佳的性能提升。这使插入件下降到大约900ms。所以相当适度的性能提升了200ms。增加序列allocationSize可以节省大量资金。我不是这样做的忠实粉丝。为了适应JPA,我觉得增加我的序列的INCREMENT BY很脏。增加这些使每个插入物的时间减少到大约600ms。因此,总共大约500毫秒被削减了这些增强功能。

这一切都很好,花花公子,但它仍然比JDBC批处理慢得多。 JPA是一个非常高的代价,易于编码。