it-roy-ru.com

JPA: обновлять только определенные поля

Есть ли способ обновить только некоторые поля объекта сущности, используя метод save из Spring Data JPA ?

Например, у меня есть сущность JPA, подобная этой:

@Entity
public class User {

  @Id
  private Long id;

  @NotNull
  private String login;

  @Id
  private String name;

  // getter / setter
  // ...
}

С его CRUD-репо:

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

В Spring MVC У меня есть контроллер, который получает объект User для его обновления: 

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

   // ...
}

Я знаю, что могу загрузить пользователя, используя findOne, затем изменить его имя и обновить его, используя save... Но если у меня есть 100 полей, и я хочу обновить 50 из них, это может быть очень раздражающим, изменять каждое значение .. 

Нет ли способа сказать что-то вроде " пропустить все нулевые значения при сохранении объекта "?

37
Andrea

У меня был тот же вопрос, и, как указывает М. Дейн, ответ - нет, вы не можете использовать сохранение. Основная проблема в том, что Spring Data не знает, что делать с нулями. Нулевое значение не установлено или установлено, потому что его нужно удалить?

Теперь, судя по вашему вопросу, я полагаю, что у вас также была та же мысль, что и у меня, которая заключалась в том, что сохранение позволило бы мне избежать ручной установки всех измененных значений.

Так можно ли тогда избежать всего картирования Мануэля? Что ж, если вы решите придерживаться соглашения о том, что значение null всегда означает «не установлено», и у вас есть исходный идентификатор модели, тогда да . Вы можете избежать любого сопоставления самостоятельно, используя Springs BeanUtils.

Вы можете сделать следующее:

  1. Читать существующий объект
  2. Используйте BeanUtils для копирования значений
  3. Сохранить объект

Теперь, Spring BeanUtils actual не поддерживает не копирование нулевых значений, поэтому он перезапишет любые значения, не установленные с null, в существующем объекте модели. К счастью, здесь есть решение:

Как игнорировать нулевые значения, используя springframework BeanUtils copyProperties?

Так что, собрав все это вместе, вы получите нечто подобное

@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

Используя JPA, вы можете сделать это следующим образом.

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

проблема не в связанных данных Spring, связанных с jpa, а в используемой библиотеке реализации jpa. В спящем режиме вы можете взглянуть на:

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

1
Pirulino

Для long, int и других типов; Вы можете использовать следующий код;

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

Если вы читаете запрос как JSON String, это можно сделать с помощью Jackson API. Вот код ниже. Код сравнивает существующие элементы POJO и создает новый с обновленными полями. Используйте новый POJO, чтобы сохраниться.

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

Окончательный результат будет следующим: обратите внимание, только lastNm и комментарии отражают изменения.

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

Вы можете написать что-то вроде 

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

Или Если вы хотите обновить только поля, которые были изменены, вы можете использовать аннотацию 

@DynamicUpdate

Пример кода:

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