Wie aktualisiere ich eine Entität mit spring-data-jpa?

193

Nun, die Frage sagt so ziemlich alles. Wie aktualisiere ich mit JPARepository eine Entität?

JPARepository hat nur eine Speichermethode , die mir nicht sagt, ob es tatsächlich erstellt oder aktualisiert wird. Zum Beispiel füge ich ein einfaches Objekt in den Datenbankbenutzer ein, das drei Felder enthält : firstname, lastnameund age:

 @Entity
 public class User {

  private String firstname;
  private String lastname;
  //Setters and getters for age omitted, but they are the same as with firstname and lastname.
  private int age;

  @Column
  public String getFirstname() {
    return firstname;
  }
  public void setFirstname(String firstname) {
    this.firstname = firstname;
  }

  @Column
  public String getLastname() {
    return lastname;
  }
  public void setLastname(String lastname) {
    this.lastname = lastname;
  }

  private long userId;

  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  public long getUserId(){
    return this.userId;
  }

  public void setUserId(long userId){
    this.userId = userId;
  }
}

Dann rufe ich einfach auf save(), was an dieser Stelle eigentlich eine Einfügung in die Datenbank ist:

 User user1 = new User();
 user1.setFirstname("john"); user1.setLastname("dew");
 user1.setAge(16);

 userService.saveUser(user1);// This call is actually using the JPARepository: userRepository.save(user);

So weit, ist es gut. Jetzt möchte ich diesen Benutzer aktualisieren, beispielsweise sein Alter ändern. Zu diesem Zweck könnte ich eine Abfrage verwenden, entweder QueryDSL oder NamedQuery, was auch immer. Aber wenn ich bedenke, dass ich nur spring-data-jpa und das JPARepository verwenden möchte, wie kann ich dann sagen, dass ich anstelle einer Einfügung ein Update durchführen möchte?

Wie kann ich spring-data-jpa konkret mitteilen, dass Benutzer mit demselben Benutzernamen und Vornamen tatsächlich GLEICH sind und dass die vorhandene Entität aktualisiert werden soll? Das Überschreiben von Gleichen hat dieses Problem nicht gelöst.

Eugene
quelle
1
Sind Sie sicher, dass die ID neu geschrieben wird, wenn Sie ein vorhandenes Objekt in der Datenbank speichern? Nie hatte dies auf meinem Projekt tbh
Byron Voorbach
@ ByronVoorbach yup du hast recht, habe das gerade getestet. Aktualisieren Sie die Frage auch, thx
Eugene
2
Hallo Freund, Sie können diesen Link stapeln stackoverflow.com/questions/24420572/… Sie können ein Ansatz wie saveOrUpdate () sein
ibrahimKiraz
Ich denke, dass wir hier eine schöne Lösung haben:
Geben Sie

Antworten:

206

Die Identität von Entitäten wird durch ihre Primärschlüssel definiert. Da firstnameund lastnamenicht Teil des Primärschlüssels sind, können Sie JPA nicht anweisen, Users mit denselben firstnames und lastnames als gleich zu behandeln, wenn sie unterschiedlich sinduserId s haben.

Wenn Sie also ein Userdurch sein firstnameund identifiziertes Objekt aktualisieren möchten lastname, müssen Sie dies Userdurch eine Abfrage ermitteln und dann die entsprechenden Felder des gefundenen Objekts ändern. Diese Änderungen werden am Ende der Transaktion automatisch in die Datenbank übertragen, sodass Sie nichts tun müssen, um diese Änderungen explizit zu speichern.

BEARBEITEN:

