webentwicklung-frage-antwort-db.com.de

Löschen funktioniert nicht mit JpaRepository

Ich habe eine Spring 4-App, bei 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 einen Gerätetest-Setup, der mit einer In-Memory-Datenbank (H2) arbeitet, 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 bestanden, die zweite versagt. Ich habe einen anderen Test ausprobiert, der den Tokenwert ändert und in der Datenbank speichert. Er funktioniert tatsächlich. Ich bin mir nicht sicher, warum das Löschen nicht funktioniert. Es gibt auch keine Ausnahmen, es bleibt nur in der Datenbank erhalten. Es funktioniert auch nicht mit meiner Oracle-Datenbank.


Bearbeiten

Ich habe immer noch dieses Problem. Ich konnte das Löschen in der Datenbank beibehalten, indem ich dies 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?

27
Twisty McGee

Ich hatte das gleiche Problem

Möglicherweise verfügt Ihre UserAccount-Entität über ein Attribut @OneToMany mit Cascade.

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

19
Davi Arimateia

Sie müssen die PreRemove-Funktion in der Klasse hinzufügen, in der Sie viele Objekte als Attribut haben, z. B. in der Education-Klasse, die eine Beziehung zu UserProfileEducation.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);
    }
}
6
Taimur

Dieses Verhalten tritt höchstwahrscheinlich auf, wenn Sie eine bidirektionale Beziehung haben und Sie nicht beide Seiten synchronisieren, WENN sowohl das übergeordnete als auch das untergeordnete Element bestehen bleiben (an die aktuelle Sitzung angehängt). 

Das ist schwierig und ich werde es 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 (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, richtig? Wir erstellen Eltern und Kinder, speichern sie in einer Datenbank, holen dann ein Kind aus der Datenbank ab, entfernen es und stellen schließlich sicher, dass alles wie erwartet funktioniert. Und es ist nicht so.

Das Löschen hier hat nicht funktioniert, weil wir den anderen Teil der Beziehung nicht synchronisiert haben, der in der aktuellen Sitzung PERSISTIERT ist. Wenn Parent nicht mit der aktuellen Sitzung verbunden ist, würde unser Test bestanden, 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);
 }
} 

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 von OP. Der richtige Weg ist natürlich, beide Teile der Beziehung zu synchronisieren, 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;
     }
 }

Natürlich könnte hier @PreRemove verwendet werden.

4
pzeszko

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

Ich habe vorher keine Löschung im Protokoll oder keine Ausnahme gesehen. 

3
Lucas Holt

Wenn Sie eine neuere Version von Spring Data verwenden, können Sie die deleteBy-Syntax ... verwenden, um eine Ihrer Anmerkungen zu entfernen: P

das nächste ist, dass das Verhalten bereits durch ein Jira-Ticket erfolgt: https://jira.spring.io/browse/DATAJPA-727

2
Eruvanos

Ihr Anfangswert für id ist 500. Das heißt, Ihre ID beginnt mit 500

@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 Ihre Datenbank, um dies zu klären

1
Bitman

Ich habe das gleiche Problem, Test ist in Ordnung, aber auf DB-Zeile wird nicht gelöscht.

haben Sie die @ Transactional-Annotation zur Methode hinzugefügt? für mich funktioniert diese änderung

0

In meinem Fall war das CASCADE.PERSIST, ich änderte für CASCADE.ALL und nahm die Änderung durch die Kaskade vor (das Vaterobjekt ändernd).

0
Sham Fiorin

Eine Möglichkeit ist, cascade = CascadeType.ALL so in Ihrem userAccount-Service zu verwenden:

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

Dann machen Sie etwas wie die folgende (oder eine ähnliche Logik)

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

Beachten Sie die Annotation @Transactional. Dadurch kann Spring (Hibernate) wissen, ob Sie entweder persistieren, zusammenführen möchten oder was auch immer Sie in der Methode tun. AFAIK das obige Beispiel sollte funktionieren, als hätten Sie keinen CascadeType-Satz, und rufen Sie JPARepository.delete(token) auf.

0
venge

CascadeType.PERSIST und orphanRemoval = true funktionieren nicht zusammen.

0
Dejan