首页 > 解决方案 > Spring Batch 在读取之前处理了 JpaPagingItemReader enityManager.flush()

问题描述

我有一个标准的双向父子关系如下:

@Entity
public class Parent {
    @Id
    private Long id;
    private String field1;
    private String field2;
    
    @OneToMany(
        mappedBy = "parent",
        orphanRemoval = true,
        cascade = CascadeType.ALL
    )
    private Set<Child> children = new HashSet<>();
    // remain code omitted
}

@Entity
public class Child {
    @Id
    private Long id;
    
    @ManyToOne(
        fetch = FetchType.LAZY
    )
    private Parent parent;
    // remain code omitted
}

和一个 jpapagegitemreader,它读取具有子实体的父实体,项目处理器更新一些父字段并孤立子实体。

public class ParentItemProcessor implements ItemProcessor<Parent, Parent> {

    @Override
    public Parent process(Parent parent) throws Exception {
        parent.setField1("new val");
        parent.setField2("new val");
        parent.getChildren().clear();
        return parent;
    }
}

这一切正常,我可以看到何时提交块,为父更新和子删除执行 sql。

我遇到的问题是在提交第一个块之后,阅读器在执行读取下一批的查询之前执行以下代码。对“entityManager.flush()”的调用会导致重新执行删除语句,从而导致异常:

引起:org.hibernate.StaleStateException:批量更新从更新[0]返回了意外的行数;实际行数:0;预期:1;执行的语句:从 child 中删除 child_id=?

protected void doReadPage() {

    EntityTransaction tx = null;
    
    if (transacted) {
        tx = entityManager.getTransaction();
        tx.begin();
        
        entityManager.flush();
        entityManager.clear();
    }
// truncated
}

显然,我可以通过在阅读器上将 'transacted' 设置为 false 来绕过此代码(这可行),但我不确定这意味着什么以及 spring 批处理的最佳实践,因为 'transacted' 是默认行为。

我还想了解在读取新项目之前在阅读器中调用 flush 的需要,特别是当阅读器和编写器具有不同的实体管理器时。

谢谢

标签: jpaspring-batch

解决方案


推荐阅读