Supprimer ne fonctionne pas avec JpaRepository
j'ai une application spring 4 où j'essaie de supprimer une instance d'une entité de ma base de données. J'ai l'entité suivante:
@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
}
j'ai une interface JpaRepository définie:
public interface TokenRepository extends JpaRepository<Token, Long> {
Token findByValue(@Param("value") String value);
}
j'ai une installation de test d'unité qui fonctionne avec une base de données en mémoire (H2) et je pré-remplit la base de données avec deux jetons:
@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));
}
la première affirmation passe, la seconde échoue. J'ai essayé un autre test qui change la valeur du jeton et l'enregistre dans la base de données et il ne fonctionne en effet, donc je ne suis pas sûr pourquoi supprimer ne fonctionne pas. Il ne jette pas d'exceptions non plus, mais ne le persiste pas dans la base de données. Ça ne marche pas non plus contre ma base de données oracle.
Modifier
toujours ce problème. J'ai pu obtenir la suppression pour persister dans la base de données en ajoutant ceci à mon interface TokenRepository:
@Modifying
@Query("delete from Token t where t.id = ?1")
void delete(Long entityId);
mais ce n'est pas une solution idéale. Toutes les idées pour ce que je dois faire pour le faire fonctionner sans cette méthode supplémentaire?
6 réponses
j'ai eu le même problème
peut-être que votre entité UserAccount a un @OneToMany avec Cascade sur un attribut.
je viens de supprimer la cascade, qui pourrait persister lors de la suppression...
je suis juste allé à travers ce aussi. Dans mon cas, j'ai dû faire en sorte que la table enfant ait un champ de clé étrangère nul, puis supprimer le parent de la relation en définissant null, puis en appelant save et delete et flush.
Je n'ai pas vu de suppression dans le journal ou AUCUNE exception avant de faire ceci.
vous devez ajouter la fonction PreRemove, dans la classe où vous avez beaucoup d'objet comme attribut E. g en classe D'éducation qui ont une relation avec UserProfile Éducation.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);
}
}
si vous utilisez une nouvelle version des données de printemps, vous pouvez utiliser la syntaxe deleteBy...vous pouvez donc supprimer une de vos annotations: P
la chose suivante est, que le comportement est déjà tractée par un billet Jira: https://jira.spring.io/browse/DATAJPA-727
votre valeur initiale pour id est de 500. Cela signifie que votre carte d'identité commence avec 500
@SequenceGenerator(name = "seqToken", sequenceName = "SEQ_TOKEN",
initialValue = 500, allocationSize = 1)
et vous sélectionnez un élément avec id 1 ici
Token deleted = tokenRepository.findOne(1L);
vérifiez donc votre base de données pour clarifier cela
il est très probable qu'un tel comportement se produit lorsque vous avez une relation bidirectionnelle et que vous ne synchronisez pas les deux côtés tout en ayant à la fois un parent et un enfant qui ont persisté (attachés à la session en cours).
C'est délicat et je vais expliquer cela avec l'exemple suivant.
@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;
}
}
écrivons un test (une transactionnel 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
}
}
Assez simple test à droite? On crée parent et enfant, on le met dans la base de données, puis on récupère un enfant. de la base de données, le supprimer et enfin s'assurer que tout fonctionne comme prévu. Et il n'est pas.
la suppression ici n'a pas fonctionné parce que nous n'avons pas synchronisé l'autre partie de la relation qui persiste dans la session en cours. Si Parent n'était pas associé à la session en cours, notre test serait réussi, c.-à-d.
@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);
}
}
et
@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!
}
bien sûr, cela ne prouve que mon point de vue et explique le comportement OP affronté. La bonne façon d'y aller est évidemment de s'y tenir. synchroniser les deux parties de la relation qui signifie:
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;
}
}