删除不使用 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));
}

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


剪辑

还是有这个问题。我能够通过在 TokenRepository 接口中添加这个来获得对数据库的持久删除:

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

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

131826 次浏览

Your initial value for id is 500. That means your id starts with 500

@SequenceGenerator(name = "seqToken", sequenceName = "SEQ_TOKEN",
initialValue = 500, allocationSize = 1)

And you select one item with id 1 here

 Token deleted = tokenRepository.findOne(1L);

So check your database to clarify that

I had the same problem

Perhaps your UserAccount entity has an @OneToMany with Cascade on some attribute.

I've just remove the cascade, than it could persist when deleting...

I just went through this too. In my case, I had to make the child table have a nullable foreign key field and then remove the parent from the relationship by setting null, then calling save and delete and flush.

I didn't see a delete in the log or any exception prior to doing this.

If you use an newer version of Spring Data, you could use deleteBy syntax...so you are able to remove one of your annotations :P

the next thing is, that the behaviour is already tract by a Jira ticket: https://jira.spring.io/browse/DATAJPA-727

You need to add PreRemove function ,in the class where you have many object as attribute e.g in Education Class which have relation with UserProfile Education.java

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);
}
}

Most probably such behaviour occurs when you have bidirectional relationship and you're not synchronizing both sides WHILE having both parent and child persisted (attached to the current session).

This is tricky and I'm gonna explain this with the following example.

@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;
}
}

Let's write a test (a transactional one btw)

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
}
}

Pretty simple test right? We're creating parent and child, save it to database, then fetching a child from database, removing it and at last making sure everything works just as expected. And it's not.

The delete here didn't work because we didn't synchronized the other part of relationship which is PERSISTED IN CURRENT SESSION. If Parent wasn't associated with current session our test would pass, i.e.

@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);
}
}

and

@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!
}

Of course it only proves my point and explains the behaviour OP faced. The proper way to go is obviously keeping in sync both parts of relationship which means:

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;
}
}

Obviously @PreRemove could be used here.

One way is to use cascade = CascadeType.ALL like this in your userAccount service:

@OneToMany(cascade = CascadeType.ALL)
private List<Token> tokens;

Then do something like the following (or similar logic)

@Transactional
public void deleteUserToken(Token token){
userAccount.getTokens().remove(token);
}

Notice the @Transactional annotation. This will allow Spring (Hibernate) to know if you want to either persist, merge, or whatever it is you are doing in the method. AFAIK the example above should work as if you had no CascadeType set, and call JPARepository.delete(token).

I've the same problem, test is ok but on db row isn't deleted.

have you added the @Transactional annotation to method? for me this change makes it work

In my case was the CASCADE.PERSIST, i changed for CASCADE.ALL, and made the change through the cascade (changing the father object).

CascadeType.PERSIST and orphanRemoval=true doesn't work together.

This is for anyone coming from Google on why their delete method is not working in Spring Boot/Hibernate, whether it's used from the JpaRepository/CrudRepository's delete or from a custom repository calling session.delete(entity) or entityManager.remove(entity).

I was upgrading from Spring Boot 1.5 to version 2.2.6 (and Hibernate 5.4.13) and had been using a custom configuration for transactionManager, something like this:

@Bean
public HibernateTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
return new HibernateTransactionManager(entityManagerFactory.unwrap(SessionFactory.class));
}

And I managed to solve it by using @EnableTransactionManagement and deleting the custom transactionManager bean definition above.

If you still have to use a custom transaction manager of sorts, changing the bean definition to the code below may also work:

@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}

As a final note, remember to enable Spring Boot's auto-configuration so the entityManagerFactory bean can be created automatically, and also remove any sessionFactory bean if you're upgrading to entityManager (otherwise Spring Boot won't do the auto-configuration properly). And lastly, ensure that your methods are @Transactional if you're not dealing with transactions manually.

@Transactional
int deleteAuthorByName(String name);

you should write @Transactional in Repository extends JpaRepository

I was facing the similar issue.

Solution 1:

The reason why the records are not being deleted could be that the entities are still attached. So we've to detach them first and then try to delete them.

Here is my code example:

User Entity:

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

Contact Entity:

@Entity
public class Contact {
@Id
private int cId;
    

@ManyToOne
private User user;
}

Delete Code:

user.getContacts().removeIf(c -> c.getcId() == contact.getcId());
this.userRepository.save(user);
this.contactRepository.delete(contact);

Here we are first removing the Contact object (which we want to delete) from the User's contacts ArrayList, and then we are using the delete() method.

Solution 2:

Here we are using the orphanRemoval attribute, which is used to delete orphaned entities from the database. An entity that is no longer attached to its parent is known as an orphaned entity.

Code example:

User Entity:

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

Contact Entity:

@Entity
public class Contact {
@Id
private int cId;
    

@ManyToOne
private User user;
}

Delete Code:

user.getContacts().removeIf(c -> c.getcId() == contact.getcId());
this.userRepository.save(user);

Here, as the Contact entity is no longer attached to its parent, it is an orphaned entity and will be deleted from the database.