删除不使用 JpaRepository编辑

我有一个 Spring 4 应用,我正在尝试从数据库中删除实体的实例。我有以下实体:

@Entity
public class Token implements Serializable {

    @Id
    @SequenceGenerator(name = "seqToken", sequenceName = "SEQ_TOKEN", initialValue = 500, allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seqToken")
    @Column(name = "TOKEN_ID", nullable = false, precision = 19, scale = 0)
    private Long id;

    @NotNull
    @Column(name = "VALUE", unique = true)
    private String value;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "USER_ACCOUNT_ID", nullable = false)
    private UserAccount userAccount;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "EXPIRES", length = 11)
    private Date expires;

    ...
    // getters and setters omitted to keep it simple
}

我定义了一个JpaRepository接口:

public interface TokenRepository extends JpaRepository<Token, Long> {

    Token findByValue(@Param("value") String value);

}

我有一个与内存数据库(H2)一起使用的单元测试设置,并且我用两个令牌预先填充数据库:

@Test
public void testDeleteToken() {
    assertThat(tokenRepository.findAll().size(), is(2));
    Token deleted = tokenRepository.findOne(1L);
    tokenRepository.delete(deleted);
    tokenRepository.flush();
    assertThat(tokenRepository.findAll().size(), is(1));
}

第一个断言通过,第二个断言失败。我尝试了另一个测试,更改令牌值并将其保存到数据库中,它确实有效,所以我不确定为什么删除不起作用。它也不会引发任何异常,只是不会将其保存到数据库中。它对我的预言机数据库也不起作用。


编辑

仍然有这个问题。我能够通过将其添加到我的TokenRepository接口来使删除内容持久保存到数据库中:

@Modifying
@Query("delete from Token t where t.id = ?1")
void delete(Long entityId);

然而,这不是一个理想的解决方案。关于我需要做些什么才能让它在没有这种额外方法的情况下工作的任何想法?


答案 1

最有可能的是,当您具有双向关系并且未同步双方同时保留父会话和子会话(附加到当前会话)时,可能会发生此类行为。

这很棘手,我将通过以下示例对此进行解释。

@Entity
public class Parent {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Long id;

    @OneToMany(cascade = CascadeType.PERSIST, mappedBy = "parent")
    private Set<Child> children = new HashSet<>(0);

    public void setChildren(Set<Child> children) {
        this.children = children;
        this.children.forEach(child -> child.setParent(this));
    }
}
@Entity
public class Child {
    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "parent_id")
    private Parent parent;

    public void setParent(Parent parent) {
        this.parent = parent;
    }
}

让我们编写一个测试(顺便说一句,这是一个事务性测试)

public class ParentTest extends IntegrationTestSpec {

    @Autowired
    private ParentRepository parentRepository;

    @Autowired
    private ChildRepository childRepository;

    @Autowired
    private ParentFixture parentFixture;

    @Test
    public void test() {
        Parent parent = new Parent();
        Child child = new Child();

        parent.setChildren(Set.of(child));
        parentRepository.save(parent);

        Child fetchedChild = childRepository.findAll().get(0);
        childRepository.delete(fetchedChild);

        assertEquals(1, parentRepository.count());
        assertEquals(0, childRepository.count()); // FAILS!!! childRepostitory.counts() returns 1
    }
}

非常简单的测试吧?我们正在创建父项和子项,将其保存到数据库,然后从数据库中提取子项,将其删除,最后确保一切按预期工作。事实并非如此。

此处的删除不起作用,因为我们没有同步关系的另一部分,该部分在当前会话中持久存在。如果家长没有与当前会话相关联,我们的测试将通过,即

@Component
public class ParentFixture {
    ...
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void thereIsParentWithChildren() {
        Parent parent = new Parent();
        Child child = new Child();
        parent.setChildren(Set.of(child));

        parentRepository.save(parent);
    }
} 

@Test
public void test() {
    parentFixture.thereIsParentWithChildren(); // we're saving Child and Parent in seperate transaction

    Child fetchedChild = childRepository.findAll().get(0);
    childRepository.delete(fetchedChild);

    assertEquals(1, parentRepository.count());
    assertEquals(0, childRepository.count()); // WORKS!
}

当然,这只能证明我的观点,并解释了OP面临的行为。正确的方法显然是保持关系的两个部分同步,这意味着:

class Parent {
    ...
     public void dismissChild(Child child) {
         this.children.remove(child);
     }

     public void dismissChildren() {
        this.children.forEach(child -> child.dismissParent()); // SYNCHRONIZING THE OTHER SIDE OF RELATIONSHIP 
        this.children.clear();
     }

}

class Child {
    ...
    public void dismissParent() {
        this.parent.dismissChild(this); //SYNCHRONIZING THE OTHER SIDE OF RELATIONSHIP
        this.parent = null;
    }
}

显然可以在这里使用。@PreRemove


答案 2

我有同样的问题

也许您的 UserAccount 实体在某些属性上具有 Cascade 的@OneToMany。

我刚刚删除了级联,而不是在删除时它可以保留...


推荐