Рубрики
Без рубрики

Частичное обновление данных с помощью весенних данных

Изучите методы и подходы к выполнению частичного, а не полного обновления

Автор оригинала: baeldung.

1. введение

Функция Crudrepository#save Spring Data, несомненно, проста, но одна особенность может быть недостатком: она обновляет каждый столбец в таблице. Такова семантика U в CRUD, но что, если вместо этого мы хотим сделать ПАТЧ?

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

2. Проблема

Как указывалось ранее, save() перезапишет любую сопоставленную сущность с предоставленными данными, что означает, что мы не можем предоставить частичные данные. Это может стать неудобным, особенно для больших объектов с большим количеством полей.

Если бы мы посмотрели на ORM, некоторые патчи существуют, например:

  • Аннотация Hibernate @DynamicUpdate , которая динамически перезаписывает запрос на обновление
  • Jpa @Column аннотация, так как мы можем запретить обновления определенных столбцов с помощью параметра updatable

Но в следующем мы подойдем к этой проблеме с конкретным намерением: Наша цель состоит в том, чтобы подготовить наши сущности для метода save , не полагаясь на ORM.

3. Наш Случай

Во-первых, давайте создадим объект Customer :

@Entity 
public class Customer {
    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO)
    public long id;
    public String name;
    public String phone;
}

Затем мы определяем простой репозиторий CRUD:

@Repository 
public interface CustomerRepository extends CrudRepository {
    Customer findById(long id);
}

Наконец, мы готовим Обслуживание клиентов :

@Service 
public class CustomerService {
    @Autowired 
    CustomerRepository repo;

    public void addCustomer(String name) {
        Customer c = new Customer();
        c.name = name;
        repo.save(c);
    }	
}

4. Загрузка и сохранение подхода

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

Хотя это просто и очевидно, это один из самых простых подходов, которые мы можем использовать.

Давайте добавим в наш сервис метод обновления контактных данных наших клиентов.

public void updateCustomerContacts(long id, String phone) {
    Customer myCustomer = repo.findById(id);
    myCustomer.phone = phone;
    repo.save(myCustomer);
}

Мы вызовем метод findById и получим соответствующий объект, затем продолжим, обновим необходимые поля и сохраним данные.

Этот базовый метод эффективен, когда количество полей для обновления относительно невелико, а наши сущности довольно просты.

Что произойдет с десятками полей для обновления?

4.1. Стратегия картографирования

Когда наши объекты имеют большое количество полей с различными уровнями доступа , довольно часто реализуется шаблон DTO .

Теперь предположим, что в нашем объекте более сотни полей phone . Написание метода, который передает данные из DTO в нашу сущность, как мы делали это раньше, может быть утомительным и довольно недостижимым.

Тем не менее, мы можем решить эту проблему, используя стратегию сопоставления, и, в частности, реализацию MapStruct.

Давайте создадим CustomerDTO :

public class CustomerDto {
    private long id;
    public String name;
    public String phone;
    //...
    private String phone99;
}

А также CustomerMapper :

@Mapper(componentModel = "spring")
public interface CustomerMapper {
    void updateCustomerFromDto(CustomerDto dto, @MappingTarget Customer entity);
}

Аннотация @MappingTarget позволяет нам обновить существующий объект, избавляя нас от необходимости писать много кода.

MapStruct имеет декоратор метода @BeanMapping , который позволяет нам определить правило для пропуска значений null во время процесса сопоставления. Давайте добавим его в наш клиент обновления из D в интерфейс метода:

@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)

С помощью этого мы можем загрузить сохраненные сущности и объединить их с ОСТАНОВКОЙ перед вызовом JPA сохранить метод: на самом деле мы будем обновлять только измененные значения.

Итак, давайте добавим в наш сервис метод, который вызовет наш картограф:

public void updateCustomer(CustomerDto dto) {
    Customer myCustomer = repo.findById(dto.id);
    mapper.updateCustomerFromDto(dto, myCustomer);
    repo.save(myCustomer);
}

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

4.2. Более простые сущности

Наконец, имейте в виду, что мы можем подойти к этой проблеме с этапа проектирования приложения.

Очень важно определить наши сущности, чтобы они были как можно меньше.

Давайте взглянем на наш Клиент объект. Что, если мы немного структурируем его и извлекем все поля phone в Contact Phone сущности и будем находиться в отношениях один ко многим ?

@Entity public class CustomerStructured {
    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO)
    public Long id;
    public String name;
    @OneToMany(fetch = FetchType.EAGER, targetEntity=ContactPhone.class, mappedBy="customerId")    
    private List contactPhones;
}

Код чистый, и, что более важно, мы кое-чего достигли. Теперь мы можем обновлять наши сущности без необходимости извлекать и заполнять все данные phone .

Обработка небольших и ограниченных объектов позволяет нам обновлять только необходимые поля.

Единственное неудобство этого подхода заключается в том, что мы должны проектировать наши сущности с осознанием, не попадая в ловушку чрезмерного инжиниринга.

5. Пользовательский запрос

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

Фактически, JPA определяет две аннотации, @Modifying и @Query , которые позволяют нам явно написать наш оператор обновления.

Теперь мы можем рассказать нашему приложению, как вести себя во время обновления, не оставляя бремени на ФОРМЕ.

Давайте добавим наш пользовательский метод обновления в репозиторий:

@Modifying
@Query("update Customer u set u.phone = :phone where u.id = :id")
void updatePhone(@Param(value = "id") long id, @Param(value = "phone") String phone);

Теперь мы можем переписать наш метод обновления:

public void updateCustomerContacts(long id, String phone) {
    repo.updatePhone(id, phone);
}

Теперь мы можем выполнить частичное обновление: всего за несколько строк кода и без изменения наших сущностей мы достигли нашей цели.

Недостатком этого метода является то, что нам придется определять метод для каждого возможного частичного обновления нашего объекта.

6. Заключение

Частичное обновление данных-довольно фундаментальная операция; в то время как мы можем использовать наш ORM для ее обработки, иногда было бы выгодно получить полный контроль над ней.

Как мы уже видели, мы можем предварительно загрузить ваши данные, а затем обновить их или определить наши пользовательские инструкции, но не забывайте знать о недостатках, которые подразумевают эти подходы, и о том, как их преодолеть.

Как обычно, исходный код этой статьи доступен на GitHub .