Java 如何在 Spring Data 中精美地更新 JPA 实体?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39741102/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 21:25:41  来源:igfitidea点击:

How to beautifully update a JPA entity in Spring Data?

javaspringjpaspring-dataspring-data-jpa

提问by Lukas Makor

So I have looked at various tutorials about JPA with Spring Data and this has been done different on many occasions and I am no quite sure what the correct approach is.

因此,我查看了有关使用 Spring Data 的 JPA 的各种教程,并且这在很多情况下都不同,我不太确定正确的方法是什么。

Assume there is the follwing entity:

假设有以下实体:

package stackoverflowTest.dao;

import javax.persistence.*;

@Entity
@Table(name = "customers")
public class Customer {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private long id;

@Column(name = "name")
private String name;

public Customer(String name) {
    this.name = name;
}

public Customer() {
}

public long getId() {
    return id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}
}

We also have a DTO which is retrieved in the service layer and then handed to the controller/client side.

我们还有一个 DTO,它在服务层中检索,然后交给控制器/客户端。

package stackoverflowTest.dto;

public class CustomerDto {

private long id;
private String name;

public CustomerDto(long id, String name) {
    this.id = id;
    this.name = name;
}

public long getId() {
    return id;
}

public void setId(long id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}
}

So now assume the Customer wants to change his name in the webui - then there will be some controller action, where there will be the updated DTO with the old ID and the new name.

所以现在假设客户想要在 webui 中更改他的名字 - 然后会有一些控制器操作,其中会有带有旧 ID 和新名称的更新 DTO。

Now I have to save this updated DTO to the database.

现在我必须将这个更新的 DTO 保存到数据库中。

Unluckily currently there is no way to update an existing customer (except than deleting the entry in the DB and creating a new Cusomter with a new auto-generated id)

不幸的是,目前无法更新现有客户(除了删除数据库中的条目并使用新的自动生成的 id 创建新的客户)

However as this is not feasible (especially considering such an entity could have hundreds of relations potentially) - so there come 2 straight forward solutions to my mind:

然而,由于这是不可行的(特别是考虑到这样的实体可能有数百个关系) - 所以我想到了两个直接的解决方案:

  1. make a setter for the id in the Customer class - and thus allow setting of the id and then save the Customer object via the corresponding repository.
  1. 为 Customer 类中的 id 创建一个 setter - 从而允许设置 id,然后通过相应的存储库保存 Customer 对象。

or

或者

  1. add the id field to the constructor and whenever you want to update a customer you always create a new object with the old id, but the new values for the other fields (in this case only the name)
  1. 将 id 字段添加到构造函数中,每当您想要更新客户时,您总是使用旧 id 创建一个新对象,但其他字段的新值(在这种情况下只有名称)

So my question is wether there is a general rule how to do this? Any maybe what the drawbacks of the 2 methods I explained are?

所以我的问题是是否有一般规则如何做到这一点?我解释的两种方法的缺点是什么?

采纳答案by Robert Niestroj

Even better then @Tanjim Rahman answer you can using Spring Data JPA use the method T getOne(ID id)

更好的是@Tanjim Rahman 回答您可以使用 Spring Data JPA 使用该方法 T getOne(ID id)

Customer customerToUpdate = customerRepository.getOne(id);
customerToUpdate.setName(customerDto.getName);
customerRepository.save(customerToUpdate);

Is's better because getOne(ID id)gets you only a reference(proxy) object and does not fetch it from the DB. On this reference you can set what you want and on save()it will do just an SQL UPDATE statement like you expect it. In comparsion when you call find()like in @Tanjim Rahmans answer spring data JPA will do an SQL SELECT to physically fetch the entity from the DB, which you dont need, when you are just updating.

更好,因为getOne(ID id)只为您提供一个引用(代理)对象,而不是从数据库中获取它。在这个参考资料中,您可以设置您想要的内容,并且save()它会像您期望的那样执行 SQL UPDATE 语句。find()相比之下,当您像@Tanjim Rahmans 那样调用时,spring data JPA 将执行 SQL SELECT 以从数据库物理获取实体,当您只是更新时,您不需要该实体。

回答by Amer Qarabsa

This is more an object initialzation question more than a jpa question, both methods work and you can have both of them at the same time , usually if the data member value is ready before the instantiation you use the constructor parameters, if this value could be updated after the instantiation you should have a setter.

这更像是一个对象初始化问题而不是 jpa 问题,这两种方法都可以工作,并且您可以同时拥有它们,通常如果数据成员值在您使用构造函数参数的实例化之前准备好,如果这个值可以是实例化后更新你应该有一个setter。

回答by Alan Hay

