如果在没有刷新的情况下发生添加和删除,orphanremoval 不会删除

orphanremoval doesn't delete if add and remove occur without flush

我只是想了解 Spring Jpa/Hibernate 的某些部分是如何工作的。正如标题所说,只有在从 collection 添加和删除 child 实体之间将实体刷新到数据库时,orphanRemoval 似乎才有效,我想知道为什么。

我有一个 parent class 与 @OneToMany 关联 child class

@Entity
class Parent {
    
    @Id
    @GeneratedValue
    @Getter
    private Long id;
    
    @OneToMany(mappedBy = parent, cascade=ALL, orphanRemoval=true)
    @Getter
    private Set<Child> children;
    
    public Parent(){
        children = new HashSet<Child>();
    }

    public Child addChild(Child child){
        child.setParent(this);
        children.add(child);
        return child;
    }

    public void removeChild(Child child){
        child.setParent(null);
        children.remove(child);
    }
}

@Entity
class Child {

    @Id
    @GeneratedValue
    @Getter
    private Long id;

    @ManyToOne
    @Setter
    private Parent parent;
}
        

我正在测试让 child 像这样从 parent 中删除(使用 @Autowired JPARepository 和 @JpaTest 注释)

@ExtendWith(SpringExtension.class)
@DataJpaTest
class PersistTest {
    
    @Autowired ParentRepository repo; // JpaRepository<Parent, Long>
    @Autowired EntityManager em;

    @Test
    public void whenChildRemoved_thenChildDeleted(){
        Parent parent = new Parent();
        Child child = parent.addChild(new Child());
        repo.save(parent);

        em.flush(); // test fails if removed

        parent.removeChild(child);
        repo.saveAndFlush(parent);
        
        assertThat(repo.findById(parent.getId()).get().getChildren()).isEmpty();
        assertThat(em.find(Child.class, child.getId()).isNull();
    }
}

如果实体管理器在将 child 添加到 parent 和删除它之间没有刷新,那么两个断言都会失败,并且在查看生成的 sql 时没有 DELETE 语句制作。如果 EM 被刷新,则进行 DELETE 并且测试通过。

基本上只是想知道是否有人可以解释为什么会这样,以及将这两个操作放在单独的 @Transactional 方法中是否会产生相同的效果。

您期望完成或创建一些交易,但实际上并没有。这就是您看到此行为的原因。

  1. @DataJpaTest 在每个方法上放置单独的事务(顺便说一句,默认情况下无论如何都会回滚);
  2. 这就是您可以使用 JpaRepository 的原因——它本身不创建交易(与 CrudRepository 相反),但有底层交易;
  3. 如果JpaRepository使用了@Transactional(REQUIRED_NEW),您可能会移除flush;

回答你最后一个问题。如果将这些操作放在单独的 @Transactional 方法中,它将以完全相同的方式工作,因为 @DataJpaTest 创建的测试中存在底层事务——hibernate 通常在最后刷新方法。你必须使用 @Transactional(REQUIRED_NEW).