Vielleicht sollte ich die Gesamtsemantik von JPA näher erläutern. Es gibt zwei Hauptansätze für das Design von Persistenz-APIs:

  • Ansatz einfügen / aktualisieren . Wenn Sie die Datenbank ändern müssen, sollten Sie die Methoden der Persistenz-API explizit aufrufen: Sie rufen insertauf, um ein Objekt einzufügen oder updateden neuen Status des Objekts in der Datenbank zu speichern.

  • Ansatz der Arbeitseinheit . In diesem Fall verfügen Sie über eine Reihe von Objekten, die von der Persistenzbibliothek verwaltet werden. Alle Änderungen, die Sie an diesen Objekten vornehmen, werden am Ende der Arbeitseinheit (dh im typischen Fall am Ende der aktuellen Transaktion) automatisch in die Datenbank übertragen. Wenn Sie neue Datensatz in die Datenbank einfügen müssen, stellen Sie das entsprechende Objekt verwaltet . Verwaltete Objekte werden anhand ihrer Primärschlüssel identifiziert. Wenn Sie also ein Objekt mit vordefiniertem Primärschlüssel verwalten , wird es dem Datenbankdatensatz mit derselben ID zugeordnet, und der Status dieses Objekts wird automatisch an diesen Datensatz weitergegeben.

JPA folgt dem letzteren Ansatz. save()In Spring Data wird JPA durch merge()einfaches JPA unterstützt, sodass Ihre Entität wie oben beschrieben verwaltet wird. Dies bedeutet, dass beim Aufrufen save()eines Objekts mit vordefinierter ID der entsprechende Datenbankdatensatz aktualisiert wird, anstatt einen neuen einzufügen, und auch erklärt wird, warum save()nicht aufgerufen wird create().

axtavt
quelle
Nun ja, ich glaube ich weiß das. Ich bezog mich streng auf spring-data-jpa. Ich habe jetzt zwei Probleme mit dieser Antwort: 1) Geschäftswerte sollten nicht Teil des Primärschlüssels sein - das ist eine bekannte Sache, oder? Es ist also nicht gut, Vor- und Nachnamen als Primärschlüssel zu haben. Und 2) Warum heißt diese Methode dann nicht create, sondern save in spring-data-jpa?
Eugene
1
"save () in Spring Data JPA wird durch merge () in normalem JPA unterstützt" haben Sie sich den Code tatsächlich angesehen? Ich habe es gerade getan und beides wurde entweder durch Fortbestehen oder Zusammenführen gesichert. Es wird basierend auf dem Vorhandensein der ID (Primärschlüssel) beibehalten oder aktualisiert. Ich denke, dies sollte in der Speichermethode dokumentiert werden. Speichern ist also tatsächlich entweder zusammenführen oder bestehen bleiben.
Eugene
Ich denke, es wird auch als Speichern bezeichnet, da es das Objekt speichern soll, egal in welchem ​​Zustand es sich befindet - es wird eine Aktualisierung oder Einfügung durchführen, die dem Speicherstatus entspricht.
Eugene
1
Das wird bei mir nicht funktionieren. Ich habe versucht, eine Entität mit einem gültigen Primärschlüssel zu speichern. Ich greife mit "order / edit /: id" auf die Seite zu und es gibt mir tatsächlich das richtige Objekt anhand der ID. Nichts, was ich aus Liebe zu Gott versuche, wird die Entität aktualisieren. Es wird immer eine neue Entität veröffentlicht. Ich habe sogar versucht, einen benutzerdefinierten Dienst zu erstellen und "Merge" mit meinem EntityManager zu verwenden, aber es funktioniert immer noch nicht. Es wird immer eine neue Entität veröffentlicht.
DtechNet
1
@DTechNet Ich hatte ein ähnliches Problem wie Sie, DtechNet, und es stellte sich heraus, dass mein Problem darin bestand, dass in meiner Spring Data-Repository-Oberfläche der falsche Primärschlüsseltyp angegeben war. Es sagte, extends CrudRepository<MyEntity, Integer>anstatt extends CrudRepository<MyEntity, String>wie es hätte sein sollen. Hilft das? Ich weiß, dass dies fast ein Jahr später ist. Hoffe es hilft jemand anderem.
Kent Bull
139

Da konzentriert sich die Antwort von @axtavt JPAnicht daraufspring-data-jpa

Das Aktualisieren einer Entität durch Abfragen und Speichern ist nicht effizient, da zwei Abfragen erforderlich sind und die Abfrage möglicherweise recht teuer sein kann, da sie möglicherweise anderen Tabellen beitritt und alle vorhandenen Sammlungen lädt fetchType=FetchType.EAGER

