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

Java 使用hibernate更新数据库上的大量行,java,hibernate,batch-processing,spring-batch,Java,Hibernate,Batch Processing,Spring Batch,我被要求使用SpringBatch、Hibernate和Quartz重写一些批处理作业。当前的实现已经使用了Hibernate,但是它们的工作方式有问题,因为它们需要花费太多的时间来完成任务 此任务包括从XML文件中获取项,并更新(或插入,但不经常发生)DB表中的对应行: <items> <item> <id>10005011</id> <field_1></field_1> <

我被要求使用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>
请注意,第一次迭代需要几秒钟,随着批处理的进行,它需要几分钟。。。我正在更新大约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);
    }
}

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


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

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

  • 从数据库中获取整个表肯定是错误的。使用(:IDs)子句中的
    where item.id,仅获取您在当前XML块中看到其id的项目
    
  • 无状态会话不能与Hibernate的持久性管理功能一起使用(无
    保存
    更新
    合并
    ,等等---只允许
    执行更新
    ,这会导致针对数据库的即时SQL)
  • 常规的、有状态的Hibernate会话会累积您所涉及的所有bean,直到提交或显式的
    清除(或者在一些特殊情况下,我们不应该在这里讨论)
  • 简而言之,批处理更新循环的框架应该是这样的:

    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();
    

    (50是一个良好的默认值,应保持在20到100之间)。否则,将不会使用JDBC批处理API。

    在tx.commit()之后调用tx.flush(),因此,从buffersession管理中清除更改应该是一个任务,而不是您的任务。只需考虑单个对象的读/写(更新),而不是块。对于您的场景,区块只是一个数字(提交间隔<代码>)@LucaBassoRicci我尝试使用JPAItemWriter,但对于项目约束,我必须使用Hibernate 3.X和SpringBatch 2.2.X,它似乎期望Hibernate 4对象,这导致了一些ClassCastException,因此我的编写器是“不可知的”,将工作委托给DAO,而不是将所有项目读入
    synchronizeItems
    调用方法
    synchronizeItem
    ,并对需要的每个项目执行读取/更新/插入manage@LucaBassoRicci我为每个区块添加了一个` session.flush()'。由于我没有使用事务(如果任务被中断,它将从第一项重新启动),我认为这就足够了。我可以在in子句中放入多少ID?如果需要的话,我可以选择较小的块大小,但是括号中的1000个项目似乎太多了。1000正是Oracle的限制,我认为其他数据库甚至没有这个限制。1000是一个很好的值,我个人在许多项目中使用过它。我使用的是DB2,限制似乎是相同的:1000个参数。。。正在尝试。根据你的建议,当前的实现太差劲了,我已经重写了所有DAO代码。现在完成任务需要几分钟,节省了几个小时。好的。我删除否决票,因为您回答了OP问题。真正的问题是OP-question:它混合了基于SB/Hibernate的应用程序,他的代码应该根据SB指南进行重组当前的实现缺少
    flush()
    调用。加上这一点有帮助
    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();
    }
    
    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