问题 使用JPA + Hibernate进行大规模插入


我需要使用EJB 3,Hibernate,Spring Data和Oracle进行大量插入。最初,我使用的是Spring Data,代码如下:

talaoAITDAO.save(taloes);

talaoAITDAO是一个Spring数据 JpaRepository 子类和taloes是TalaoAIT实体的集合。在此实体中,其各自的ID具有以下形式:

@Id
@Column(name = "ID_TALAO_AIT")
@SequenceGenerator(name = "SQ_TALAO_AIT", sequenceName = "SQ_TALAO_AIT", allocationSize = 1000)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SQ_TALAO_AIT")
private Long id;

此实体也没有相关实体进行级联插入。

我的问题在于,所有实体都是单独插入的(例如 INSERT INTO TABLE(col1, col2) VALUES (val1, val2))。有时,它可能会导致超时,并且所有插入都将被回滚。我想要在批量插入中转换这些单独的插入(例如 INSERT INTO TABLE(col1, col2) VALUES (val11, val12), (val21, val22), (val31, val32), ...)。

我找到了研究替代方案以提高性能 这一页 在hibernate文档中,超越 Hibernate批量大小混乱 和 这个其他页面。基于它们,我写了这段代码:

Session session = super.getEntityManager().unwrap(Session.class);
int batchSize = 1000;
for (int i = 0; i < taloes.size(); i++) {
    TalaoAIT talaoAIT = taloes.get(i);
    session.save(talaoAIT);
    if(i % batchSize == 0) {
        session.flush();
        session.clear();
    }
    taloes.add(talaoAIT);
}
session.flush();
session.clear();

另外,在peristence.xml中,我添加了以下属性:

<property name="hibernate.jdbc.batch_size" value="1000" />
<property name="order_inserts" value="true" />

然而,虽然在我的测试中我发现了一个微妙的差异(主要是大集合和大批量),但它并没有那么大。在日志控制台中,我看到Hibernate继续进行单独插入,而不是替换它们进行大量插入。在我的实体中,我使用的是序列生成器我相信它不是问题(根据Hibernate文档,如果我使用Identity生成器,我会遇到问题)。

所以,我的问题是这里可以缺少什么。一些配置?有些方法没用过?

谢谢,

拉斐尔阿丰索。


12867
2017-11-29 12:01


起源

你为什么打开包装? session?你可以做 flush() 和 clear() 在...上 entityManager 直。但是:通常使用java进行大插入是错误的方法;将所有内容转储到文件,将这些转移到目标服务器并进行批量加载通常效果更好。也就是说,偶尔你需要在代码中进行一些转换;这也许就是其中之一。 - beerbajay
我首先会将批量大小减少到更合理的范围(比如50)。然后为hibernate启用DEBUG日志记录,看看发生了什么。还要确保您拥有支持批量更新的数据库(和JDBC驱动程序)。您使用的是哪个数据库和hibernate版本? - M. Deinum
如果我记得这是正确的,那么即使使用了bulkinserts,你也会在日志中为每个实体找到单个insert-statement。如果启用<category name =“org.hibernate”> <priority value =“DEBUG”/> </ category>,您应该会看到有关批量更新的一些特殊信息。像“[AbstractBatcher]执行批量大小:5”和“[期望]批量更新成功未知:0” - treeno
M. Deinium:我正在使用Hibernate 4.1.9和oracle 11.2。关于Sequence,因为我将SequenceGenerator中的allocationSize定义为1000,每1000次插入就会调用它。 - Rafael Afonso
嗨,到目前为止任何更新..?我有相同的问题,我已正确更新每个配置,但仍然弹簧数据jpa generationg多个插入语句。 - utsav anand


答案:


有几件事。

首先,您的配置属性是错误的 order_inserts 一定是 hibernate.order_inserts 。目前您的设置被忽略,您没有更改任何内容。

接下来使用 EntityManager 而不是做所有讨厌的hibernate东西。该 EntityManager 还有一个 flush 和 clear 方法。这至少应该清理你的方法。如果没有这个顺序,这有助于清理会话并防止对那里的所有对象进行脏检查。

EntityManager em = getEntityManager();
int batchSize = 1000;
for (int i = 0; i < taloes.size(); i++) {
    TalaoAIT talaoAIT = taloes.get(i);
    em.persist(talaoAIT);
    if(i % batchSize == 0) {
        em.flush();
        em.clear();
    }
    taloes.add(talaoAIT);
}
em.flush();
em.clear();

接下来你不应该让你的批次变大,因为这可能会导致内存问题,从50开始,测试哪些/什么表现最佳。有一点,脏检查将花费更多时间,然后刷新并清除到数据库。你想找到这个甜蜜点。


15
2017-11-29 12:48



实际上,使用batchsize写一个循环在20到50之间,并在该循环中执行'flush'和'clear'。此外,hibernate属性应该是相同的批处理大小:<property name =“hibernate.jdbc.batch_size”value =“xxx”/> - K.C.
你如何得到你的entityManager?我尝试刷新时不断收到javax.persistence.TransactionRequiredException错误 - obesechicken13
所以起初我尝试使用@PersistentContext EntityManager entityManager连接实体管理器,但后来我不允许使用共享实体管理器进行刷新。所以我在这里遵循了解决方案 stackoverflow.com/questions/26606608/... 而它只是挂起 - obesechicken13


M. Deinum发布的解决方案非常适合我,前提是我在JPA中设置了以下Hibernate属性 persistence.xml 文件:

<property name="hibernate.jdbc.batch_size" value="50" />
<property name="hibernate.jdbc.batch_versioned_data" value="true" />
<property name="hibernate.order_inserts" value="true" />
<property name="hibernate.order_updates" value="true" />
<property name="hibernate.cache.use_second_level_cache" value="false" />
<property name="hibernate.connection.autocommit" value="false" />

我正在使用Oracle数据库,所以我也定义了这个:

<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect" />

1
2018-02-09 18:29





我最近发现了一个很有前途的小库,用于使用Hibernate和Postgresql批量插入。它被称为 踏板方言 并使用Postgresql - 命令 COPY 许多人声称要比批量插入更快(参考: Postgresql手册Postgresql插入策略 - 性能测试复制如何工作以及为什么它比插入快得多?)。踏板方言允许使用 COPY没有完全失去Hibernate的易用性。您仍然可以自动映射实体和行,而不必自己实现它。


0
2017-07-26 14:52