简体   繁体   English

是否可以使用带有 JPA 的 Spring MVC 仅更新实体上的属性子集?

[英]Is it possible to update only a subset of attributes on an entity using Spring MVC with JPA?

I'm working with Spring Roo, using Spring MVC and JPA for persistence with a MySQL database.我正在使用 Spring Roo,使用 Spring MVC 和 JPA 来持久化 MySQL 数据库。 I'm very new to Spring MVC and Java in general but have worked with CakePHP and Rails.总的来说,我对 Spring MVC 和 Java 很陌生,但已经使用过 CakePHP 和 Rails。

I have a User entity that contains personal details in addition to a password.我有一个User实体,除了密码之外还包含个人详细信息。 Something like this (excluding a lot of Roo-generated functionality in additional .aj files):像这样的东西(不包括附加 .aj 文件中的许多 Roo 生成的功能):

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
    ...
}

Then I have an edit action in my User controller that I created following conventions from Roo's auto-generated scaffolding:然后我在我的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";
}

And a JSPX view to render the form, again following Roo's conventions:还有一个 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>

I do not want the form to update the password, just the provided fields (first name, last name, and address).希望表单更新密码,只更新提供的字段(名字、姓氏和地址)。

The update action, again following Roo convention:更新操作,再次遵循 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";
}

The user object is updated perfectly, but the problem is that it overwrites the password field with null because it's not provided as an input in the form, and thus set to null in the User object passed to the form submit request handler.用户对象被完美更新,但问题是它用 null 覆盖了密码字段,因为它没有作为表单中的输入提供,因此在传递给表单提交请求处理程序的用户对象中设置为 null。 The problem doesn't show up with the Roo generated scaffolding because they provide form inputs for all of the columns. Roo 生成的脚手架不会出现问题,因为它们为所有列提供表单输入。 So I could add it as a hidden field but that doesn't sound like a good idea.所以我可以将它添加为隐藏字段,但这听起来不是一个好主意。 And I get the feeling there's a much better way to do it...我觉得有更好的方法来做到这一点......


TL;DR How can I update only the entity attributes provided in a form without overwriting the other attributes? TL;DR 如何仅更新表单中提供的实体属性而不覆盖其他属性?

In other words, how can I make Spring/JPA generate the SQL换句话说,我怎样才能让 Spring/JPA 生成 SQL

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

instead of代替

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

Code samples would be fantastic since I'm new to all of this :)代码示例会很棒,因为我是所有这些的新手:)


Thank you!谢谢!


UPDATE: I was able to make it work using yglodt's suggestion, adding the following method to my User model:更新:我能够使用 yglodt 的建议使其工作,将以下方法添加到我的用户模型中:

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

and calling that from my controller action instead of user.merge():并从我的控制器操作而不是 user.merge() 调用它:

user.mergeWithExistingAndUpdate();

I usually solve this in the service layer.我通常在服务层解决这个问题。

You can read the entity you want to update from the DB, and overwrite the attributes which you are getting from your form.您可以从数据库中读取要更新的实体,并覆盖从表单中获取的属性。

This way you change only the attributes you want.这样您就可以只更改您想要的属性。

Code example:代码示例:

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

}

EDIT 1编辑 1

There is in fact a Spring-way to solve this issue, using @SessionAttributes , see this anwer:实际上有一个 Spring-way 来解决这个问题,使用@SessionAttributes ,看这个 anwer:

https://stackoverflow.com/a/3675919/272180 https://stackoverflow.com/a/3675919/272180

I did not yet test it, but it looks promising.我还没有测试它,但它看起来很有希望。

EDIT 2编辑 2

Eventually I tested it and it works as expected.最终我测试了它,它按预期工作。

There is one thing however which can make you shoot in your foot:然而,有一件事可以让你用脚射门:

If you open several tabs with the same form, the opening of the last tab overwrites the sessionAttribute of the others, and, on submit, can potentially corrupt your data.如果您使用相同的表单打开多个选项卡,最后一个选项卡的打开会覆盖其他选项卡的sessionAttribute ,并且在提交时可能会损坏您的数据。 There is a solution in this blog post: http://marty-java-dev.blogspot.com/2010/09/spring-3-session-level-model-attributes.html这篇博文中有一个解决方案: http : //marty-java-dev.blogspot.com/2010/09/spring-3-session-level-model-attributes.html

But at the end, if you never open multiple tabs for editing, you will not have a problem anyway.但最后,如果您从不打开多个选项卡进行编辑,无论如何您都不会遇到问题。

If you never want to update a particular attribute, you can mark it with updatable=false :如果您永远不想更新特定属性,您可以使用updatable=false标记它:

@Column(name="CREATED_ON", updatable=false)
private Date createdOn;

Once you load an entity and you modify it, as long as the current Session or EntityManager is open, Hibernate can track changes through the dirty checking mechanism.一旦你加载了一个实体并修改了它,只要当前的SessionEntityManager是打开的,Hibernate 就可以通过脏检查机制来跟踪变化。 Then, during flush , an SQL UPDATE will be executed.然后,在flush期间,将执行 SQL UPDATE。

If you don't like that all columns are included in the UPDATE statement, you can use dynamic update:如果您不喜欢所有列都包含在UPDATE语句中,您可以使用动态更新:

@Entity
@DynamicUpdate
public class Product {
   //code omitted for brevity
}

Then, only the modified columns will be included in the UPDATE statement.然后,只有修改过的列将包含在UPDATE语句中。

If your persistence provider is Hibernate, use the hibernate-specific annotation: @DynamicUpdate on the entity:如果您的持久性提供程序是 Hibernate,请在实体上使用特定于 Hibernate 的注释: @DynamicUpdate

For updating, should this entity use dynamic sql generation where only changed columns get referenced in the prepared sql statement?对于更新,该实体是否应该使用动态 sql 生成,其中在准备好的 sql 语句中只引用更改的列?

Note, for re-attachment of detached entities this is not possible without select-before-update being enabled.请注意,对于分离实体的重新附加,如果不启用更新前选择,这是不可能的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM