Spring批处理:替代JpaPagingItemReader导致ORA-01555

时间:2017-05-20 09:21:52

标签: spring oracle spring-data-jpa spring-batch

我有一个包含两个步骤的批处理作业

  1. 步骤1:转到外部数据库,调用存储过程,编写jpa实体并使用标记NOT_PROCESSED将它们保存到内部数据库中。
  2. 第2步:使用标记为NOT_PROCESSED的已保存实体进行循环,处理它们并将更新的实体写回(不更新标记)
  3. 处理完所有这些文件后,所有文件的标记都会更新为PROCESSED。即全部或全部更新。

    第1步还可以,工作非常顺利。

    步骤2基本上是JpaPagingItemReader,pageSize = 4,处理器集(主要是http调用)和JpaItemWriter,commit-interval = 1。 (我知道建议将pageSize等于commit-interval,它就是我所拥有的)它也是一个多线程的步骤,有10个线程完成这项工作。

    在第2步说我有两种疑问:

    1. 阅读:select * from ENTITY where processed=false order by id嵌套在两个查询中,用于分页select ... from (select .. where rownum < M) where rownum >= N

    2. 写:update ENTITY set .. where id = ID

    3. 出于某种原因,当我有足够的实体时,我会臭名昭着:

        

      Ora-01555,快照太旧:回滚段名为&#34;&#34;   太小了

      我不知道该错误的确切原因(撤消统计数据没有显示任何不良内容,因此希望DBA很快就能找到罪魁祸首),但与此同时我认为读取查询的内容非常糟糕坏。无论如何,这样的分页查询对于数据库来说很难,但我想当你阅读并同时更新你读到的条目时,它可能会导致这种错误。

      我想改变第2步中采用的方法,而不是在页面中阅读。我想将所有的id读入内存只有一次(即给我我需要处理的所有实体的id),然后给每个线程提供该列表中的id。链中的第一个处理器将通过JPA通过id获取实体。这样我就会一个接一个地更新和编写实体,同时我只读了一次我需要的ID。

      我的问题是我无法找到这种读卡器的开箱即用解决方案。有什么我可以用的吗?

1 个答案:

答案 0 :(得分:0)

嗯,我自己实施了解决方案,它基于thisthis。事实上我没有直接使用它们,但我的实现非常接近。

基本上,这就是它的外观(我没有代码,所以使用我的记忆)

        SELECT ca.item_id
            ,ca.FIELD_ID
            ,ca.attr_val
            ,ca.upd_dtt
            ,ca.upd_usr
            ,mf.[ITEM_NAME]
        FROM contract_attr ca
        left JOIN mfr mf on ca.attr_val = mf.[ITEM_PK]

我的存储库正在使用JPA,因此它使用public class MyUnprocessedIdReader extends AbstractItemCountingItemStreamItemReader<Long> { private final Object lock = new Object(); private initialized = false; private final MyObjectsRepository repo; private List<Long> ids; private int current = -1; public MyUnprocessedIdReader(MyObjectsRepository repo) { this.repo = repo; } public void doOpen() { synchronized(lock) { Assert.state(!initialized, "Cannot open an already opened ItemReader, call close first"); this.initialized = true; this.ids = ImmutableList.copyOf(repo.findAllUnprocessed()); } } public Long doRead() { synchronized(lock) { if (ids == null || !initialized) { throw new IllegalStateException("Have you opened the reader?"); } ++current; if (current < ids.size()) { return ids.get(current); } else { return null; } } } public void doClose() { synchronized(lock) { this.initialized = false; this.current = -1; this.ids = null; } } }

之类的东西

此外,我还在链中添加了一个处理器:

entityManager.createQuery("select obj.id from Objects where obj.processed = false order by obj.id asc", Long.class).executeSelect()

有人可能会说它的可扩展性低于使用游标,而且读取方法也存在争议,但是这是一个非常简单的解决方案,可以很好地完成其工作,直到未处理的ID数量太大。处理线程也花费大量时间来调用外部REST服务,因此读取时的争用不会成为瓶颈。

P.S。稍后我会更新是否用ORA-01555解决了这个问题。