Kemas kini Data Separa dengan Data Musim Semi

1. Pengenalan

Penjimatan CrudRespository # Spring Data tidak diragukan lagi sederhana, tetapi satu ciri boleh menjadi kelemahan: Ia mengemas kini setiap lajur dalam jadual. Seperti semantik U dalam CRUD, tetapi bagaimana jika kita mahu melakukan PATCH sebagai gantinya?

Dalam tutorial ini, kita akan membahas teknik dan pendekatan untuk melakukan sebahagian dan bukannya kemas kini penuh.

2. Masalah

Seperti yang dinyatakan sebelumnya, save () akan menimpa mana-mana entiti yang sesuai dengan data yang diberikan, yang bermaksud bahawa kami tidak dapat memberikan sebahagian data. Itu boleh menjadi tidak selesa, terutamanya untuk objek yang lebih besar dengan banyak medan.

Sekiranya kita melihat ORM, terdapat beberapa tambalan, seperti:

  • Anotasi @DynamicUpdat dan Hibernate , yang secara dinamik menulis semula pertanyaan kemas kini
  • Anotasi @Column JPA , kerana kami tidak membenarkan kemas kini pada lajur tertentu menggunakan parameter yang dapat dikemas kini

Tetapi berikut ini, kami akan mendekati masalah ini dengan maksud tertentu: Tujuan kami adalah untuk mempersiapkan entiti kami untuk kaedah menyimpan tanpa bergantung pada ORM.

3. Kes Kita

Pertama, mari kita bina entiti Pelanggan :

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

Kemudian, kami menentukan repositori CRUD ringkas:

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

Akhirnya, kami menyediakan Perkhidmatan Pelanggan :

@Service public class CustomerService { @Autowired CustomerRepository repo; public void addCustomer(String name) { Customer c = new Customer(); c.name = name; repo.save(c); } }

4. Muat dan Simpan Pendekatan

Mari kita lihat dahulu pendekatan yang mungkin biasa: memuat entiti kami dari pangkalan data dan kemudian hanya mengemas kini bidang yang kami perlukan.

Walaupun ini mudah dan jelas, ini adalah pendekatan termudah yang dapat kita gunakan.

Mari tambahkan kaedah dalam perkhidmatan kami untuk mengemas kini data hubungan pelanggan kami.

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

Kami akan memanggil kaedah findById dan mengambil entiti yang sepadan, kemudian kami meneruskan dan mengemas kini bidang yang diperlukan dan meneruskan data.

Teknik asas ini cekap apabila bilangan bidang yang hendak dikemas kini agak kecil, dan entiti kami agak sederhana.

Apa yang akan berlaku dengan puluhan bidang untuk dikemas kini?

4.1. Strategi Pemetaan

Apabila objek kami mempunyai sebilangan besar medan dengan tahap akses yang berbeza, adalah biasa untuk menerapkan corak DTO.

Sekarang, anggaplah kita mempunyai lebih dari seratus bidang telefon dalam objek kita. Menulis kaedah yang mengalirkan data dari DTO ke entiti kami, seperti yang kami lakukan sebelumnya, boleh mengganggu, dan tidak dapat dikendalikan.

Walaupun begitu, kita dapat mengatasi masalah ini dengan menggunakan strategi pemetaan, dan khususnya dengan implementasi MapStruct .

Mari buat CustomerDto :

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

Dan juga CustomerMapper :

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

The @MappingTarget anotasi membolehkan kami mengemas kini objek yang sedia ada, menyelamatkan kita dari kesakitan menulis banyak kod.

MapStruct mempunyai penghias kaedah @BeanMapping , yang memungkinkan kita menentukan peraturan untuk melangkau nilai nol selama proses pemetaan. Mari tambahkannya ke antara muka kaedah updateCustomerFromDto kami :

@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)

Dengan ini, kita dapat memuat entiti yang tersimpan dan menggabungkannya dengan DTO sebelum memanggil kaedah simpan JPA : sebenarnya, kita hanya akan mengemas kini nilai yang diubah.

Oleh itu, mari tambah kaedah untuk perkhidmatan kami, yang akan memanggil pemeta kami:

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

Kelemahan pendekatan ini adalah bahawa kita tidak dapat meneruskan nilai nol ke pangkalan data semasa kemas kini.

4.2. Entiti yang lebih sederhana

Akhirnya, ingatlah bahawa kita dapat mengatasi masalah ini dari fasa reka bentuk aplikasi.

Adalah mustahak untuk menentukan entiti kita sekecil mungkin.

Mari lihat entiti Pelanggan kami . Bagaimana jika kita menyusunnya sedikit, dan mengekstrak semua bidang telefon ke entiti ContactPhone dan berada dalam hubungan satu-ke-banyak?

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

Kodnya bersih dan, yang lebih penting, kami mencapai sesuatu. Sekarang, kita boleh mengemas kini entiti kita tanpa perlu mengambil dan mengisi semua data telefon .

Mengendalikan entiti kecil dan terikat membolehkan kita mengemas kini bidang yang diperlukan sahaja.

Satu-satunya ketidaknyamanan dari pendekatan ini adalah kita harus merancang entiti kita dengan kesedaran, tanpa terjebak dalam perangkap overengineering.

5. Pertanyaan Tersuai

Pendekatan lain yang dapat kita laksanakan adalah menentukan permintaan khusus untuk kemas kini separa.

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.

Seperti yang telah kita lihat, kita dapat memuatkan data kita terlebih dahulu dan kemudian memperbaruinya atau menentukan pernyataan khusus kita, tetapi ingatlah untuk mengetahui kekurangan yang terdapat dalam pendekatan ini dan bagaimana mengatasinya.

Seperti biasa, kod sumber untuk artikel ini terdapat di GitHub.