2010-06-23 77 views
4

我正在尝试使用JPA在大对象图上进行级联保存。例如(我的对象图是大了一点,但足够接近):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也冬眠。后端是oracle 11g。问题实际上是如何将sql放在一起。这些操作中的每一个都是分散完成的,而不是批量完成,所以如果我的应用服务器和数据库服务器之间的网络延迟为5毫秒,则执行200次离散操作会增加1秒。我试过增加我的序列的分配大小,但只有一点帮助。我也试过直接使用JDBC作为批处理声明:

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

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

是否有任何使JPA(我现在坚持1.0)......无需钻研供应商特定的东西,如冬眠批量插入更快?

谢谢!

回答

2

的解决办法是让JDBC批处理和冲洗,清除了EntityManager定期(而不是批量大小相同),但我不知道的厂商中立的方式做到这一点:

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

  • 对于EclipseLink,它看起来像是批处理写入模式。见杰夫·萨瑟兰的帖子this thread(应该也可以指定大小)。

  • this blog post的意见,批量写入不可用的TopLink要点:(

+0

感谢您的回应!将发布我在下面做的事情! – user364939 2010-06-25 15:38:08

+0

感谢您的信息,非常好 – Greg 2011-04-06 23:23:16

1

感谢帕斯卡尔的反应。我已经做了一些测试,我能显著提高性能。

由于没有优化我有一个插入,大约需要1100毫秒用我加入的persistence.xml的EclipseLink:

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

我尝试了其他属性(Oracle-JDBC等),但JDBC看起来能够提供最佳性能。这使插入下降到约900毫秒。所以200ms的性能相当适中。增加序列分配大小节省了大量资金。我不是做这件事的粉丝。我发现为了适应JPA而增加我的序列的INCREMENT BY是很脏的。增加这些时间使每个插件的时间降低到大约600ms。所以总共大约500毫秒被削减与这些增强。

所有这些都很好,很棒,但它仍然比JDBC批处理速度慢得多。为便于编码,JPA的代价相当高昂。

+0

感谢您的反馈。我应该注意到'allocateSize'。 +1 – 2010-06-25 16:27:34

2

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

JPA的成本可能在隔离INSERT场景中最明显,因为您没有从重复读取事务或共享缓存中获得任何好处,并且取决于您为支付这些新实体的价格而付出的缓存配置flush/commit中的缓存。

请注意,创建第一个EntityManager也需要花费所有的元数据处理,类加载,可能的编织和元模型初始化。确保你保持这个时间超出你的比较。在您的真实应用程序中,只会发生一次,所有后续的EntityManager都将从共享元数据中受益。

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

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

Doug

+0

感谢您的回复。对于像oracle这样的数据库进行序列获取,我不确定为什么你不能在插入语句(my_seq.nextval)中放置这个权利。多次这样做的网络延迟会导致减速。在oracle中获取下一个序列值所需的时间在统计上是不显着的。 – user364939 2010-12-10 00:21:29

+2

在大多数数据库中,在INSERT语句内赋值非常快。挑战在于,您通常还需要应用程序中用于缓存,维护身份或级联主键的应用程序的新值。如果您的数据库支持在INSERT中使用nextval,则它还必须从INSERT中返回值以供JPA提供程序使用。 – 2011-02-08 13:24:29

+0

好点..... – user364939 2011-09-29 23:45:58