Spring Boot: как эффективно обновить объект? - PullRequest
0 голосов
/ 07 апреля 2020

Привет всем, я новичок в мире 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);
    }

Вместо того, чтобы использовать каждый раз, когда установить и получить, я хочу использовать конвертер.

Ответы [ 2 ]

2 голосов
/ 07 апреля 2020

Подход к передаче идентификатора объекта в качестве переменной пути при обновлении не совсем верен. Подумайте об этом: у вас есть @RequestBody, почему вы тоже не включили идентификатор в это тело? Почему вы хотите указать переменную пути для него?

Теперь, если у вас есть полный Customer с его идентификатором из тела, вам не нужно делать какие-либо вызовы в ваш репозиторий, потому что hibernate добавляет его в состояние persistent уже на основе его идентификатора и простой

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

должен работать.

Q: Что такое постоянное состояние?

A: Постоянный объект связан со строкой таблицы базы данных и управляется текущим запущенным контекстом постоянства. (customerRepository.findById() просто запрашивает у БД, существует ли сущность с указанным идентификатором и добавляет ее в постоянное состояние. Hibernate управляет всем этим процессом, если у вас есть поле @Id с комментариями и заполнено, другими словами:

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

- это ПОЧТИ то же самое, что и:

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

)

СОВЕТЫ: ​​ В любом случае, вы не должны иметь (если вы не знали) модель на уровне контроллера. Почему? Допустим, ваша модель Customer может иметь несколько разрешений. Одна из возможных структур может выглядеть следующим образом:

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

   @OneToMany(mappedBy="customer",--other configs here--)
   private List<Permission> permissions;
}

и

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

    private String name;

    private String creationDate;

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

Вы можете видеть, что у вас есть перекрестная ссылка между Customer и Permission сущностью, которая в конечном итоге приведет к исключение переполнения стека (если вы этого не понимаете, вы можете подумать о рекурсивной функции, которая не имеет условия для остановки и вызывается снова и снова => переполнение стека. То же самое происходит и здесь).

Что вы можете сделать? Создание так называемого DTO класса, который вы хотите, чтобы клиент получал вместо модели. Как вы можете создать этот DTO? Подумайте, что пользователь НУЖЕН знать.

1) Является ли "creationDate" из Permission обязательным полем для пользователя? Не совсем.

2) Является ли "id" из Permission обязательным для пользователя полем? В некоторых случаях да, в других - нет.

Возможный CustomerDTO может выглядеть следующим образом:

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

, и вы можете заметить, что я использую List<String> вместо List<Permission> для разрешений клиента, которые фактически являются именами разрешений.

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 -> permission.getName())
               .collect(Collectors.toList());
    );

    // easy-way => using a ModelMapper
    customerDTO = modelMapper.map(customer,CustomerDTO.class);

    return customerDTO;
}
0 голосов
/ 07 апреля 2020

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

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

Используйте merge для отображения данных

    @Override
    public Customer update(String id, Customer newCustomer) {
        Customer customer = customerRepository.findById(id).get();
        merge(newCustomer, customer);
        return customerRepository.save(customer);
    }

Добавьте зависимость в pom. xml для сопоставителя модели

        <dependency>
            <groupId>org.modelmapper</groupId>
            <artifactId>modelmapper</artifactId>
            <version>2.3.4</version>
        </dependency>
...