Подтвердить что ты не робот

Возможно ли обновить только подмножество атрибутов для объекта с использованием 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 для визуализации формы, снова следуя соглашениям Roo:

<?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";
}

Пользовательский объект обновляется отлично, но проблема в том, что он перезаписывает поле пароля с нулевым значением, поскольку он не предоставляется в виде ввода в форме и поэтому устанавливает значение null в объекте User, переданном обработчику запроса формы. Проблема не возникает с создаваемыми Roo строительными лесами, поскольку они предоставляют входные данные формы для всех столбцов. Поэтому я мог бы добавить это как скрытое поле, но это не похоже на хорошую идею. И я чувствую, что есть намного лучший способ сделать это...


TL; DR Как я могу обновить только атрибуты сущности, предоставленные в форме, без перезаписывания других атрибутов?

Другими словами, как я могу заставить Spring/JPA генерировать SQL

UPDATE user SET firstname=?, lastname=?, address=?

вместо

UPDATE user SET firstname=?, lastname=?, address=?, password=?

Примеры кода были бы фантастическими, так как я новичок во всем этом:)


Спасибо!


UPDATE: Мне удалось заставить его работать с помощью предложения 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();
4b9b3361

Ответ 1

Я обычно решаю это на сервисном уровне.

Вы можете прочитать объект, который хотите обновить из БД, и перезаписать атрибуты, которые вы получаете из своей формы.

Таким образом вы изменяете только те атрибуты, которые вы хотите.

Пример кода:

@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 -way для решения этой проблемы, используя @SessionAttributes, см. этот anwer:

fooobar.com/questions/274873/...

Я еще не тестировал его, но он выглядит многообещающим.

РЕДАКТИРОВАТЬ 2

В конце концов я протестировал его, и он работает как ожидалось.

Однако есть одна вещь, которая может заставить вас стрелять в ногу:

Если вы открываете несколько вкладок с одинаковой формой, открытие последней вкладки перезаписывает sessionAttribute других, а при отправке может потенциально испортить ваши данные. В этом блоге есть решение: http://marty-java-dev.blogspot.com/2010/09/spring-3-session-level-model-attributes.html

Но в конце, если вы никогда не открываете несколько вкладок для редактирования, у вас все равно не будет проблем.

Ответ 2

В этой статье подробно объясняется ваш вопрос, но я также подведу его здесь.

Если вы никогда не хотите обновлять определенный атрибут, вы можете пометить его с помощью 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 будут включены только измененные столбцы.

Ответ 3

Если ваш поставщик непрерывности является Hibernate, используйте аннотацию, зависящую от гибернации: @DynamicUpdate для объекта:

Для обновления, должен ли этот объект использовать динамическое построение sql, где только ссылки на столбцы ссылаются в подготовленном SQL-выражении?

Обратите внимание, что для повторного присоединения отдельных объектов это невозможно без включенного выбора-до-обновления.