Частично актуализиране на данни с пролетни данни

1. Въведение

Запазването на CrudRespository # на Spring Data е несъмнено просто, но една функция може да бъде недостатък: Актуализира всяка колона в таблицата. Такава е семантиката на U в CRUD, но какво, ако вместо това искаме да направим PATCH?

В този урок ще разгледаме техниките и подходите за извършване на частична вместо пълна актуализация.

2. Проблем

Както беше посочено по-горе, save () ще замени всеки съвпадащ обект с предоставените данни, което означава, че не можем да предоставим частични данни. Това може да стане неудобно, особено за по-големи обекти с много полета.

Ако разгледаме ORM, съществуват някои кръпки, като:

  • @DynamicUpdat e анотация на Hibernate , която динамично презаписва заявката за актуализация
  • Анотацията на JPA @Column , тъй като можем да забраним актуализации на конкретни колони, използвайки параметъра за актуализиране

Но по-долу ще подходим към този проблем с конкретно намерение: Нашата цел е да подготвим нашите обекти за метода на запазване , без да разчитаме на ORM.

3. Нашият случай

Първо, нека изградим обект на клиента :

@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); }

И накрая, ние подготвяме CustomerService :

@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 модел.

Да предположим, че в нашия обект има повече от сто телефонни полета. Написването на метод, който излива данните от 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 , който ни позволява да дефинираме правило за пропускане на нулеви стойности по време на процеса на картографиране. Нека го добавим към нашия интерфейс на метода updateCustomerFromDto :

@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)

С това можем да заредим съхранени обекти и да ги обединим с DTO, преди да извикаме метода за запазване на JPA : всъщност ще актуализираме само модифицираните стойности.

И така, нека добавим метод към нашата услуга, който ще извика нашия картограф:

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

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

4.2. По-прости обекти

Накрая, имайте предвид, че можем да подходим към този проблем от фазата на проектиране на приложението.

От съществено значение е да определим нашите обекти да бъдат възможно най-малки.

Нека да разгледаме нашата организация на клиента . Какво ще стане, ако го структурираме малко и извлечем всички телефонни полета в обектите на ContactPhone и бъдем във връзка един към много?

@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; }

Кодът е чист и по-важното е, че постигнахме нещо. Сега можем да актуализираме нашите обекти, без да се налага да извличаме и попълваме всички телефонни данни.

Обработката на малки и ограничени обекти ни позволява да актуализираме само необходимите полета.

Единственото неудобство на този подход е, че ние трябва да проектираме нашите обекти със съзнание, без да попадаме в капана на свръхинженеринга.

5. Персонализирана заявка

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

In fact, JPA defines two annotations, @Modifying and @Query, which allow us to write our update statement explicitly.

We can now tell our application how to behave during an update, without leaving the burden on the ORM.

Let's add our custom update method in the repository:

@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); 

Now, we can rewrite our update method:

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

Now we are able to perform a partial update: with just a few lines of code and without altering our entities we've achieved our goal.

The disadvantage of this technique is that we'll have to define a method for each possible partial update of our object.

6. Conclusion

The partial data update is quite a fundamental operation; while we can have our ORM to handle it, sometimes it could be profitable to get full control over it.

Както видяхме, можем да заредим предварително данните си и след това да ги актуализираме или да дефинираме потребителските си изявления, но не забравяйте да сте наясно с недостатъците, които тези подходи предполагат и как да ги преодолеем.

Както обикновено, изходният код за тази статия е достъпен в GitHub.