Comment mettre à jour une entité à l'aide de spring-data-jpa?

196

Eh bien, la question dit à peu près tout. Utilisation de JPARepository, comment mettre à jour une entité?

JPARepository n'a qu'une méthode de sauvegarde , qui ne me dit pas s'il est réellement créé ou mis à jour. Par exemple, insérer un objet simple à la base de données utilisateur, qui a trois champs: firstname, lastnameet 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;
  }
}

Ensuite save(), j'appelle simplement , qui à ce stade est en fait une insertion dans la base de données:

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

Jusqu'ici tout va bien. Maintenant, je veux mettre à jour cet utilisateur, disons changer son âge. Pour cela, je pourrais utiliser une requête, QueryDSL ou NamedQuery, peu importe. Mais, étant donné que je veux juste utiliser spring-data-jpa et le référentiel JPAR, comment puis-je lui dire qu'au lieu d'un insert, je veux faire une mise à jour?

Plus précisément, comment puis-je dire à spring-data-jpa que les utilisateurs avec le même nom d'utilisateur et le même prénom sont en fait EQUAL et que l'entité existante est censée être mise à jour? Le remplacement des égaux n'a pas résolu ce problème.

Eugène
la source
1
Êtes-vous sûr que l'ID est réécrit lorsque vous enregistrez un objet existant dans la base de données? Jamais eu cela sur mon projet tbh
Byron Voorbach
@ByronVoorbach vous avez raison, je viens de tester ça. mettre à jour la question également, merci
Eugene
2
Bonjour ami, vous pouvez regarder ce lien stackoverflow.com/questions/24420572/… vous pouvez être une approche comme saveOrUpdate ()
ibrahimKiraz
Je pense que nous avons une belle solution ici: entrez la description du lien ici
Clebio Vieira

Réponses:

208

L'identité des entités est définie par leurs clés primaires. Puisque firstnameet lastnamene font pas partie de la clé primaire, vous ne pouvez pas dire à JPA de traiter Users avec les mêmes firstnames et lastnames comme égaux s'ils ont des userIds différents .

Donc, si vous souhaitez mettre à jour un Useridentifié par son firstnameet lastname, vous devez le trouver Userpar une requête, puis modifier les champs appropriés de l'objet que vous avez trouvé. Ces modifications seront automatiquement vidées dans la base de données à la fin de la transaction, de sorte que vous n'ayez rien à faire pour enregistrer ces modifications explicitement.

ÉDITER:

