如何持久化大量实体(JPA)

21

我需要处理一个CSV文件,并且对于每条记录(行)都要保留一个实体。目前,我是这样做的:

while ((line = reader.readNext()) != null) {
    Entity entity = createEntityObject(line);
    entityManager.save(entity);
    i++;
}

这里的save(Entity)方法基本上只是一个EntityManager.merge()调用。CSV文件中大约有20,000个实体(行)。这种方法有效吗?它似乎非常慢。使用EntityManager.persist()会更好吗?这种解决方案有什么缺陷吗?

编辑

这是一个漫长的过程(超过400秒),我尝试了使用persistmerge两种解决方案。两者完成所需的时间大致相同(459秒对443秒)。问题在于像这样逐个保存实体是否最优。据我所知,Hibernate(我的JPA提供程序)确实实现了一些缓存/刷新功能,因此我不必担心这个问题。

4个回答

14
JPA API不能提供所有选项来实现最佳性能。根据您想要做得有多快,您将不得不寻找ORM特定选项-在这种情况下是Hibernate。
需要检查的事项: 1. 检查是否使用单个事务(是的,显然您确定了这一点) 2. 检查JPA提供程序(Hibernate)是否正在使用JDBC批处理API(请参阅:hibernate.jdbc.batch_size) 3. 检查是否可以绕过获取生成的键(取决于数据库/ JDBC驱动程序,您从中获得多少好处-请参阅:hibernate.jdbc.use_getGeneratedKeys) 4. 检查是否可以绕过级联逻辑(仅有最小的性能优势)
因此,在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(但不能每个事务设置)。 - Rob Bygrave

6

我认为一种常见的方法是使用事务。如果您开始一个新的事务,然后持久化大量对象,直到您提交事务它们才会被插入到数据库中。如果您需要提交大量项目,这可以带给您一些效率。

请查看EntityManager.getTransaction


1
它在一个事务中运行(使用Spring的@Transactional注解)。 - John Manak
你可以尝试移除注释,看看性能是否会有所改变。你也可以通过设置断点,在一定数量的持久化调用后检查数据库,确认行还没有被插入来确认它是否是一次性提交。可能是Spring在10或100个调用后提交,你可以进行一些调整以改善性能。 - dough

5
为了让它更快,至少在Hibernate中,您需要在插入一定数量的记录后进行flush()和clear()操作。 我已经使用过这种方法处理了数百万条记录,它是有效的。 它仍然慢,但比不做这些操作要快得多。 基本结构如下所示:
int i = 0;
for(MyThingy thingy : lotsOfThingies) {

    dao.save(thingy.toModel())

    if(++i % 20 == 0) {
        dao.flushAndClear();
    }

}

4

1
在这种情况下,本地查询不会提供太多的加速。你所能做的就是将它们分组并进行批处理,这可以在JPA提供程序级别或JDBC驱动程序级别上完成。然而,在我的特定情况下,我可以使用INSERT INTO ... SELECT FROM ... 组合,这将大大提高速度,因此我支持这个方法。 - Jacek Prucia

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