使用Hibernate批量插入或更新?

时间:2011-09-08 14:14:27

标签: java mysql hibernate

我需要从每日CSV文件中消耗大量数据。 CSV包含大约120K条记录。使用hibernate时,这会慢慢爬行。基本上,当使用saveOrUpdate()时,似乎hibernate在每个INSERT(或UPDATE)之前都在执行SELECT;对于使用saveOrUpdate()持久化的每个实例,在实际INSERT或UPDATE之前发出SELECT。我能理解为什么会这样做,但是它的批量处理效率非常低,我正在寻找替代方案

我确信性能问题在于我使用hibernate的方式,因为我有另一个版本使用本机SQL(以同样的方式解析CSV),并且它实际上围绕着这个圈子运行新版)

那么,对于实际问题,是否存在一个hibernate替代mysqls“INSERT ... ON DUPLICATE”语法?

或者,如果我选择为此执行本机SQL,我可以在hibernate事务中执行本机SQL吗?意思是,它会支持提交/回滚吗?

6 个答案:

答案 0 :(得分:31)

批量操作存在许多可能的瓶颈。最好的方法在很大程度上取决于您的数据。请查看有关批处理的Hibernate Manual部分。

至少要确保使用以下模式(从手册中复制):

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
    if ( i % 20 == 0 ) { //20, same as the JDBC batch size
        //flush a batch of inserts and release memory:
        session.flush();
        session.clear();
    }
}

tx.commit();
session.close();

如果要将平面文件映射到非常复杂的对象图形,则可能需要获得更多创意,但基本原则是必须在每次刷新/向数据库推送大小合适的数据块之间找到平衡点提交并避免扩大会话级缓存的大小。

最后,如果您不需要Hibernate处理任何集合或级联以正确插入数据,请考虑使用StatelessSession

答案 1 :(得分:5)

根据an answer to a similar question,可以使用configuring Hibernate to insert objects using a custom stored procedure来完成,它使用您的数据库的upsert功能。但这并不漂亮。

答案 2 :(得分:3)

来自Hibernate Batch Processing 对于更新,我使用了以下内容:

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

ScrollableResults employeeCursor = session.createQuery("FROM EMPLOYEE")
                                   .scroll();
int count = 0;

while ( employeeCursor.next() ) {
   Employee employee = (Employee) employeeCursor.get(0);
   employee.updateEmployee();
   seession.update(employee); 
   if ( ++count % 50 == 0 ) {
      session.flush();
      session.clear();
   }
}
tx.commit();
session.close();

但是对于插入,我会选择 jcwayne 回答

答案 3 :(得分:1)

如果使用序列或本机生成器,Hibernate将使用select来获取id:

<id name="id" column="ID">
    <generator class="native" />
</id>

你应该使用hilo或seqHiLo生成器:

<id name="id" type="long" column="id">  
    <generator class="seqhilo">
        <param name="sequence">SEQ_NAME</param>
        <param name="max_lo">100</param>
    </generator>
</id>

答案 4 :(得分:1)

如果只想导入数据而不进行任何处理或转换,那么PostgreSQL COPY之类的工具是导入数据的最快方法。

但是,如果需要进行转换,数据聚合,现有数据与传入数据之间的关联/合并,则需要应用程序级批处理。

在这种情况下,正如我在this article中所解释的那样,您希望定期刷新-清除-提交:

int entityCount = 50;
int batchSize = 25;

EntityManager entityManager = entityManagerFactory()
    .createEntityManager();

EntityTransaction entityTransaction = entityManager
    .getTransaction();

try {
    entityTransaction.begin();

    for (int i = 0; i < entityCount; i++) {
        if (i > 0 && i % batchSize == 0) {
            entityTransaction.commit();
            entityTransaction.begin();

            entityManager.clear();
        }

        Post post = new Post(
            String.format("Post %d", i + 1)
        );

        entityManager.persist(post);
    }

    entityTransaction.commit();
} catch (RuntimeException e) {
    if (entityTransaction.isActive()) {
        entityTransaction.rollback();
    }
    throw e;
} finally {
    entityManager.close();
}

此外,请确保还使用以下配置属性启用JDBC批处理:

<property
    name="hibernate.jdbc.batch_size"
    value="25"
/>

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

<property
    name="hibernate.order_updates"  
    value="true"
/>

有关这些Hibernate配置属性的更多详细信息,请查看this article

答案 5 :(得分:0)

“额外”选择是为您的数据生成唯一标识符。

切换到HiLo序列生成,您可以通过分配大小的数量减少到数据库的序列往返。请注意,除非您调整HiLo发生器的序列值,否则主键会有间隙