简体   繁体   English

如何在 Spring Data 中漂亮地更新 JPA 实体?

[英]How to beautifully update a JPA entity in Spring Data?

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 中更改他的名字 - 然后会有一些控制器操作,其中将有更新的 DTO 与旧 ID 和新名称。

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.在 Customer 类中为 id 创建一个设置器 - 从而允许设置 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)将 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?我解释的两种方法的缺点可能是什么?

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.更好,因为getOne(ID id) 只给你一个引用(代理)对象,而不是从数据库中获取它。 On this reference you can set what you want and on save() it will do just an SQL UPDATE statement like you expect it.在此参考中,您可以设置您想要的内容,而在save() ,它将只执行您期望的 SQL UPDATE 语句。 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.相比之下,当您像@Tanjim Rahmans 一样调用find() ,spring data JPA 将执行 SQL SELECT 以在您刚刚更新时从数据库中物理获取您不需要的实体。

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(即使以丢失更新的方式)。

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

If you need to work with DTOs rather than entities directly then you should retrieve the existing Customer 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

Simple JPA update..简单的 JPA 更新..

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

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;用户从服务器请求他的数据并在 UI 中获取它们;
  2. User corrects his data and sends it back to server with already present ID;用户更正他的数据并将其发送回服务器,并使用已经存在的 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...在服务器上,您通过用户获取具有更新数据的 DTO,通过 ID 在 DB 中找到它(否则抛出异常)并使用所有给定数据、外键等转换 DTO -> 实体...
  4. Then you just merge it, or if using Spring Data invoke save(), which in turn will merge it (see this thread );然后你只需合并它,或者如果使用 Spring Data 调用 save(),它又会合并它(请参阅此线程);

PS This operation will inevitably issue 2 queries: select and update. PS这个操作难免会发出2个查询:select和update。 Again, 2 queries, even if you wanna update a single field.同样,2 个查询,即使您想更新单个字段。 However, if you utilize Hibernate's proprietary @DynamicUpdate annotation on top of entity class, it will help you not to include into update statement all the fields, but only those that actually changed.但是,如果您在实体类之上使用 Hibernate 专有的@DynamicUpdate注释,它将帮助您不将所有字段包含在更新语句中,而只包含那些实际更改的字段。

PS If you do not wanna pay for first select statement and prefer to use Spring Data's @Modifying query, be prepared to lose L2C cache region related to modifiable entity; PS如果您不想为第一个 select 语句付费并且更喜欢使用 Spring Data 的@Modifying查询,请准备好丢失与可修改实体相关的 L2C 缓存区域; 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.本机更新查询的情况更糟(请参阅此线程),当然也准备手动编写这些查询,测试它们并在将来支持它们。

I have encountered this issue!我遇到过这个问题!
Luckily, I determine 2 ways and understand some things but the rest is not clear.幸运的是,我确定了2 种方式并了解了一些事情,但其余的尚不清楚。
Hope someone discuss or support if you know.希望知道的人讨论或支持。

  1. Use RepositoryExtendJPA.save(entity) .使用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;此块代码更新了 id = 0 的记录的新名称;
  2. Use @Transactional from javax or spring framework.使用 javax 或 spring 框架中的@Transactional
    Let put @Transactional upon your class or specified function, both are ok.让@Transactional 放在你的类或指定的函数上,两者都可以。
    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.因此,您在实体中修改的所有内容都将更新到数据库中。

There is a method in JpaRepository JpaRepository 中有一个方法

getOne

It is deprecated at the moment in favor of目前它已被弃用,取而代之的是

getById

So correct approach would be所以正确的方法是

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

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

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