Peut-être devrais-je élaborer sur la sémantique générale de JPA. Il existe deux approches principales pour la conception des API de persistance:

  • approche d'insertion / mise à jour . Lorsque vous avez besoin de modifier la base de données, vous devez appeler explicitement les méthodes d'API de persistance: vous appelez insertpour insérer un objet, ou updatepour enregistrer le nouvel état de l'objet dans la base de données.

  • Approche de l'unité de travail . Dans ce cas, vous disposez d'un ensemble d'objets gérés par la bibliothèque de persistance. Toutes les modifications que vous apportez à ces objets seront automatiquement vidées dans la base de données à la fin de l'unité de travail (c'est-à-dire à la fin de la transaction en cours dans un cas typique). Lorsque vous devez insérer un nouvel enregistrement dans la base de données, vous rendez l'objet correspondant géré . Les objets gérés sont identifiés par leurs clés primaires, de sorte que si vous créez un objet avec une clé primaire prédéfinie gérée , il sera associé à l'enregistrement de base de données du même identifiant et l'état de cet objet sera automatiquement propagé à cet enregistrement.

JPA suit cette dernière approche. save()dans Spring Data JPA est soutenu par merge()JPA simple, donc il rend votre entité gérée comme décrit ci-dessus. Cela signifie que l'appel save()à un objet avec un identifiant prédéfini mettra à jour l'enregistrement de base de données correspondant plutôt que d'en insérer un nouveau, et explique également pourquoi il save()n'est pas appelé create().

axtavt
la source
eh bien oui je pense que je le sais. Je faisais strictement référence à spring-data-jpa. J'ai deux problèmes avec cette réponse maintenant: 1) les valeurs commerciales ne sont pas censées faire partie de la clé primaire - c'est une chose connue, non? Donc, avoir le prénom et le nom comme clé primaire n'est pas bon. Et 2) Pourquoi cette méthode n'est-elle pas appelée create, mais sauvegardée à la place dans spring-data-jpa?
Eugene
1
"save () dans Spring Data JPA est soutenu par merge () dans JPA brut" Avez-vous réellement regardé le code? Je viens de le faire et cela a été soutenu par persister ou fusionner. Il persistera ou se mettra à jour en fonction de la présence de l'identifiant (clé primaire). Ceci, je pense, devrait être documenté dans la méthode save. Donc, enregistrer est en fait SOIT fusion ou persistance.
Eugene
Je pense que cela s'appelle également save, car il est censé enregistrer l'objet quel que soit l'état dans lequel il se trouve - il va effectuer une mise à jour ou une insertion qui est égale à l'état de sauvegarde.
Eugene
1
Cela ne fonctionnera pas pour moi. J'ai essayé de sauvegarder sur une entité avec une clé primaire valide. J'accède à la page avec "order / edit /: id" et il me donne en fait l'objet correct par Id. Rien de ce que j'essaye pour l'amour de Dieu ne mettra à jour l'entité. Il publie toujours une nouvelle entité .. J'ai même essayé de créer un service personnalisé et d'utiliser "merge" avec mon EntityManager et cela ne fonctionnera toujours pas. Il affichera toujours une nouvelle entité.
DtechNet
1
@DTechNet Je rencontrais un problème similaire à vous, DtechNet, et il s'est avéré que mon problème était que j'avais le mauvais type de clé primaire spécifié dans mon interface de référentiel Spring Data. Il a dit extends CrudRepository<MyEntity, Integer>au lieu de extends CrudRepository<MyEntity, String>ce qu'il aurait dû. Est ce que ça aide? Je sais que c'est presque un an plus tard. J'espère que cela aide quelqu'un d'autre.
Kent Bull
141

Puisque la réponse de @axtavt se concentre sur le JPAnonspring-data-jpa

Mettre à jour une entité en interrogeant puis en enregistrant n'est pas efficace car cela nécessite deux requêtes et éventuellement la requête peut être assez coûteuse car elle peut rejoindre d'autres tables et charger toutes les collections qui ont fetchType=FetchType.EAGER

Spring-data-jpaprend en charge l'opération de mise à jour.
Vous devez définir la méthode dans l'interface du référentiel et l'annoter avec @Queryet @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);

@Querysert à définir une requête personnalisée et @Modifyingindique spring-data-jpaque cette requête est une opération de mise à jour et qu'elle executeUpdate()ne l' exige pas executeQuery().

Vous pouvez spécifier d'autres types de retour:
int- le nombre d'enregistrements mis à jour.
boolean- vrai si un enregistrement est mis à jour. Sinon, faux.


Remarque : exécutez ce code dans une transaction .

Hussachai
la source
10
Assurez-vous de l'exécuter dans la transaction
hussachai
1
Hey! Merci d'utiliser des beans de données de printemps. Il s'occupera donc automatiquement de ma mise à jour. <S étend T> S sauvegarde (entité S); s'occupe automatiquement de la mise à jour. je n'ai pas eu à utiliser votre méthode! Merci quand même!
bks4line
3
Anytime :) La méthode save fonctionne si vous voulez sauvegarder l'entité (elle déléguera l'appel à em.persist () ou em.merge () derrière la scène). Quoi qu'il en soit, la requête personnalisée est utile lorsque vous souhaitez mettre à jour uniquement certains champs de la base de données.
hussachai
que diriez-vous quand l'un de vos paramètres est l'id de la sous-entité (manyToOne) comment mettre à jour cela? (le livre a un auteur et vous avez passé l'identifiant du livre et l'identifiant de l'auteur pour mettre à jour l'auteur du livre)
Mahdi
1
To update an entity by querying then saving is not efficientce ne sont pas les deux seuls choix. Il existe un moyen de spécifier l'id et d'obtenir l'objet de ligne sans l'interroger. Si vous faites un row = repo.getOne(id)et puis row.attr = 42; repo.save(row);et regardez les journaux, vous ne verrez que la requête de mise à jour.
nurettin
21

