使用hibernate在数据库上更新大量行

时间:2014-07-21 08:05:57

标签: java hibernate batch-processing spring-batch

我被要求使用Spring Batch,Hibernate和Quartz重写一些批处理作业。当前的实现已经使用了Hibernate,但它们的工作方式有问题,因为它们花费了太多时间来完成任务。

此任务包括从XML文件中获取项目并更新(或插入,但不经常发生)数据库表中的对应行:

<items>
    <item>
        <id>10005011</id>
        <field_1></field_1> <!--
        <field_2></field_2>
        ...
        <field_n></field_n>
    </item>
    <item>
        <id>23455245</id>
        <field_1></field_1> <!--
        <field_2></field_2>
        ...
        <field_n></field_n>
    </item>

    ...
    <item>
        <id>101000454</id> <!-- about 70000  items-->
        <field_1></field_1> <!--
        <field_2></field_2>
        ...
        <field_n></field_n>
    </item>
</items>

文件很大,所以我设置了1000块的大小:读者需要1000个项目,编写器会收到一个大小的List来更新表格,并将其委托给DAO(ItemDao)。

它的工作方式如下:

  1. 它读取表格中的所有项目(获取列表)
  2. 使用ID作为密钥将这些项目存储在地图中。
  3. 循环接收作为参数接收的项目列表,并逐个复制现有项目中的所有非空字段。 Hibernate自动更新修改后的bean。
  4. 我面临的问题是,每个查克都需要比前一个更多的时间,没有明显的理由。这是我的日志;

    12:33:53,376 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:33:56,927 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:33:59,258 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:34:01,358 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:34:03,145 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:34:31,872 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:35:15,694 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:36:06,211 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:37:02,154 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:38:07,124 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:39:19,519 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:40:34,432 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:41:59,926 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:43:31,951 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:45:12,337 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:46:56,331 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:48:49,726 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:50:48,649 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:52:52,897 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:55:06,056 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:57:28,105 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    12:59:55,983 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    13:02:40,224 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    13:05:29,506 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    13:08:21,031 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    13:11:18,521 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    13:14:31,911 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    13:18:03,994 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    13:21:43,960 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    13:25:32,084 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    13:29:28,366 INFO  [batch.writer.ItemWriter] Updated/inserted 1000 items
    

    请注意,第一次迭代需要几秒钟,当批处理继续时,需要几分钟......我正在更新大约70000(七万)个项目,最后一次迭代每个需要超过半个小时。

    这是DAO中被调用的方法:

    public void synchronizeItems(List<Item> newItemList,
            Jurisdiction jurisdiction) throws ServiceException {
    
        Map<Long, Item> ItemMap = new HashMap<Long, Item>();
        List<Item> existingItemList = getAllItems(jurisdiction
                .getJurisdictionId());
        for (Item o : existingItemList) {
            ItemMap.put(o.getProprietorId(), o);
        }
    
        for (Item newItem : newItemList) {
            updateItem(newItem, jurisdiction, ItemMap);
        }
    }
    
    
    private void updateItem(Item newItem, Jurisdiction jurisdiction,
            Map<Long, Item> ItemMap) throws DAOException {
    
        Item currItem = ItemMap.get(newItem.getProprietorId());
        if (currItem != null) {
            //just updates currItem, copying all not null attributes from newItem
            copyProperties(currItem, newItem); 
        } else {
            //some times there is a new item
            lspDao.create(newItem);
        }
    }
    

    所以我有两个问题:

    • 为什么循环中每次调用synchronizeItems的时间都比之前的要长?
    • 有更好的方法来更新行吗?

    我正在考虑使用无状态会话,然后只获取所有项目一次(目前查询在每个循环中执行一次),所以我必须手动调用session.update(Item),类似于:

    public void batchUpdate(List<T> list) {
        StatelessSession session = sessionFactory.openStatelessSession();
        Transaction tx = session.beginTransaction();
        for(int i=0;i < list.size();i++){
            session.update(list.get(i));
        }
        tx.commit();
    }
    

2 个答案:

答案 0 :(得分:2)

过去我遇到过类似的问题。它通过提交每个块更新来解决。因此,每次1000次更新后提交的方法应该有效。

实际上,它会在每次1000次更新(在休眠端或数据库端)之后越来越多地保留信息,以便为回滚做好准备。因此,所有数据都在缓冲区中,直到您提交。

答案 1 :(得分:1)

我没有使用Spring Batch的经验,但是这些源于我使用纯Hibernate工作的指南可能对您有所帮助:

  1. 从数据库中获取整个表肯定是错误的。使用where item.id in (:ids)子句
  2. 仅获取您在当前XML块中看到的ID的项目
  3. 无状态会话不能与Hibernate的持久性管理功能一起使用(没有saveupdatemerge等等 - 只允许executeUpdate,导致针对数据库的立即SQL);
  4. 常规的有状态Hibernate会话累积你所涉及的所有bean,直到提交或显式clear(或者在某些我们不应该在这里讨论的特殊情况)。
  5. 简而言之,这就是批量更新循环的框架应如下所示:

    Session hb = ...;
    Transaction tx = ...;
    hb.setCacheMode(CacheMode.IGNORE);
    hb.setFlushMode(FlushMode.COMMIT);
    for (List<Item> chunk : chunks) {
      ... process chunk ...
      hb.flush();
      hb.clear();
    }
    hb.commit();
    tx.close();
    

    另外请务必配置

    hibernate.jdbc.batch_size=50
    

    (50是一个很好的默认值,它应该保持在20到100的范围内)。如果没有这个,将不会使用JDBC Batch API。