多线程jpa读取时出现Hibernate Null指针异常



我有一个spring-batch作业,它使用RepositoryItemReader从数据库中读取,然后将结果转换为Map,然后写入以弹性搜索结果。虽然有点慢,但效果很好。所以现在我想添加一个池大小为4的taskExecutor来加快速度:

return stepBuilders.get("search-export").<AbstractEntityDefinition, Map<String, Object>>chunk(nemesisSearchProperties.getExport().getChunkSize())
.reader(reader)
.processor(processor)
.writer(writer)
.stream(reader)
.transactionAttribute(transactionAttribute)
.listener(chunkSessionReplicatorExecutionListener)
.listener(new NemesisChunkLoggingListener(indexName + " search export", nemesisSearchProperties.getExport().getChunkSize()))
.taskExecutor(searchExportTaskExecutor).throttleLimit(4) // <-- I add this
.build();

然而,当我添加任务执行器时,我得到的是:

java.lang.NullPointerException
at org.hibernate.internal.util.collections.IdentityMap.entryArray(IdentityMap.java:162)
at org.hibernate.internal.util.collections.IdentityMap.concurrentEntries(IdentityMap.java:58)
at org.hibernate.engine.internal.StatefulPersistenceContext.forEachCollectionEntry(StatefulPersistenceContext.java:1135)
at org.hibernate.event.internal.AbstractFlushingEventListener.prepareCollectionFlushes(AbstractFlushingEventListener.java:193)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:85)

看看IdentityMap,我可以看到:

(1)     if ( entryArray == null ) {
(2)         entryArray = new Map.Entry[ map.size() ];
final Iterator<Entry<IdentityKey<K>, V>> itr = map.entrySet().iterator();
int i = 0;
while ( itr.hasNext() ) {
final Entry<IdentityKey<K>, V> me = itr.next();
(3)             entryArray[i++] = new IdentityMapEntry( me.getKey().key, me.getValue() ); // Here entryArray is NULL!!!
}
}

异常发生在第(3(行,其中entryArray为null。我想知道怎么可能,因为entryArray在第(1(行被检查为null,在第(2(行被初始化。

任何想法都将不胜感激。

我不知道您的读取器、处理器和编写器是什么样子的,但由于在线程之间共享Session,这看起来像是一个并发问题。确保每个线程都有一个单独的会话。

请参阅文档:https://docs.spring.io/spring-batch/docs/current/reference/html/scalability.html

对于一些常见的批处理用例,使用多线程步骤实现存在一些实际限制。步骤中的许多参与者(如读者和作者(都是有状态的。如果状态未按线程隔离,则这些组件在多线程步骤中不可用。特别是,SpringBatch中的大多数现成的读写器都不是为多线程使用而设计的。

最好为所有对象创建一个包装器,将它们各自的读写器等存储在本地线程中。

Java/Jakarta Batch做到了这一点。读卡器、写入器和处理器实例总是每个分区,所以您永远不会遇到问题。

最新更新