Vous pouvez simplement utiliser cette fonction avec la fonction JPA save (), mais l'objet envoyé comme paramètre doit contenir un identifiant existant dans la base de données sinon cela ne fonctionnera pas, car save () lorsque nous envoyons un objet sans identifiant, il ajoute directement une ligne dans base de données, mais si nous envoyons un objet avec un identifiant existant, cela change les colonnes déjà trouvées dans la base de données.

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
la source
4
la performance n'est-elle pas un problème si vous devez charger l'objet à partir de la base de données avant d'effectuer la mise à jour? (désolé pour mon anglais)
août0490
3
il y a deux requêtes au lieu d'une, ce qui n'est pas du tout préféré
Tigran Babajanyan
Je sais que je viens de montrer une autre méthode à faire! mais pourquoi le jpa a implémenté la fonction de mise à jour quand l'id est le même?
Kalifornium
17

Comme cela a déjà été mentionné par d'autres, le save()lui - même contient à la fois une opération de création et de mise à jour.

Je veux juste ajouter un supplément sur ce qui se cache derrière la save()méthode.

Tout d'abord, voyons la hiérarchie étendre / implémenter le CrudRepository<T,ID>, entrez la description de l'image ici

Ok, vérifions l' save()implémentation à 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);
    }
}

Comme vous pouvez le voir, il vérifiera si l'ID existe ou non dans un premier temps, si l'entité est déjà là, seule la mise à jour se fera par merge(entity)méthode et dans le cas contraire, un nouvel enregistrement est inséré par persist(entity)méthode.

Eugène
la source
7

En utilisant spring-data-jpa save(), j'avais le même problème que @DtechNet. Je veux dire que chacun save()créait un nouvel objet au lieu d'une mise à jour. Pour résoudre ce problème, j'ai dû ajouter un versionchamp à l'entité et à la table associée.

sourire
la source
ce que vous entendez par pour ajouter le champ de version à l'entité et à la table associée.
jcrshankar
5

Voici comment j'ai résolu le problème:

User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);
Adam
la source
Utilisez la @Transactionméthode ci-dessus pour plusieurs requêtes de base de données. Dans ce cas, inutile userRepository.save(inbound);, les modifications sont automatiquement supprimées.
Grigory Kislin
5

La save()méthode de données de printemps vous aidera à effectuer les deux: ajouter un nouvel élément et mettre à jour un élément existant.

Appelez le save()et profitez de la vie :))

Amir Mhp
la source
De cette façon, si j'ai envoyé différent, Idje l'enregistrerai, comment puis-je éviter de sauvegarder un nouvel enregistrement.
Abd Abughazaleh
1
@AbdAbughazaleh vérifie si l'ID entrant existe ou non dans votre référentiel. vous pouvez utiliser 'repository.findById (id) .map (entity -> {// faire quelque chose return repository.save (entity)}) .orElseGet (() -> {// faire quelque chose return;}); '
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
la source
@JoshuaTaylor en effet, a manqué cela entièrement :) supprimera le commentaire ...
Eugene
1

Plus précisément, comment puis-je dire à spring-data-jpa que les utilisateurs qui ont le même nom d'utilisateur et le même prénom sont en fait EQUAL et qu'il est censé mettre à jour l'entité. Le remplacement des égaux ne fonctionnait pas.

Dans ce but particulier, on peut introduire une clé composite comme celle-ci:

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`))

Cartographie:

@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
}

Voici comment l'utiliser:

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

    private String description;

    //...
}

JpaRepository ressemblerait à ceci:

public interface UserEntityRepository extends JpaRepository<UserEntity, UserKey>

Ensuite, vous pouvez utiliser l' idiome suivant : accepter DTO avec les informations de l'utilisateur, extraire le nom et le prénom et créer UserKey, puis créer une UserEntity avec cette clé composite, puis appeler Spring Data save () qui devrait tout trier pour vous.

Andreas Gelever
la source