If you need to work with DTOs rather than entities directly then you should retrieve the existingCustomer instance and map the updated fields from the DTO to that.

如果您需要直接使用 DTO 而不是实体,那么您应该检索现有的Customer 实例并将更新的字段从 DTO 映射到该实例。

Customer entity = //load from DB
//map fields from DTO to entity

回答by Esty

Simple JPA update..

简单的 JPA 更新..

Customer customer = em.find(id, Customer.class); //Consider em as JPA EntityManager
customer.setName(customerDto.getName);
em.merge(customer);

回答by gorefest

In Spring Data you simply define an update query if you have the ID

在 Spring Data 中,如果您有 ID,您只需定义一个更新查询

  @Repository
  public interface CustomerRepository extends JpaRepository<Customer , Long> {

     @Query("update Customer c set c.name = :name WHERE c.id = :customerId")
     void setCustomerName(@Param("customerId") Long id, @Param("name") String name);

  }

Some solutions claim to use Spring data and do JPA oldschool (even in a manner with lost updates) instead.

一些解决方案声称使用 Spring 数据并改为执行 JPA oldschool(即使以丢失更新的方式)。

回答by Andreas Gelever

So now assume the Customer wants to change his name in the webui - then there will be some controller action, where there will be the updated DTO with the old ID and the new name.

所以现在假设客户想要在 webui 中更改他的名字 - 然后会有一些控制器操作,其中会有带有旧 ID 和新名称的更新 DTO。

Normally, you have the following workflow:

通常,您有以下工作流程:

  1. User requests his data from server and obtains them in UI;
  2. User corrects his data and sends it back to server with already present ID;
  3. On server you obtain DTO with updated data by user, find it in DB by ID (otherwise throw exception) and transform DTO -> Entity with all given data, foreign keys, etc...
  4. Then you just mergeit, or if using Spring Data invoke save(), which in turn will mergeit (see this thread);
  1. 用户从服务器请求他的数据并在 UI 中获取它们;
  2. 用户更正他的数据并将其发送回服务器,并使用已经存在的 ID;
  3. 在服务器上,您通过用户获取具有更新数据的 DTO,通过 ID 在 DB 中找到它(否则抛出异常)并使用所有给定数据、外键等转换 DTO -> 实体...
  4. 然后你只需合并它,或者如果使用 Spring Data 调用 save(),它又会合并它(请参阅此线程);

P.S.This operation will inevitably issue 2 queries: select and update. Again, 2 queries, even if you wanna update a single field. However, if you utilize Hibernate's proprietary @DynamicUpdateannotation on top of entity class, it will help you not to include into update statement all the fields, but only those that actually changed.

PS这个操作难免会发出2个查询:select和update。同样,2 个查询,即使您想更新单个字段。但是,如果您在实体类之上使用 Hibernate 专有的@DynamicUpdate注释,它将帮助您不将所有字段包含在更新语句中,而只包含那些实际更改的字段。

P.S.If you do not wanna pay for first select statement and prefer to use Spring Data's @Modifyingquery, be prepared to lose L2C cache region related to modifiable entity; even worse situation with native update queries (see this thread) and also of course be prepared to write those queries manually, test them and support them in the future.

PS如果您不想为第一个 select 语句付费并且更喜欢使用 Spring Data 的@Modifying查询,请准备好丢失与可修改实体相关的 L2C 缓存区域;本机更新查询的情况更糟(请参阅此线程),当然也准备手动编写这些查询,测试它们并在将来支持它们。

回答by Neo_

I have encountered this issue!
Luckily, I determine 2 waysand understand some things but the rest is not clear.
Hope someone discuss or support if you know.

我遇到过这个问题!
幸运的是,我确定了2 种方式并了解了一些事情,但其余的尚不清楚。
希望知道的人讨论或支持。

  1. Use RepositoryExtendJPA.save(entity).
    Example:
    List<Person> person = this.PersonRepository.findById(0) person.setName("Neo"); This.PersonReository.save(person);
    this block code updated new name for record which has id = 0;
  2. Use @Transactionalfrom javax or spring framework.
    Let put @Transactional upon your class or specified function, both are ok.
    I read at somewhere that this annotation do a "commit" action at the end your function flow. So, every things you modified at entity would be updated to database.
  1. 使用RepositoryExtendJPA.save(entity)
    示例:
    List<Person> person = this.PersonRepository.findById(0) person.setName("Neo"); This.PersonReository.save(person);
    此块代码更新了 id = 0 的记录的新名称;
  2. 使用javax 或 spring 框架中的@Transactional
    让@Transactional 放在你的类或指定的函数上,两者都可以。
    我在某处读到此注释在您的函数流结束时执行“提交”操作。因此,您在实体中修改的所有内容都将更新到数据库中。