如何持久化大量实体(JPA)
Posted
技术标签:
【中文标题】如何持久化大量实体(JPA)【英文标题】:How to persist a lot of entities (JPA) 【发布时间】:2011-08-04 16:41:07 【问题描述】:我需要处理一个 CSV 文件并为每条记录(行)保留一个实体。现在,我是这样做的:
while ((line = reader.readNext()) != null)
Entity entity = createEntityObject(line);
entityManager.save(entity);
i++;
save(Entity)
方法基本上只是一个EntityManager.merge()
调用。 CSV 文件中有大约 20,000 个实体(行)。这是一种有效的方法吗?它似乎很慢。使用EntityManager.persist()
会更好吗?这个解决方案有任何缺陷吗?
编辑
这是一个漫长的过程(超过 400 次),我尝试了两种解决方案,persist
和 merge
。两者都需要大约相同的时间来完成(459 秒对 443 秒)。问题是这样一个一个地保存实体是否是最优的。据我所知,Hibernate(这是我的 JPA 提供程序)确实实现了一些缓存/刷新功能,所以我不必担心这一点。
【问题讨论】:
【参考方案1】:您可以使用经典的 SQL 插入语句将它们直接写入数据库。
@见EntityManager.createNativeQuery
【讨论】:
在这种特殊情况下,本机查询不会提供太多加速。您所能做的就是将它们与批处理组合在一起,您可以在 JPA 提供程序级别或 JDBC 驱动程序级别进行操作。但是,在我的特殊情况下,我可以使用 INSERT INTO ... SELECT FROM ... 组合,这将大大加快速度,所以我的 +1 也是如此。【参考方案2】:我认为一种常见的方法是使用交易。如果您开始一个新事务然后持久化大量对象,那么在您提交事务之前,它们实际上不会被插入到数据库中。如果您要提交大量项目,这可以提高您的效率。
查看EntityManager.getTransaction
【讨论】:
它确实在事务中运行(使用 Spring 的 @Transactional)。 您可以尝试删除注释并查看性能是否发生变化。您还可以通过设置断点来确认它正在使用一举,并且在运行了一些 perist 调用后检查数据库以确认尚未插入行。可能是在 10 次或 100 次左右调用之后 spring 提交,您可以进行一些调整来改变性能。【参考方案3】:为了让它运行得更快,至少在 Hibernate 中,你会在一定数量的插入之后执行 flush() 和 clear()。我已经为数百万条记录完成了这种方法,并且它有效。它仍然很慢,但比不做要快得多。基本结构是这样的:
int i = 0;
for(MyThingy thingy : lotsOfThingies)
dao.save(thingy.toModel())
if(++i % 20 == 0)
dao.flushAndClear();
【讨论】:
【参考方案4】:JPA API 并未为您提供所有选项以使其达到最佳状态。根据您想要执行此操作的速度,您将不得不寻找 ORM 特定选项 - 在您的情况下为 Hibernate。
检查事项:
-
检查您使用的是单笔交易(是的,显然您对此很确定)
检查您的 JPA 提供程序 (Hibernate) 是否使用 JDBC 批处理 API(请参阅:hibernate.jdbc.batch_size)
检查是否可以绕过获取生成的密钥(取决于 db/jdbc 驱动程序从中获得多少好处 - 请参阅:hibernate.jdbc.use_getGeneratedKeys)
检查您是否可以绕过级联逻辑(仅能从中获得最小的性能优势)
所以在 Ebean ORM 中,这将是:
EbeanServer server = Ebean.getServer(null);
Transaction transaction = server.beginTransaction();
try
// Use JDBC batch API with a batch size of 100
transaction.setBatchSize(100);
// Don't bother getting generated keys
transaction.setBatchGetGeneratedKeys(false);
// Skip cascading persist
transaction.setPersistCascade(false);
// persist your beans ...
Iterator<YourEntity> it = null; // obviously should not be null
while (it.hasNext())
YourEntity yourEntity = it.next();
server.save(yourEntity);
transaction.commit();
finally
transaction.end();
哦,如果您通过原始 JDBC 执行此操作,您将跳过 ORM 开销(更少的对象创建/垃圾收集等) - 所以我不会忽略该选项。
所以是的,这并不能回答您的问题,但可能有助于您搜索更多 ORM 特定的批量插入调整。
【讨论】:
您可以检查 hibernate.jdbc.batch_size 和 hibernate.jdbc.use_getGeneratedKeys (但不能为每个事务设置)。以上是关于如何持久化大量实体(JPA)的主要内容,如果未能解决你的问题,请参考以下文章
如何持久化具有两级 oneToMany 关系的 JPA 实体?
Java JPA(EclipseLink)如何在持久化实际实体之前接收下一个 GeneratedValue?