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

12

我需要使用 EJB 3、Hibernate、Spring Data 和 Oracle 进行大规模插入。最初,我正在使用 Spring Data,以下是代码:

talaoAITDAO.save(taloes);

talaoAITDAO是Spring Data的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 batch size confusion这另一个页面。基于它们,我编写了这段代码:

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();

另外,在 persistence.xml 文件中,我添加了以下这些属性:

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

然而,尽管在我的测试中我感觉到了微妙的差异(主要是在大集合和大批量大小方面),但它并不像期望的那样大。在日志控制台中,我看到Hibernate继续执行单个插入操作,而不是替换它们进行大规模插入。由于在我的实体中,我使用了Sequence生成器,我相信这不是问题(根据Hibernate文档,如果我使用Identity生成器,我会遇到问题)。

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

谢谢,

Rafael Afonso。


为什么要解包 session?你可以直接在 entityManager 上执行 flush()clear()。但是通常使用 Java 进行大量插入是错误的方式;将所有内容转储到文件中,将其传输到目标服务器并进行批量加载通常效果更好。话虽如此,偶尔你确实需要在代码中进行一些转换;这也许就是其中之一。 - beerbajay
我首先会将批处理大小缩小到更合理的层面(比如50)。然后为Hibernate启用DEBUG日志,查看发生了什么。另外,请确保您有一个支持批量更新的数据库(和JDBC驱动程序)。您使用的是哪个数据库和Hibernate版本? - M. Deinum
1
如果我没记错的话,即使使用bulkinserts,你在日志中会找到每个实体的单个insert语句。如果你启用 <category name="org.hibernate"><priority value="DEBUG" /> </category>,你应该可以看到一些有关批处理更新的特殊信息。类似于 "[AbstractBatcher] Executing batch size: 5" 和 "[Expectations] success of batch update unknown: 0"。 - treeno
M. Deinium:我正在使用Hibernate 4.1.9与Oracle 11.2。关于Sequence,由于我在SequenceGenerator中定义了allocationSize为1000,因此它将在每1000次插入时被调用。 - Rafael Afonso
你们这方面有什么更新吗?我也遇到了同样的问题,已经正确更新了每个配置,但是Spring Data JPA仍然生成多个插入语句。 - utsav anand
您可以随时回退到HQL,这里有我的示例链接 - Lukasz Frankowski
3个回答

16

有几件事情。

首先,你的配置属性是错误的,order_inserts必须改为hibernate.order_inserts。目前你的设置被忽略了,你什么都没改变。

接下来,使用EntityManager而不是做所有那些讨厌的Hibernate操作。EntityManager还有flushclear方法。这应该至少可以清理你的方法。没有这个顺序,这有助于清理会话并防止对其中所有对象进行脏检查。

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开始测试哪个表现最佳。存在这样一种情况,即脏检查所需时间比刷新到数据库并清除更长。您需要找到这个甜点。


1
确实,编写一个循环,其中batchsize在20到50之间,并在该循环中执行'flush'和'clear'操作。此外,hibernate属性应为相同的批量大小:<property name="hibernate.jdbc.batch_size" value="xxx" />。 - K.C.
你如何获取你的entityManager?每次我尝试刷新时,我都会收到javax.persistence.TransactionRequiredException错误。 - obesechicken13
所以一开始我尝试使用@PersistentContext EntityManager entityManager来连接实体管理器,但是我无法使用共享实体管理器进行刷新。所以我按照这里的解决方案https://dev59.com/LF8d5IYBdhLWcg3woDYZ,但它卡住了。 - obesechicken13

1

M. Deinum发布的解决方案对我非常有效,只要我在JPA的persistence.xml文件中设置了以下Hibernate属性:

<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" />

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接