Spring-data-jpaunterstützt den Update-Vorgang.
Sie müssen die Methode in der Repository-Schnittstelle definieren @Queryund mit und kommentieren @Modifying.

@Modifying
@Query("update User u set u.firstname = ?1, u.lastname = ?2 where u.id = ?3")
void setUserInfoById(String firstname, String lastname, Integer userId);

@Querydient zum Definieren einer benutzerdefinierten Abfrage und @Modifyingzum Feststellen, spring-data-jpadass es sich bei dieser Abfrage um eine Aktualisierungsoperation handelt und dies executeUpdate()nicht erforderlich ist executeQuery().

Sie können andere Rückgabetypen angeben:
int- Die Anzahl der Datensätze, die aktualisiert werden.
boolean- true, wenn ein Datensatz aktualisiert wird. Ansonsten falsch.


Hinweis : Führen Sie diesen Code in einer Transaktion aus .

Hussachai
quelle
10
Stellen
1
Hallo! Danke, ich benutze Spring Data Beans. Also kümmert es sich automatisch um mein Update. <S erweitert T> S speichern (S Entität); kümmert sich automatisch um das Update. Ich musste deine Methode nicht anwenden! Trotzdem danke!
bks4line
3
Jederzeit :) Die Speichermethode funktioniert, wenn Sie die Entität speichern möchten (sie delegiert den Aufruf entweder an em.persist () oder em.merge () hinter den Kulissen). Auf jeden Fall ist die benutzerdefinierte Abfrage nützlich, wenn Sie nur einige Felder in der Datenbank aktualisieren möchten.
Hussachai
Wie wäre es, wenn einer Ihrer Parameter die ID der Unterentität (manyToOne) ist? Wie sollte das aktualisiert werden? (Buch haben einen Autor und Sie haben Buch-ID und Autoren-ID übergeben, um den Buchautor zu aktualisieren)
Mahdi
1
To update an entity by querying then saving is not efficientDies sind nicht die einzigen beiden Möglichkeiten. Es gibt eine Möglichkeit, die ID anzugeben und das Zeilenobjekt abzurufen, ohne es abzufragen. Wenn Sie ein row = repo.getOne(id)und dann row.attr = 42; repo.save(row);ausführen und die Protokolle anzeigen, wird nur die Aktualisierungsabfrage angezeigt.
Nurettin
21

Sie können diese Funktion einfach mit der JPA-Funktion save () verwenden, aber das als Parameter gesendete Objekt muss eine vorhandene ID in der Datenbank enthalten, da dies sonst nicht funktioniert, da save () beim Senden eines Objekts ohne ID direkt eine Zeile hinzufügt Datenbank, aber wenn wir ein Objekt mit einer vorhandenen ID senden, werden die bereits in der Datenbank gefundenen Spalten geändert.

public void updateUser(Userinfos u) {
    User userFromDb = userRepository.findById(u.getid());
    // crush the variables of the object found
    userFromDb.setFirstname("john"); 
    userFromDb.setLastname("dew");
    userFromDb.setAge(16);
    userRepository.save(userFromDb);
}
Kalifornium
quelle
4
Ist die Leistung kein Problem, wenn Sie das Objekt vor dem Update aus der Datenbank laden müssen? (Entschuldigung für mein Englisch)
August 0490
3
Es gibt zwei Abfragen anstelle von einer, die nicht bevorzugt werden
Tigran Babajanyan
Ich weiß, ich Juste zeigte eine andere Methode zu tun! aber warum hat die jpa die update-funktion implementiert, wenn die id identisch ist?
Kalifornium
17

Wie bereits von anderen erwähnt, ist die save() enthält selbst sowohl einen Erstellungs- als auch einen Aktualisierungsvorgang.

Ich möchte nur eine Ergänzung darüber hinzufügen, was hinter dem steht save() Methode .

Lassen Sie uns zunächst die Erweiterungs- / Implementierungshierarchie der CrudRepository<T,ID>, Geben Sie hier die Bildbeschreibung ein

