如何在SkipListener的onSkipInWrite方法中访问Reader生成的A类对象
如何在SkipListener的onSkipInWrite方法中访问Reader生成的A类对象
嗨,我完全懂你现在的困扰——当写操作失败触发onSkipInWrite时,只能拿到转换后的B对象,但你需要原始的A对象来更新它的状态,又不想用包装类这种看起来不够优雅的实现方式。下面给你几个实用的解决方案,你可以根据自己的场景选择:
方案一:给B类添加临时字段存储A的ID
这是最直接且低侵入的方案:给B类加一个transient修饰的临时字段,用来存储对应A对象的ID。transient关键字能保证这个字段不会被持久化到数据库,完全符合临时存储的需求。
代码示例
首先修改B类:
public class B { // 你的原有字段 private String field1; private Integer field2; // 临时存储A的ID,不会被持久化 private transient Long aId; // getter和setter public Long getAId() { return aId; } public void setAId(Long aId) { this.aId = aId; } }
然后在Processor中把A的ID赋值给B的临时字段:
@Bean public ItemProcessor<A, B> itemProcessor() { return item -> { B b = new B(); // 原有映射逻辑:把A的字段映射到B b.setField1(item.getField1()); b.setField2(item.getField2()); // 存入A的ID b.setAId(item.getId()); return b; }; }
最后在SkipListener的onSkipInWrite方法中,通过这个ID查询到A并更新状态:
@Component @StepScope public class CustomSkipListener extends SkipListenerSupport { private final ARepository aRepository; public CustomSkipListener(ARepository aRepository) { this.aRepository = aRepository; } @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void onSkipInWrite(B item, Throwable t) { A a = aRepository.findById(item.getAId()) .orElseThrow(() -> new RuntimeException("未找到对应的A对象")); a.setStatus("FAILED"); aRepository.save(a); } }
方案二:利用StepExecution的ExecutionContext跟踪Chunk中的A对象
如果不想修改B类,可以通过Step的执行上下文(ExecutionContext)来临时存储当前Chunk中所有A对象的ID映射。需要自定义一个ItemStreamListener来跟踪读取到的A对象。
代码示例
首先定义跟踪A对象的Listener:
@Component @StepScope public class AItemTrackerListener implements ItemStreamListener<A> { private final StepExecution stepExecution; private Map<Long, A> currentChunkAMap = new HashMap<>(); public AItemTrackerListener(StepExecution stepExecution) { this.stepExecution = stepExecution; } @Override public void afterRead(A item) { // 把读取到的A对象存入临时Map currentChunkAMap.put(item.getId(), item); } @Override public void update(ExecutionContext executionContext) { // 将当前Chunk的A映射存入Step执行上下文 stepExecution.getExecutionContext().put("currentChunkAMap", currentChunkAMap); } @Override public void open(ExecutionContext executionContext) { // 每次Chunk开始时清空Map currentChunkAMap.clear(); } // 其他空实现的方法可以省略 }
然后在Step中注册这个Listener:
stepFactory.get("name") .<A, B>chunk(SIZE) .reader(aItemReader) .processor(itemProcessor) .writer(bEntityWriter) .listener(aItemTrackerListener) // 注册跟踪Listener .listener(customSkipListener) // 注册你的SkipListener .faultTolerant() .skip(Throwable.class) .skipLimit(LIMIT) .build();
最后在SkipListener中从上下文取出A对象:
@Component @StepScope public class CustomSkipListener extends SkipListenerSupport { private final StepExecution stepExecution; private final ARepository aRepository; public CustomSkipListener(StepExecution stepExecution, ARepository aRepository) { this.stepExecution = stepExecution; this.aRepository = aRepository; } @Override @Transactional(propagation = Propagation.REQUIRES_NEW) public void onSkipInWrite(B item, Throwable t) { // 假设B类已有关联A的外键字段aId Map<Long, A> chunkAMap = (Map<Long, A>) stepExecution.getExecutionContext().get("currentChunkAMap"); A a = chunkAMap.get(item.getAId()); if (a != null) { a.setStatus("FAILED"); aRepository.save(a); } } }
注意:如果A对象无法序列化,建议只存储A的ID列表而不是整个对象,避免ExecutionContext序列化报错。
额外提示:如果B类已有A的关联字段
如果你的B对象本身就有指向A的外键字段(比如业务上本来就需要关联),那直接用这个字段查询A即可,连临时字段都不用加,这是最省心的情况。
备注:内容来源于stack exchange,提问作者myschu
相关产品推荐
相关产品推荐

