有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

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

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

<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个项目,编写器接收该大小的列表以更新表,并将其委托给DAO(ItemDao)

其工作方式如下:

  1. 它读取表中的所有项目(获取列表)
  2. 将这些项目的ID作为键存储在地图中
  3. 循环作为参数接收的项目列表,并逐个复制现有项目中的所有非空字段。Hibernate自动更新修改过的bean

我面临的问题是,每一个chuck都比前一个chuck需要更多的时间,而且没有明显的原因。这是我的日志

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) 个答案

  1. # 1 楼答案

    我没有使用Spring Batch的经验,但从我使用纯Hibernate的工作中得出的这些指导原则可能会帮助您:

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

    简而言之,批处理更新循环的框架应该是这样的:

    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批处理API

  2. # 2 楼答案

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

    事实上,在每1000次更新之后(在hibernate端或DB端),它会在某个地方保存越来越多的信息,以便为回滚做好准备。所以,在您提交之前,所有数据都在缓冲区中的某个位置