Можно ли обновить только подмножество атрибутов на объекте, используя Spring MVC с JPA?
Я работаю с Spring Roo, использую Spring MVC и JPA для сохранения с базой данных MySQL. Я очень новичок в Spring MVC и Java в целом, но работал с CakePHP и Rails.
у меня есть User
объект, который содержит личные данные в дополнение к паролю. Примерно так (за исключением большого количества сгенерированных Roo функций в дополнительных файлах.aj):
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "ID")
private Long id;
@Column(name = "PASSWORD", length = 32)
private String password;
@Column(name = "FIRST_NAME", length = 25)
private String firstName;
@Column(name = "LAST_NAME", length = 25)
private String lastName;
@Column(name = "ADDRESS", length = 255)
private String address;
// The appropriate getters and setters
...
}
Тогда у меня есть действие редактирования в моем User
контроллер, который я создал следующие соглашения из автоматически сгенерированных лесов Roo:
@RequestMapping(value="/edit", method = RequestMethod.GET)
public String editForm(Model uiModel) {
String username = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
uiModel.addAttribute("user", User.findUserByUsername(username).getSingleResult());
return "account/edit";
}
И представление JSPX для визуализации формы, опять же в соответствии с соглашениями Ру:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:field="urn:jsptagdir:/WEB-INF/tags/form/fields" xmlns:form="urn:jsptagdir:/WEB-INF/tags/form" xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0">
<jsp:directive.page contentType="text/html;charset=UTF-8"/>
<jsp:output omit-xml-declaration="yes"/>
<form:update id="" label="Personal Details" modelAttribute="user" path="/account" versionField="none">
<field:input id="" field="firstName" label="First Name" />
<field:input id="" field="lastName" label="Last Name" />
<field:textarea id="" field="address" label="Street Address" />
</form:update>
</div>
Я не хочу, чтобы форма обновляла пароль, только предоставленные поля (имя, фамилия и адрес).
Действие обновления, снова в соответствии с соглашением Roo:
@RequestMapping(method = RequestMethod.PUT, produces = "text/html")
public String edit(@Valid User user, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
if (bindingResult.hasErrors()) {
uiModel.addAttribute("user", user);
return "account/edit";
}
uiModel.asMap().clear();
user.merge();
return "redirect:/account";
}
Пользовательский объект обновляется идеально, но проблема в том, что он перезаписывает поле пароля нулем, поскольку он не предоставляется в качестве входных данных в форме, и, таким образом, устанавливается равным нулю в объекте пользователя, передаваемом обработчику запроса отправки формы. Проблема не проявляется с созданными Roo скаффолдингами, потому что они предоставляют входные данные формы для всех столбцов. Так что я могу добавить это как скрытое поле, но это не очень хорошая идея. И я чувствую, что есть гораздо лучший способ сделать это...
TL; DR Как я могу обновить только атрибуты сущности, представленные в форме, не перезаписывая другие атрибуты?
Другими словами, как я могу заставить Spring/JPA генерировать SQL
UPDATE user SET firstname=?, lastname=?, address=?
вместо
UPDATE user SET firstname=?, lastname=?, address=?, password=?
Примеры кода были бы фантастическими, так как я новичок во всем этом:)
Спасибо!
ОБНОВЛЕНИЕ: я смог заставить это работать, используя предложение yglodt, добавив следующий метод к моей модели User:
@Transactional
public void mergeWithExistingAndUpdate() {
final User existingUser = User.findUser(this.getId());
existingUser.setFirstName(this.getFirstName());
existingUser.setLastName(this.getLastName());
existingUser.setAddress(this.getAddress());
existingUser.flush();
}
и вызывая это из моего действия контроллера вместо user.merge():
user.mergeWithExistingAndUpdate();
3 ответа
Я обычно решаю это на уровне обслуживания.
Вы можете прочитать сущность, которую хотите обновить, из БД и перезаписать атрибуты, которые вы получаете из своей формы.
Таким образом, вы можете изменить только те атрибуты, которые вам нужны.
Пример кода:
@Service
@Transactional
public class UserService {
@Resource(name = "sessionFactory")
private SessionFactory sessionFactory;
public void mergeWithExistingAndUpdate(final Person personFromPost) {
Session session = sessionFactory.getCurrentSession();
Person existingPerson = (Person) session.get(Person.class, personFromPost.getId());
// set here explicitly what must/can be overwritten by the html form POST
existingPerson.setName(personFromPost.getName());
existingPerson.setEmail(personFromPost.getEmail());
existingPerson.setDateModified(new Date());
existingPerson.setUserModified(Utils.getCurrentUser());
session.update(existingPerson);
}
}
РЕДАКТИРОВАТЬ 1
Существует на самом деле Spring-способ решить эту проблему, используя @SessionAttributes
см. этот ответ:
/questions/35297205/spring-mvc-30-kak-svyazat-s-postoyannyim-obektom/35297218#35297218
Я еще не тестировал, но выглядит многообещающе.
РЕДАКТИРОВАТЬ 2
В конце концов я проверил это, и он работает как ожидалось.
Однако есть одна вещь, которая может заставить вас выстрелить в ногу:
Если вы открываете несколько вкладок с одной и той же формой, открытие последней вкладки перезаписывает sessionAttribute
других, и, при отправке, может потенциально испортить ваши данные. В этом сообщении блога есть решение: http://marty-java-dev.blogspot.com/2010/09/spring-3-session-level-model-attributes.html
Но в конце концов, если вы никогда не откроете несколько вкладок для редактирования, у вас все равно не возникнет проблем.
Эта статья подробно объясняет ваш вопрос, но я собираюсь подвести его итог и здесь.
Если вы не хотите обновлять определенный атрибут, вы можете пометить его updatable=false
:
@Column(name="CREATED_ON", updatable=false)
private Date createdOn;
После того, как вы загрузите объект и измените его, пока текущий Session
или же EntityManager
открыт, Hibernate может отслеживать изменения с помощью грязного механизма проверки. Затем во время флешаSQL
обновление будет выполнено.
Если вам не нравится, что все столбцы включены в UPDATE
Заявление, вы можете использовать динамическое обновление:
@Entity
@DynamicUpdate
public class Product {
//code omitted for brevity
}
Тогда только измененные столбцы будут включены в UPDATE
заявление.
Если ваш поставщик персистентности - Hibernate, используйте аннотацию, относящуюся к hibernate: @DynamicUpdate
на объекте:
Для обновления, должен ли этот объект использовать динамическое создание SQL, где только подготовленные операторы SQL ссылаются только на измененные столбцы?
Обратите внимание, что для повторного присоединения отсоединенных объектов это невозможно без включения функции выбора до обновления.