Löschen funktioniert nicht mit JpaRepository

Lesezeit: 8 Minuten

Benutzer-Avatar
Twisty McGee

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 ich fülle die Datenbank vorab mit zwei Token aus:

@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 geht, die zweite schlägt fehl. Ich habe einen anderen Test ausprobiert, der den Tokenwert ändert und in der Datenbank speichert, und er funktioniert tatsächlich, daher bin ich mir nicht sicher, warum das Löschen nicht funktioniert. Es löst auch keine Ausnahmen aus, sondern speichert es nur nicht in der Datenbank. Es funktioniert auch nicht gegen meine Oracle-Datenbank.


Bearbeiten

Habe immer noch dieses Problem. Ich konnte den Löschvorgang in der Datenbank beibehalten, indem ich dies zu meiner TokenRepository-Schnittstelle 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?

  • hast du dafür schon mal eine lösung gefunden? gleiches Problem auch in der allerneuesten Frühjahrsversion. laut mysql-protokolltabelle wird niemals eine löschanweisung ausgegeben, keine fehlermeldung verfügbar.

    – phil294

    7. August 2018 um 13:50 Uhr

  • Unglücklicherweise nicht. Ich musste es hier nicht mehr tun und die anderen Orte, an denen ich es tun musste, schienen gut zu funktionieren. Ich weiß nicht, was an diesem Fall so besonders war.

    – Twisty McGee

    7. August 2018 um 17:04 Uhr

  • Sie sollten in Betracht ziehen, diese @Modifying-Löschanweisung als Antwort zu posten. Nur so konnte ich es auch lösen. Ich habe die Frage positiv bewertet, aber ich würde auch diese Antwort positiv bewerten.

    – Taugenichts

    15. Juli 2019 um 21:27 Uhr

  • Das Überschreiben der Löschmethode(n) hat es auch für mich gelöst, nicht sicher, warum dies passiert ist (Upgrade von Spring Boot 2.1.5 auf 2.1.6)

    – Brunaldo

    23. Juni 2020 um 15:53 ​​Uhr

  • Dasselbe Problem hier bei einer einfachsten Entität, die keine Beziehungen verwendet. speichern und dann löschen in einer Transaktion, z. B. funktioniert die Testmethode nicht mehr mit Spring 2.1.6. Wir verwenden jetzt Spring 2.4.1, stufen spring-data jedoch auf 2.1.8.RELEASE herunter, um dies zu umgehen. Hibernate bleibt unverändert 5.4.25.

    – JRA_TLL

    7. Juni 2021 um 8:39 Uhr


Benutzer-Avatar
pzeszko

Höchstwahrscheinlich tritt ein solches Verhalten auf, wenn Sie eine bidirektionale Beziehung haben und nicht beide Seiten synchronisieren, WÄHREND Elternteil und Kind bestehen bleiben (an die aktuelle Sitzung angehängt).

Das ist knifflig und ich werde dies mit dem folgenden Beispiel 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 transaktionalen)

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 Kind, speichern es 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 nicht synchronisiert haben, nämlich PERSISTED IN CURRENT SESSION. Wenn Parent nicht mit der aktuellen Sitzung verbunden wäre, 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 war. 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.

  • Wirklich gute Arbeit an der Erklärung hier. Ich hatte das gleiche Problem und die gleiche scheinbare “Problemumgehung” – Löschen durch eine benutzerdefinierte Abfrage, aber ich wollte es nicht verwenden, da es offensichtlich ein Hack war. Der Hook @PreRemove hat hier wirklich Wunder bewirkt. Vielen Dank!

    – Ognjen Mišić

    31. Oktober 2019 um 10:17 Uhr

  • Froh, dass ich helfen konnte!

    – pzeszko

    31. Oktober 2019 um 15:29 Uhr

  • Ich hatte ein ähnliches Problem mit der @OneToOne-Beziehung und Ihre Erklärung hat mir geholfen, es zu lösen. Sehr schön! Vielen Dank.

    – Fahrersti

    15. März 2021 um 12:05 Uhr

  • etwas spät dazu, aber was meinst du in diesem Zusammenhang mit synchronisieren?

    – Kelsanität

    30. August 2021 um 13:54 Uhr

Ich hatte das gleiche Problem

Vielleicht hat Ihre UserAccount-Entität ein @OneToMany mit Cascade für ein Attribut.

Ich habe gerade die Kaskade entfernt, als könnte sie beim Löschen bestehen bleiben …

  • Das hat auch bei mir funktioniert. Insbesondere habe ich herumgespielt und konnte diese Kaskadentypen beibehalten: [CascadeType.PERSIST,CascadeType.REFRESH] und trotzdem das Löschen durch das untergeordnete Repository arbeiten lassen. Was für mich keinen Sinn machte, war, dass ich die Eigentümerentität über “mappedBy” auf dem Elternteil zum Kind gemacht habe

    – GameSalutes

    5. April 2016 um 22:36 Uhr

  • Diese Lösung scheint zu funktionieren, gibt aber keine Erklärung.

    – Ivan Matavulj

    8. Mai 2017 um 15:51 Uhr


Benutzer-Avatar
Taimur

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

Eine Möglichkeit ist die Verwendung cascade = CascadeType.ALL wie folgt in Ihrem userAccount-Dienst:

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

Dann tun Sie so etwas wie das Folgende (oder eine ähnliche Logik)

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

Beachten Sie die @Transactional Anmerkung. Dadurch kann Spring (Hibernate) wissen, ob Sie entweder bestehen bleiben, zusammenführen oder was auch immer Sie in der Methode tun. AFAIK das obige Beispiel sollte arbeiten, als ob Sie keinen CascadeType gesetzt hätten, und rufen Sie auf JPARepository.delete(token).

Dies ist für alle, die von Google kommen und wissen, warum ihre Löschmethode nicht funktioniert Spring Boot/Ruhezustandob es aus dem JpaRepository/CrudRepository verwendet wird delete oder aus einem benutzerdefinierten Repository-Aufruf session.delete(entity) oder entityManager.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 verwendet transactionManageretwas wie das:

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

Und ich habe es geschafft, es mit zu lösen @EnableTransactionManagement und Löschen der Gewohnheit
transactionManager Bohnendefinition oben.

Wenn Sie immer noch eine Art benutzerdefinierten Transaktionsmanager verwenden müssen, kann es auch funktionieren, die Bean-Definition in den folgenden Code zu ändern:

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

Als letzte Anmerkung denken Sie daran, die automatische Konfiguration von Spring Boot zu aktivieren entityManagerFactory Bean kann automatisch erstellt werden, und auch alle entfernen sessionFactory Bean, wenn Sie auf upgraden entityManager (Andernfalls führt Spring Boot die automatische Konfiguration nicht richtig durch). Und schließlich stellen Sie sicher, dass Ihre Methoden sind @Transactional wenn Sie Transaktionen nicht manuell bearbeiten.

Benutzer-Avatar
Lukas Holt

Ich habe das auch gerade durchgemacht. In meinem Fall musste ich die untergeordnete Tabelle mit einem nullbaren Fremdschlüsselfeld versehen und dann das übergeordnete Element aus der Beziehung entfernen, indem ich null setzte und dann save und delete und flush aufrief.

Ich habe vorher kein Löschen im Protokoll oder eine Ausnahme gesehen.

Benutzer-Avatar
Eruvanos

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 von einem Jira-Ticket erfasst wird:
https://jira.spring.io/browse/DATAJPA-727

1283360cookie-checkLöschen funktioniert nicht mit JpaRepository

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy