Spring Boot: 如何高效地更新对象?

huangapple go评论71阅读模式
英文:

Spring Boot : how to update object efficently?

问题

大家好,我是新来的Spring世界。实际上我想知道怎样使用转换器来更新对象,而不是使用set和get逐个更新每个元素。目前在我的控制器中有:

@PostMapping("/edit/{userId}")
public Customer updateCustomer(@RequestBody Customer newCustomer, @PathVariable final String userId) {
    return customerService.update(userId, newCustomer);
}

这是我更新顾客对象的方式:

@Override
public Customer update(String id, Customer newCustomer) {
    Customer customer = customerRepository.findById(id).get();
    
    customer.setFirstName(newCustomer.getFirstName());
    customer.setLastName(newCustomer.getLastName());
    customer.setEmail(newCustomer.getEmail());
    
    return customerRepository.save(customer);
}

我想使用一个转换器来代替每次的set和get操作。

英文:

Hello everyone I'm new to Spring world. Actually I want to know how can we use converter to update object instead of updating each element one by one using set and get. Right now in my controller I've :

    @PostMapping("/edit/{userId}")
    public Customer updateCustomer(@RequestBody Customer newCustomer, @PathVariable final String userId)
    {
        return customerService.update(userId, newCustomer);
    }

and this is how I'm updating the customer object :

    @Override
    public Customer update(String id, Customer newCustomer) {
    	Customer customer = customerRepository.findById(id).get();
    	
    	customer.setFirstName(newCustomer.getFirstName());
        customer.setLastName(newCustomer.getLastName());
        customer.setEmail(newCustomer.getEmail());
        
        return customerRepository.save(customer);
    }

Instead of using each time set and get, I want to use a converter.

答案1

得分: 5

通过在更新实体时将实体的 ID 作为路径变量传递的方法并不是很合适。思考一下:既然你有一个 @RequestBody,为什么不在这个主体中也包含 ID 呢?为什么要为它指定一个路径变量?

现在,如果你从主体中获取了带有 ID 的完整 Customer,你就不必对存储库进行任何调用,因为 Hibernate 已经根据其 ID 将其添加到了“持久”状态,基于其 ID 和简单的

public Customer update(Customer newCustomer) {
      return customerRepository.save(newCustomer);
}

就可以工作。

Q: 什么是持久状态?

A: 持久状态的实体已与数据库表行关联,并由当前运行的持久性上下文进行管理。( customerRepository.findById() 只是询问数据库是否存在具有指定 ID 的实体,并将其添加到持久状态。如果你有一个带有 @Id 注解的字段并且填充了它,Hibernate 将管理所有这些过程。换句话说:

Customer customer = new Customer();
customer.setId(1);

几乎与:

Customer customer = customerRepository.findById(1).get();

是一样的。)

提示: 不管怎样,你不应该在控制器层中拥有一个模型(如果你不知道的话)。为什么?假设你的 Customer 模型可以有多个权限。一个可能的结构如下:

@Entity
public class Customer{
   //这里是私有字段;

   @OneToMany(mappedBy="customer",--其他配置在这里--)
   private List<Permission> permissions;
}

@Entity
public class Permission{
    @Id
    private Long id;

    private String name;

    private String creationDate;
    
    @ManyToOne(--在这里配置--)
    private Customer customer;
}

你可以看到 CustomerPermission 实体之间存在交叉引用,这最终会导致堆栈溢出异常(如果你不理解这一点,可以想象一个递归函数,它没有停止的条件,不断地被调用,导致堆栈溢出。这里发生的情况与之类似)。

你可以做什么?创建一个所谓的 DTO 类,你希望客户端接收它,而不是一个模型。如何创建这个 DTO?考虑一下用户需要知道什么。

  1. Permission 的“creationDate”对于用户来说是必需的字段吗?不完全是。

  2. Permission 的“id”对于用户来说是必需的字段吗?在某些情况下是,而在其他情况下则不是。

一个可能的 CustomerDTO 可以如下所示:

public class CustomerDTO
{
   private String firstName;
   private String lastName;
   private List<String> permissions;
}

你可以注意到,我使用了一个 List<String>,而不是 List<Permission>,用于客户的权限,实际上是权限的名称。

public CustomerDTO convertModelToDto(Customer customer)
{
    //较复杂的方式
    CustomerDTO customerDTO = new CustomerDTO();
    customerDTO.setFirstName(customer.getFirstName());
    customerDTO.setLastName(customer.getLastName());
    customerDTO.setPermissions(
       customer.getPermissions()
               .stream()
               .map(permission -> permission.getName())
               .collect(Collectors.toList());
    );
    
    // 简单方式 => 使用 ModelMapper
    customerDTO = modelMapper.map(customer, CustomerDTO.class);
    
    return customerDTO;
}
英文:

The approach of passing the entity's id as a path variable when you're updating it isn't really right. Think about this: you have a @RequestBody, why don't you include the id inside this body too? Why do you want to specify a path variable for it?

Now, if you have the full Customer with its id from the body, you don't have to make any calls to your repository because hibernate adds it to a persistent state already based on its id and a simple

public Customer update(Customer newCustomer) {
      return customerRepository.save(newCustomer);
}

should work.

Q: What is a persistent state?

A: A persistent entity has been associated with a database table row and it’s being managed by the current running Persistence Context. ( customerRepository.findById() is just asking the DB if the entity with the specified id exists and add it to a persistent state. Hibernate manage all this process if you have an @Id annotated field and is filled, in other words:

   Customer customer = new Customer();
   customer.setId(1); 

is ALMOST the same thing as :

   Customer customer = customerRepository.findById(1).get();

)

TIPS: Anyway, you shouldn't have (if you didn't know) a model in the controller layer. Why? Let's say that your Customer model can have multiple permissions. One possible structure could look like this:

@Entity
public class Customer{
   //private fields here;

   @OneToMany(mappedBy=&quot;customer&quot;,--other configs here--)
   private List&lt;Permission&gt; permissions;
}

and

@Entity
public class Permission{
    @Id
    private Long id;

    private String name;

    private String creationDate;
    
    @ManyToOne(--configs here--)
    private Customer customer;
}

You can see that you have a cross reference between Customer and Permission entity which will eventually lead to a stack overflow exception (if you don't understand this, you can think about a recursive function which doesn't have a condition to stop and it's called over and over again => stack overflow. The same thing is happening here).

What can you do? Creating a so called DTO class that you want the client to receive instead of a model. How can you create this DTO? Think about what the user NEEDS to know.

  1. Is "creationDate" from Permission a necessary field for the user? Not really.

  2. Is "id" from Permission a necessary field for the user? In some cases yes, in others, not.

A possible CustomerDTO could look like this:

public class CustomerDTO
{
   private String firstName;
   private String lastName;
   private List&lt;String&gt; permissions;
}

and you can notice that I'm using a List&lt;String&gt; instead of List&lt;Permission&gt; for customer's permissions which are in fact the permissions' names.

public CustomerDTO convertModelToDto(Customer customer)
{
    //hard way
    CustomerDTO customerDTO = new CustomerDTO();
    customerDTO.setFirstName(customer.getFirstName());
    customerDTO.setLastName(customer.getLastName());
    customerDTO.setPermissions(
       customer.getPermissions()
               .stream()
               .map(permission -&gt; permission.getName())
               .collect(Collectors.toList());
    );
    
    // easy-way =&gt; using a ModelMapper
    customerDTO = modelMapper.map(customer,CustomerDTO.class);
    
    return customerDTO;
}

答案2

得分: 0

使用ModelMapper将一个模型映射到另一个模型。
首先定义一个函数,可以将源数据映射到目标模型。将其作为库在需要时使用。

  public static <T> void merge(T source, T target) {
    ModelMapper modelMapper = new ModelMapper();
    modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
    modelMapper.map(source, target);
  }

使用merge进行数据映射

Customer customer = customerRepository.findById(id).get();
merge(newCustomer, customer);
customerRepository.save(customer);

在pom.xml中添加ModelMapper的依赖

<dependency>
	<groupId>org.modelmapper</groupId>
	<artifactId>modelmapper</artifactId>
	<version>2.3.4</version>
</dependency>
英文:

Use ModelMapper to map one model into another.
First define a function that can map source data into the target model. Use this as a library to use whenever want.

  public static &lt;T&gt; void merge(T source, T target) {
    ModelMapper modelMapper = new ModelMapper();
    modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
    modelMapper.map(source, target);
  }

Use merge for mapping data

Customer customer = customerRepository.findById(id).get();
merge(newCustomer, customer);
customerRepository.save(customer);

Add dependency in pom.xml for model mapper

&lt;dependency&gt;
	&lt;groupId&gt;org.modelmapper&lt;/groupId&gt;
	&lt;artifactId&gt;modelmapper&lt;/artifactId&gt;
	&lt;version&gt;2.3.4&lt;/version&gt;
&lt;/dependency&gt;

huangapple
  • 本文由 发表于 2020年4月8日 01:39:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/61086060.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定