Ok, überprüfen wir die save()Implementierung unter SimpleJpaRepository<T, ID>,

@Transactional
public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

Wie Sie sehen können, wird zunächst geprüft, ob die ID vorhanden ist oder nicht. Wenn die Entität bereits vorhanden ist, erfolgt die Aktualisierung nur nach merge(entity)Methode. Andernfalls wird nach Methode ein neuer Datensatz eingefügt persist(entity).

Eugene
quelle
7

Mit spring-data-jpa save()hatte ich das gleiche Problem wie @DtechNet. Ich meine, jeder hat save()ein neues Objekt erstellt, anstatt es zu aktualisieren. Um dies zu lösen, musste ich der versionEntität und der zugehörigen Tabelle ein Feld hinzufügen .

Lächeln
quelle
Was Sie damit meinen, Versionsfeld zur Entität und zugehörigen Tabelle hinzuzufügen.
jcrshankar
5

So habe ich das Problem gelöst:

User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);
Adam
quelle
Verwenden Sie die @Transactionobige Methode für mehrere Datenbankanforderungen. Eine in diesem Fall nicht benötigte userRepository.save(inbound);Änderung wird automatisch gespült.
Grigory Kislin
5

Federdaten save() Datenmethode hilft Ihnen dabei, beides auszuführen: Hinzufügen eines neuen Elements und Aktualisieren eines vorhandenen Elements.

Rufen Sie einfach an save()und genießen Sie das Leben :))

Amir Mhp
quelle
Wenn ich auf diese Weise andere gesendete IdDaten speichere, wie kann ich das Speichern neuer Datensätze vermeiden?
Abd Abughazaleh
1
@AbdAbughazaleh Überprüfen Sie, ob die eingehende ID in Ihrem Repository vorhanden ist oder nicht. Sie können 'repository.findById (id) .map (entity -> {// etwas zurückgeben repository.save (entity)}) .orElseGet (() -> {// etwas zurückgeben;}) verwenden; '
Amir Mhp
1
public void updateLaserDataByHumanId(String replacement, String humanId) {
    List<LaserData> laserDataByHumanId = laserDataRepository.findByHumanId(humanId);
    laserDataByHumanId.stream()
            .map(en -> en.setHumanId(replacement))
            .collect(Collectors.toList())
            .forEach(en -> laserDataRepository.save(en));
}
BinLee
quelle
@ JoshuaTaylor in der Tat, das völlig verpasst :) wird den Kommentar entfernen ...
Eugene
1

Insbesondere, wie kann ich spring-data-jpa mitteilen, dass Benutzer mit demselben Benutzernamen und Vornamen tatsächlich GLEICH sind und dass die Entität aktualisiert werden soll. Das Überschreiben von Gleichen hat nicht funktioniert.

Zu diesem speziellen Zweck kann man einen zusammengesetzten Schlüssel wie folgt einführen:

CREATE TABLE IF NOT EXISTS `test`.`user` (
  `username` VARCHAR(45) NOT NULL,
  `firstname` VARCHAR(45) NOT NULL,
  `description` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`username`, `firstname`))

Kartierung:

@Embeddable
public class UserKey implements Serializable {
    protected String username;
    protected String firstname;

    public UserKey() {}

    public UserKey(String username, String firstname) {
        this.username = username;
        this.firstname = firstname;
    }
    // equals, hashCode
}

So verwenden Sie es:

@Entity
public class UserEntity implements Serializable {
    @EmbeddedId
    private UserKey primaryKey;

    private String description;

    //...
}

JpaRepository würde so aussehen:

public interface UserEntityRepository extends JpaRepository<UserEntity, UserKey>

Dann könnten Sie die folgende Redewendung verwenden: DTO mit Benutzerinformationen akzeptieren, Name und Vorname extrahieren und UserKey erstellen, dann eine UserEntity mit diesem zusammengesetzten Schlüssel erstellen und dann Spring Data save () aufrufen, das alles für Sie aussortieren soll.

Andreas Gelever
quelle