Ich habe eine Spring 4-App, in der ich versuche, eine Instanz einer Entität aus meiner Datenbank zu löschen. Ich habe die folgende Entität:
@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
}
Ich habe eine JpaRepository-Schnittstelle definiert:
public interface TokenRepository extends JpaRepository<Token, Long> {
Token findByValue(@Param("value") String value);
}
Ich habe ein Unit-Test-Setup, das mit einer In-Memory-Datenbank (H2) funktioniert, und fülle die Datenbank mit zwei Token vor:
@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));
}
Die erste Behauptung ist erfolgreich, die zweite fehlgeschlagen. Ich habe einen anderen Test versucht, der den Token-Wert ändert und in der Datenbank speichert, und er funktioniert tatsächlich. Daher bin ich mir nicht sicher, warum das Löschen nicht funktioniert. Es werden auch keine Ausnahmen ausgelöst, sondern nur nicht in der Datenbank gespeichert. Es funktioniert auch nicht gegen meine Oracle-Datenbank.
Bearbeiten
Ich habe immer noch dieses Problem. Ich konnte das Löschen in der Datenbank beibehalten, indem ich dies zu meiner TokenRepository-Oberfläche hinzufügte:
@Modifying
@Query("delete from Token t where t.id = ?1")
void delete(Long entityId);
Dies ist jedoch keine ideale Lösung. Irgendwelche Ideen, was ich tun muss, damit es ohne diese zusätzliche Methode funktioniert?
quelle
Antworten:
Höchstwahrscheinlich tritt ein solches Verhalten auf, wenn Sie eine bidirektionale Beziehung haben und nicht beide Seiten synchronisieren, während sowohl Eltern als auch Kind bestehen bleiben (an die aktuelle Sitzung angehängt).
Das ist schwierig und ich werde dies anhand des folgenden Beispiels erklären.
@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; } }
Schreiben wir einen Test (übrigens einen Transaktions-Test)
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 } }
Ziemlich einfacher Test, oder? Wir erstellen Eltern und Kinder, speichern sie in der Datenbank, holen dann ein Kind aus der Datenbank, entfernen es und stellen schließlich sicher, dass alles wie erwartet funktioniert. Und das ist es nicht.
Das Löschen hier hat nicht funktioniert, weil wir den anderen Teil der Beziehung, der in der aktuellen Sitzung bestehen bleibt, nicht synchronisiert haben. Wenn Eltern nicht mit der aktuellen Sitzung verbunden wären, würde unser Test bestehen, dh
@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); } }
und
@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! }
Natürlich beweist es nur meinen Standpunkt und erklärt das Verhalten, mit dem OP konfrontiert ist. Der richtige Weg ist offensichtlich, beide Teile der Beziehung synchron zu halten, was bedeutet:
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; } }
Offensichtlich
@PreRemove
könnte hier verwendet werden.quelle
Ich hatte das gleiche Problem
Möglicherweise verfügt Ihre UserAccount-Entität über ein @ OneToMany mit Cascade für ein Attribut.
Ich habe gerade die Kaskade entfernt, als sie beim Löschen bestehen bleiben könnte ...
quelle
Sie müssen die PreRemove-Funktion in der Klasse hinzufügen, in der Sie viele Objekte als Attribut haben, z. B. in der Bildungsklasse, die eine Beziehung zu UserProfile Education.java haben
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); } }
quelle
Eine Möglichkeit besteht darin,
cascade = CascadeType.ALL
Folgendes in Ihrem userAccount-Dienst zu verwenden:@OneToMany(cascade = CascadeType.ALL) private List<Token> tokens;
Dann machen Sie etwas wie das Folgende (oder eine ähnliche Logik)
@Transactional public void deleteUserToken(Token token){ userAccount.getTokens().remove(token); }
Beachten Sie die
@Transactional
Anmerkung. Auf diese Weise kann Spring (Ruhezustand) feststellen, ob Sie entweder fortfahren, zusammenführen oder was auch immer Sie in der Methode tun möchten. AFAIK Das obige Beispiel sollte so funktionieren, als ob Sie keinen CascadeType festgelegt hätten, und aufrufenJPARepository.delete(token)
.quelle
Dies ist für alle, die von Google erfahren, warum ihre Löschmethode in Spring Boot / Hibernate nicht funktioniert , unabhängig davon, ob sie aus dem JpaRepository / CrudRepository
delete
oder aus einem benutzerdefinierten Repository-Aufruf verwendet wirdsession.delete(entity)
oderentityManager.remove(entity)
.Ich habe ein Upgrade von Spring Boot 1.5 auf Version 2.2.6 (und Hibernate 5.4.13) durchgeführt und eine benutzerdefinierte Konfiguration für Folgendes verwendet
transactionManager
:@Bean public HibernateTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { return new HibernateTransactionManager(entityManagerFactory.unwrap(SessionFactory.class)); }
Und ich habe es geschafft, es zu lösen, indem ich die obige
@EnableTransactionManagement
benutzerdefiniertetransactionManager
Bean-Definition verwendet und gelöscht habe .Wenn Sie immer noch eine Art benutzerdefinierten Transaktionsmanager verwenden müssen, kann das Ändern der Bean-Definition in den folgenden Code auch funktionieren:
@Bean public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) { return new JpaTransactionManager(entityManagerFactory); }
Denken Sie abschließend daran, die automatische Konfiguration von Spring Boot zu aktivieren, damit die
entityManagerFactory
Bean automatisch erstellt werden kann, und entfernensessionFactory
Sie auch alle Beans, wenn Sie ein Upgrade durchführenentityManager
(andernfalls führt Spring Boot die automatische Konfiguration nicht ordnungsgemäß durch). Stellen Sie schließlich sicher, dass Ihre Methoden funktionieren,@Transactional
wenn Sie Transaktionen nicht manuell bearbeiten.quelle
Ich habe das auch gerade durchgemacht. In meinem Fall musste ich dafür sorgen, dass die untergeordnete Tabelle ein nullbares Fremdschlüsselfeld hat, und dann das übergeordnete Element aus der Beziehung entfernen, indem ich null setzte und dann save and delete und flush aufrief.
Ich habe vorher kein Löschen im Protokoll oder eine Ausnahme gesehen.
quelle
Wenn Sie eine neuere Version von Spring Data verwenden, können Sie die deleteBy-Syntax verwenden, damit Sie eine Ihrer Anmerkungen entfernen können: P.
Das nächste ist, dass das Verhalten bereits durch ein Jira-Ticket erfasst wird: https://jira.spring.io/browse/DATAJPA-727
quelle
Ihr Anfangswert für ID ist 500. Das bedeutet, dass Ihre ID mit 500 beginnt
@SequenceGenerator(name = "seqToken", sequenceName = "SEQ_TOKEN", initialValue = 500, allocationSize = 1)
Und Sie wählen hier einen Artikel mit der ID 1 aus
Token deleted = tokenRepository.findOne(1L);
Überprüfen Sie also Ihre Datenbank, um dies zu klären
quelle
Ich habe das gleiche Problem, Test ist in Ordnung, aber in der Datenbankzeile wird nicht gelöscht.
Haben Sie der Methode die Annotation @Transactional hinzugefügt? Für mich funktioniert diese Änderung
quelle
@Transactional int deleteAuthorByName(String name);
Sie sollten @Transactional in Repository schreiben, um JpaRepository zu erweitern
quelle
In meinem Fall war CASCADE.PERSIST, ich habe für CASCADE.ALL geändert und die Änderung durch die Kaskade vorgenommen (Ändern des Vaterobjekts).
quelle
CascadeType.PERSIST und orphanRemoval = true funktionieren nicht zusammen.
quelle