JPA: обновление только определенных полей
Есть ли способ для обновления только некоторых полей объекта сущности, используя метод save
от Spring данных 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 из них, это может быть очень неприятным изменением каждое значение..
Нет ли способа сказать что-то вроде " пропустить все нулевые значения при сохранении объекта"
Ответы
Ответ 1
У меня был тот же вопрос, и, как указывает M. Deinum, ответ отрицательный, вы не можете использовать save. Основная проблема заключается в том, что Spring Data не знал, что делать с нулями. Не установлено ли значение null или оно установлено, потому что его нужно удалить?
Теперь, судя по вашему вопросу, я предполагаю, что у вас также была та же мысль, что и у меня, и это спасение позволило мне избежать ручной установки всех измененных значений.
Итак, можно ли избежать всех мануэльных карт? Ну, если вы решите придерживаться соглашения, что nulls всегда означает "не установлено", и у вас есть исходный идентификатор модели, то да.
Вы можете избежать любого сопоставления себя, используя Springs BeanUtils.
Вы можете сделать следующее:
- Прочитать существующий объект
- Использовать BeanUtils для копирования значений
- Сохранить объект
Теперь Spring BeanUtils actual не поддерживает не копирование нулевых значений, поэтому он перезапишет любые значения, не заданные нулем для объекта текущей модели. К счастью, здесь есть решение:
Как игнорировать нулевые значения с помощью 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);
}
Ответ 2
Используя 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();
Ответ 3
проблема не в spring данных, связанных с jpa, а в реализации lib, которую вы используете.
В случае спящего режима вы можете взглянуть на:
http://www.mkyong.com/hibernate/hibernate-dynamic-update-attribute-example/
Ответ 4
Если вы читаете запрос как 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]
Ответ 5
Вы можете написать что-то вроде
@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"})
Ответ 6
Для long, int и других типов; Вы можете использовать следующий код;
if (srcValue == null|(src.getPropertyTypeDescriptor(pd.getName()).getType().equals(long.class) && srcValue.toString().equals("0")))
emptyNames.add(pd.getName());