使用 JPA + Hibernate 进行大规模插入
Posted
技术标签:
【中文标题】使用 JPA + Hibernate 进行大规模插入【英文标题】:Massive insert with JPA + Hibernate 【发布时间】:2013-12-15 15:01:18 【问题描述】:我需要使用 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 文档中找到了this page Hibernate batch size confusion 和 this other page。基于它们,我编写了以下代码:
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 文档,如果我使用身份生成器,我会遇到问题)。
所以,我的问题是这里可能缺少什么。一些配置?有什么方法没用过?
谢谢,
拉斐尔·阿方索。
【问题讨论】:
你为什么要解开session
?您可以直接在entityManager
上执行flush()
和clear()
。但是:通常用 java 做大插入是错误的方法;将所有内容转储到文件,将它们传输到目标服务器并批量加载通常效果更好。也就是说,有时您实际上需要对代码进行一些转换;这可能是其中一种情况。
我会首先将批量大小缩减到更合理的值(比如 50)。然后为休眠启用调试日志记录,看看发生了什么。还要确保您有一个支持批量更新的数据库(和 JDBC 驱动程序)。您使用的是哪个数据库和休眠版本?
如果我没记错的话,即使使用了bulkinserts,您也会在日志中找到每个实体的单个插入语句。如果启用 有几件事。
首先你的配置属性是错误的 order_inserts
必须是 hibernate.order_inserts
。目前您的设置被忽略并且您没有更改任何内容。
接下来使用EntityManager
而不是做那些讨厌的休眠工作。 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 之类的开始,然后测试哪个/什么表现最好。有时脏检查将花费更多时间,然后对数据库进行刷新和清除。你想找到这个甜蜜点。
【讨论】:
确实,编写一个批处理大小在 20 到 50 之间的循环,并在该循环中执行“刷新”和“清除”。此外,休眠属性应该是相同的批量大小:如果我在我的 JPA persistence.xml
文件中设置了以下 Hibernate 属性,M. Deinum 发布的解决方案对我来说非常有用:
<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" />
【讨论】:
【参考方案3】:我最近发现了一个很有前途的小型库,用于使用 Hibernate 和 Postgresql 批量插入。它被称为 pedal-dialect 并使用 Postgresql - 命令 COPY
,许多人声称它比批量插入要快得多(参考:Postgresql manual、Postgresql Insert Strategies - Performance Test、How does copy work and why is it so much faster than insert?)。踏板方言允许使用 COPY
而不会完全失去 Hibernate 的易用性。您仍然可以获得实体和行的自动映射,而不必自己实现它。
【讨论】:
以上是关于使用 JPA + Hibernate 进行大规模插入的主要内容,如果未能解决你的问题,请参考以下文章
使用 Spring JPA / Hibernate 进行条件插入
使用 JPA 在 Hibernate 中使用 EAGER 类型进行多次提取
使用 JPA / Hibernate 在无状态应用程序中进行乐观锁定