5

我正在尝试使用 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的集合的A。代码只是

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 放在一起。这些操作中的每一个都是离散而不是批量完成的,所以如果我的应用服务器和数据库服务器之间的网络延迟甚至为 5 毫秒,那么执行 200 次离散操作会增加 1 秒。我已经尝试增加序列的 allocationSize ,但这只会有所帮助。我还尝试将直接 JDBC 作为批处理语句:

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

对于我的数据模型,直接 JDBC 批处理大约需要 33 毫秒。Oracle 本身为 100 多个插入花费了 5 毫秒。

有没有让 JPA(我现在被 1.0 卡住......)在不深入研究供应商特定的东西(如休眠批量插入)的情况下运行得更快?

谢谢!

4

3 回答 3

3

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

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

请注意,创建第一个 EntityManager 也是有成本的,其中所有元数据处理、类加载、可能的编织和元模型初始化都在其中。确保将这段时间排除在比较之外。在您的实际应用程序中,这会发生一次,所有后续的 EntityManager 都将从共享元数据中受益。

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

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

道格

于 2010-07-05T14:54:27.423 回答
2

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

在没有优化的情况下,我插入大约需要 1100 毫秒。使用 eclipselink 我添加到 persistence.xml:

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

我尝试了其他属性(Oracle-JDBC 等),但 JDBC 似乎提供了最佳的性能提升。这使插入时间缩短到大约 900 毫秒。所以相当适度的性能增加了 200 毫秒。增加序列分配大小带来了很大的节省。我不喜欢这样做。我发现仅仅为了适应 JPA 而增加我的序列的 INCREMENT BY 很脏。增加这些可以将每个插入的时间减少到大约 600 毫秒。因此,这些增强功能总共缩短了大约 500 毫秒。

所有这一切都很好而且很花哨,但它仍然比 JDBC 批处理慢得多。JPA 为易于编码付出了相当高的代价。

于 2010-06-25T16:22:09.450 回答
2

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

  • 使用 Hibernate,您必须设置hibernate.jdbc.batch_size配置选项。请参阅第 13 章。批处理

  • 使用 EclipseLink,看起来有一个批量写入模式。请参阅此线程中 Jeff Sutherland 的帖子(也应该可以指定大小)。

  • 根据这篇博文的评论,TopLink Essentials 中不支持批量写作 :(

于 2010-06-24T02:41:34.577 回答