Spring Batch中H2变更持久化问题:Reader写入后Processor查询无结果
为什么ItemReader能查到但ItemProcessor查不到?
你遇到的问题核心是事务可见性+JPA缓存机制:
- ItemReader里的
findByName能返回对象,是因为这个查询和save在同一个@Transactional方法内,JPA的一级缓存(EntityManager缓存)已经缓存了刚保存的BatchFile实例,查询直接从缓存取,根本没去数据库查。 - ItemProcessor中的
findByName触发的是数据库查询(JPQL默认会走数据库而非缓存),但此时Spring Batch的步骤大事务还没提交,H2默认的READ_COMMITTED隔离级别看不到未提交的数据库变更,所以查不到。
Spring Batch的事务设计逻辑
默认情况下,Spring Batch的每个Step是一个大事务边界,整个Chunk(默认10条数据)的Reader、Processor、Writer操作都包裹在这个事务里。只有当Chunk处理完成(Writer执行完毕),事务才会提交到数据库。这种设计是为了保证数据原子性:要么整个Chunk的处理全部成功,要么全部回滚,避免部分数据提交后后续步骤失败导致的不一致。
这就是为什么步骤间不会自动刷新会话/提交事务——如果中途刷新,一旦后续Processor/Writer失败,已经提交的变更无法回滚,破坏了事务的一致性。
解决方法
方案1:让保存操作独立提交事务
修改BatchFileService的save方法,强制开启新事务并立即提交,这样变更会立刻写入数据库,Processor就能查到:
@Transactional(propagation = Propagation.REQUIRES_NEW) public long save(BatchFile batchFile) { return batchFileRepository.save(batchFile).getId(); }
注意:这种方式下,即使后续步骤失败,已经保存的NEW状态记录不会回滚,适合需要保留中间状态的场景。
方案2:手动刷新EntityManager到数据库
如果希望变更仍受步骤大事务控制(失败时能回滚),可以在ItemReader保存后手动刷新EntityManager,将缓存同步到数据库:
先在BatchFileService添加flush方法:
@Autowired private EntityManager entityManager; @Transactional public void flush() { entityManager.flush(); }
然后在ItemReader中调用:
batchFileService.save(new BatchFile(fileEntry.getName(), currentBatchName)); batchFileService.flush(); // 手动将缓存变更同步到数据库
此时数据库能看到未提交的变更(同一个事务内允许读取自己的未提交数据),Processor的查询就能命中。
方案3:传递对象避免重复查询
最高效的方式是在ItemReader中创建BatchFile后,直接和FileEntry一起传递给Processor,不用再查数据库:
- 创建复合类封装两个对象:
public class FileProcessingWrapper { private FileEntry fileEntry; private BatchFile batchFile; // 构造器、getter/setter }
- 修改ItemReader返回这个复合类:
@Override public synchronized FileProcessingWrapper read() throws IOException { if (fileIterator.hasNext()) { FileEntry fileEntry = fileIterator.next(); // ... 原有fileEntry处理逻辑 BatchFile batchFile = new BatchFile(fileEntry.getName(), currentBatchName); batchFileService.save(batchFile); return new FileProcessingWrapper(fileEntry, batchFile); } return null; }
- 修改ItemProcessor直接使用传递的
BatchFile:
public FileProcessingWrapper process(FileProcessingWrapper wrapper) { BatchFile batchFile = wrapper.getBatchFile(); batchFile.setStatus("PROCESSING"); batchFileService.save(batchFile); return wrapper; }
这种方式完全绕开了事务可见性问题,还减少了数据库查询,性能更好。
内容的提问来源于stack exchange,提问作者Q_Q_

