删除不使用JpaRepository

Twi*_*Gee 44 java hibernate jpa spring-data spring-data-jpa

我有一个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
}
Run Code Online (Sandbox Code Playgroud)

我定义了一个JpaRepository接口:

public interface TokenRepository extends JpaRepository<Token, Long> {

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

}
Run Code Online (Sandbox Code Playgroud)

我有一个单元测试设置,与内存数据库(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));
}
Run Code Online (Sandbox Code Playgroud)

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


编辑

还有这个问题.通过将此删除添加到我的TokenRepository接口,我能够将删除保留到数据库:

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

然而,这不是一个理想的解决方案.如果没有这个额外的方法,我需要做些什么来让它工作?

小智 29

我有同样的问题

也许你的UserAccount实体在某些属性上有一个带有Cascade的@OneToMany.

我只是删除了级​​联,而不是删除时可能会持续...

  • 这个解决方案似乎有效,但没有给出任何解释. (9认同)

pze*_*zko 22

当您具有双向关系并且在父级和子级都持续存在的情况下不同步时(附加到当前会话),很可能会发生这种情况。

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

@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;
    }
}
Run Code Online (Sandbox Code Playgroud)

让我们编写一个测试(顺便说一句)

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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

这里的删除无效,因为我们没有同步当前会话中存在的关系的另一部分。如果父级与当前会话没有关联,则我们的测试将通过,即

@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);
    }
} 
Run Code Online (Sandbox Code Playgroud)

@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!
}
Run Code Online (Sandbox Code Playgroud)

当然,这仅证明了我的观点,并解释了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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这里的解释真的很好。我遇到了同样的问题和同样明显的“解决方法” - 通过自定义查询删除,但我不想使用它,因为这显然是一个黑客行为。@PreRemove 钩子在这里确实创造了奇迹。谢谢! (3认同)

Tai*_*mur 9

您需要在具有多个对象作为属性的类中添加PreRemove函数,例如在与ClassProfile Education.java有关系的Education Class中

private Set<UserProfile> userProfiles = new HashSet<UserProfile>(0);

@ManyToMany(fetch = FetchType.EAGER, mappedBy = "educations")
public Set<UserProfile> getUserProfiles() {
    return this.userProfiles;
}

@PreRemove
private void removeEducationFromUsersProfile() {
    for (UsersProfile u : usersProfiles) {
        u.getEducationses().remove(this);
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 7

一种方法是cascade = CascadeType.ALL在您的 userAccount 服务中像这样使用:

@OneToMany(cascade = CascadeType.ALL)
private List<Token> tokens;
Run Code Online (Sandbox Code Playgroud)

然后执行以下操作(或类似的逻辑)

@Transactional
public void deleteUserToken(Token token){
    userAccount.getTokens().remove(token);
}
Run Code Online (Sandbox Code Playgroud)

注意@Transactional注释。这将允许 Spring (Hibernate) 知道您是要持久化、合并还是要在方法中执行任何操作。AFAIK 上面的例子应该像你没有设置 CascadeType 一样工作,并调用JPARepository.delete(token).


Dha*_*dar 6

我面临着类似的问题。

解决方案一:

记录未被删除的原因可能是实体仍然附加。所以我们必须先分离它们,然后尝试删除它们。

这是我的代码示例:

User Entity:

@Entity
public class User {
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
    private List<Contact> contacts = new ArrayList<>();
}
Run Code Online (Sandbox Code Playgroud)

Contact Entity:

@Entity
public class Contact {
    @Id
    private int cId;
    
    @ManyToOne
    private User user;
}
Run Code Online (Sandbox Code Playgroud)

Delete Code:

user.getContacts().removeIf(c -> c.getcId() == contact.getcId());
this.userRepository.save(user);
this.contactRepository.delete(contact);
Run Code Online (Sandbox Code Playgroud)

在这里,我们首先从用户的联系人 ArrayList 中删除 Contact 对象(我们想要删除),然后使用 delete() 方法。

解决方案2:

这里我们使用orphanRemoval属性,该属性用于从数据库中删除孤立实体。不再依附于其父实体的实体称为孤立实体。

代码示例:

User Entity:

@Entity
public class User {
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval = true)
    private List<Contact> contacts = new ArrayList<>();
}
Run Code Online (Sandbox Code Playgroud)

Contact Entity:

@Entity
public class Contact {
    @Id
    private int cId;
    
    @ManyToOne
    private User user;
}
Run Code Online (Sandbox Code Playgroud)

Delete Code:

user.getContacts().removeIf(c -> c.getcId() == contact.getcId());
this.userRepository.save(user);
Run Code Online (Sandbox Code Playgroud)

在这里,由于联系人实体不再附加到其父实体,因此它是一个孤立实体,将从数据库中删除。


Bit*_*man 1

您的 id 初始值为 500。这意味着您的 id 以 500 开头

@SequenceGenerator(name = "seqToken", sequenceName = "SEQ_TOKEN",
initialValue = 500, allocationSize = 1)
Run Code Online (Sandbox Code Playgroud)

然后您在此处选择 id 为 1 的一项

 Token deleted = tokenRepository.findOne(1L);
Run Code Online (Sandbox Code Playgroud)

因此,请检查您的数据库以澄清这一点