web-dev-qa-db-fra.com

JPA: mettre à jour uniquement des champs spécifiques

Existe-t-il un moyen de mettre à jour seulement certains champs d'un objet entité à l'aide de la méthode save à partir de Spring Data JPA ?

Par exemple, j'ai une entité JPA comme celle-ci:

@Entity
public class User {

  @Id
  private Long id;

  @NotNull
  private String login;

  @Id
  private String name;

  // getter / setter
  // ...
}

Avec son repo CRUD:

public interface UserRepository extends CrudRepository<User, Long> { }

Dans Spring MVC J'ai un contrôleur qui obtient un objet User pour le mettre à jour: 

@RequestMapping(value = "/rest/user", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<?> updateUser(@RequestBody User user) {

   // Assuming that user have its id and it is already stored in the database,
   // and user.login is null since I don't want to change it,
   // while user.name have the new value

   // I would update only its name while the login value should keep the value 
   // in the database
   userRepository.save(user);

   // ...
}

Je sais que je pourrais charger l'utilisateur en utilisant findOne, puis changer son nom et le mettre à jour en utilisant save... Mais si j'ai 100 champs et que je veux en mettre à jour 50, cela pourrait être très gênant de changer chaque valeur .. 

N'y a-t-il aucun moyen de dire quelque chose comme " ignorer toutes les valeurs nulles lors de l'enregistrement de l'objet "?

37
Andrea

J'avais la même question et, comme le souligne M. Deinum, la réponse est non, vous ne pouvez pas utiliser de sauvegarde. Le problème principal est que Spring Data ne sait pas quoi faire avec les valeurs NULL. La valeur null n'est-elle pas définie ou est-elle définie car elle doit être supprimée?

Maintenant, à en juger par votre question, je suppose que vous aviez également la même pensée que moi, à savoir que la sauvegarde me permettrait d’éviter de définir manuellement toutes les valeurs modifiées.

Alors est-il possible d'éviter tout le mappage manuel alors? Eh bien, si vous choisissez de vous conformer à la convention voulant que nulls signifie toujours "non défini" et que vous ayez l'identifiant du modèle d'origine, alors oui ... vous pouvez éviter tout mappage vous-même en utilisant Springs BeanUtils.

Vous pouvez faire ce qui suit:

  1. Lire l'objet existant
  2. Utiliser BeanUtils pour copier des valeurs
  3. Sauvegarder l'objet

À présent, les valeurs réelles de Spring BeanUtils ne prennent pas en charge la copie des valeurs null. Par conséquent, toutes les valeurs non définies avec la valeur null seront écrasées sur l'objet modèle existant. Heureusement, il existe une solution ici:

Comment ignorer les valeurs NULL en utilisant springframework BeanUtils copyProperties?

Donc, en mettant tout cela ensemble, vous vous retrouveriez avec quelque chose comme ça

@RequestMapping(value = "/rest/user", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<?> updateUser(@RequestBody User user) {

   User existing = userRepository.read(user.getId());
   copyNonNullProperties(user, existing);
   userRepository.save(existing);

   // ...
}

public static void copyNonNullProperties(Object src, Object target) {
    BeanUtils.copyProperties(src, target, getNullPropertyNames(src));
}

public static String[] getNullPropertyNames (Object source) {
    final BeanWrapper src = new BeanWrapperImpl(source);
    Java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();

    Set<String> emptyNames = new HashSet<String>();
    for(Java.beans.PropertyDescriptor pd : pds) {
        Object srcValue = src.getPropertyValue(pd.getName());
        if (srcValue == null) emptyNames.add(pd.getName());
    }
    String[] result = new String[emptyNames.size()];
    return emptyNames.toArray(result);
}
30
William

En utilisant JPA, vous pouvez le faire de cette façon.

CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaUpdate<User> criteria = builder.createCriteriaUpdate(User.class);
Root<User> root = criteria.from(User.class);
criteria.set(root.get("lastSeen"), date);
criteria.where(builder.equal(root.get("id"), user.getId()));
session.createQuery(criteria).executeUpdate();
4
Arjun Nayak

le problème n’est pas lié aux données de printemps jpa, mais à la bibliothèque d’implémentation jpa que vous utilisez. En cas d'hibernation, vous pouvez regarder:

http://www.mkyong.com/hibernate/hibernate-dynamic-update-attribute-example/

1
Pirulino

Pour les types long, int et autres; Vous pouvez utiliser le code suivant;

            if (srcValue == null|(src.getPropertyTypeDescriptor(pd.getName()).getType().equals(long.class) && srcValue.toString().equals("0")))
            emptyNames.add(pd.getName());
0
bmck

Si vous lisez la demande en tant que chaîne JSON, vous pouvez le faire à l'aide de l'API Jackson. Voici le code ci-dessous. Code compare un POJO Elements existant et en crée un avec les champs mis à jour. Utilisez le nouveau POJO pour persister.

public class TestJacksonUpdate {

class Person implements Serializable {
    private static final long serialVersionUID = -7207591780123645266L;
    public String code = "1000";
    public String firstNm = "John";
    public String lastNm;
    public Integer age;
    public String comments = "Old Comments";

    @Override
    public String toString() {
        return "Person [code=" + code + ", firstNm=" + firstNm + ", lastNm=" + lastNm + ", age=" + age
                + ", comments=" + comments + "]";
    }
}

public static void main(String[] args) throws JsonProcessingException, IOException {
    TestJacksonUpdate o = new TestJacksonUpdate();

    String input = "{\"code\":\"1000\",\"lastNm\":\"Smith\",\"comments\":\"Jackson Update WOW\"}";
    Person persist = o.new Person();

    System.out.println("persist: " + persist);

    ObjectMapper mapper = new ObjectMapper();
    Person finalPerson = mapper.readerForUpdating(persist).readValue(input);

    System.out.println("Final: " + finalPerson);
}}

Le résultat final serait, Notez que lastNm et Les commentaires reflètent les changements.

persist: Person [code=1000, firstNm=John, lastNm=null, age=null, comments=Old Comments]
Final: Person [code=1000, firstNm=John, lastNm=Smith, age=null, comments=Jackson Update WOW]
0
Anand

Vous êtes capable d'écrire quelque chose comme 

@Modifying
    @Query("update StudentXGroup iSxG set iSxG.deleteStatute = 1 where iSxG.groupId = ?1")
    Integer deleteStudnetsFromDeltedGroup(Integer groupId);

Ou Si vous souhaitez mettre à jour uniquement les champs modifiés, vous pouvez utiliser l'annotation. 

@DynamicUpdate

Exemple de code:

@Entity
@Table(name = "lesson", schema = "oma")
@Where(clause = "delete_statute = 0")
@DynamicUpdate
@SQLDelete(sql = "update oma.lesson set delete_statute = 1, "
        + "delete_date = CURRENT_TIMESTAMP, "
        + "delete_user = '@currentUser' "
        + "where lesson_id = ?")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})