Pembaruan Data Parsial dengan Data Musim Semi

1. Perkenalan

Semi data ini CrudRespository # save tidak diragukan lagi sederhana, tetapi salah satu fitur bisa menjadi kelemahan: Ini update setiap kolom dalam tabel. Begitulah semantik U di CRUD, tapi bagaimana jika kita ingin melakukan PATCH?

Dalam tutorial ini, kita akan membahas teknik dan pendekatan untuk melakukan sebagian daripada pembaruan penuh.

2. Masalah

Seperti yang dinyatakan sebelumnya, save () akan menimpa entitas apa pun yang cocok dengan data yang disediakan, artinya kami tidak dapat menyediakan sebagian data. Itu bisa menjadi tidak nyaman, terutama untuk objek yang lebih besar dengan banyak bidang.

Jika kita melihat ORM, beberapa patch ada, seperti:

  • Anotasi @DynamicUpdat e dari Hibernate , yang secara dinamis menulis ulang kueri pembaruan
  • JPA ini @column penjelasan, seperti yang kita dapat melarang pembaruan pada kolom tertentu menggunakan diupdate parameter

Namun berikut ini, kami akan mendekati masalah ini dengan maksud khusus: Tujuan kami adalah mempersiapkan entitas kami untuk metode penyimpanan tanpa bergantung pada ORM.

3. Kasus Kami

Pertama, mari kita membangun entitas Pelanggan :

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

Kemudian, kami mendefinisikan repositori CRUD sederhana:

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

Terakhir, kami menyiapkan Layanan Pelanggan :

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

4. Pendekatan Muat dan Simpan

Pertama mari kita lihat pendekatan yang mungkin sudah tidak asing lagi: memuat entitas kita dari database dan kemudian memperbarui hanya bidang yang kita butuhkan.

Meskipun ini sederhana dan jelas, ini adalah pendekatan paling sederhana yang dapat kita gunakan.

Mari tambahkan metode di layanan kami untuk memperbarui data kontak pelanggan kami.

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

Kami akan memanggil metode findById dan mengambil entitas yang cocok, lalu kami melanjutkan dan memperbarui bidang yang diperlukan dan menyimpan data.

Teknik dasar ini efisien ketika jumlah bidang yang akan diperbarui relatif kecil, dan entitas kita agak sederhana.

Apa yang akan terjadi dengan lusinan bidang untuk diperbarui?

4.1. Strategi Pemetaan

Ketika objek kita memiliki banyak kolom dengan tingkat akses yang berbeda, sangat umum untuk mengimplementasikan pola DTO.

Sekarang, misalkan kita memiliki lebih dari seratus bidang telepon di objek kita. Menulis metode yang menuangkan data dari DTO ke entitas kita, seperti yang kita lakukan sebelumnya, bisa menyusahkan, dan sangat tidak bisa dikelola.

Namun demikian, kita bisa mengatasi masalah ini dengan menggunakan strategi pemetaan, dan secara khusus 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 penjelasan memungkinkan kita memperbarui objek yang ada, menyelamatkan kita dari rasa sakit menulis banyak kode.

MapStruct memiliki dekorator metode @BeanMapping , yang memungkinkan kita menentukan aturan untuk melewati nilai null selama proses pemetaan. Mari tambahkan ke antarmuka metode updateCustomerFromDto kita :

@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)

Dengan ini, kita dapat memuat entitas yang disimpan dan menggabungkannya dengan DTO sebelum memanggil metode penyimpanan JPA : pada kenyataannya, kita hanya akan memperbarui nilai yang dimodifikasi.

Jadi, mari tambahkan metode ke layanan kita, yang akan memanggil mapper kita:

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

Kelemahan dari pendekatan ini adalah kita tidak bisa meneruskan nilai null ke database selama update.

4.2. Entitas yang Lebih Sederhana

Terakhir, perlu diingat bahwa kita bisa mendekati masalah ini dari tahap desain aplikasi.

Sangat penting untuk mendefinisikan entitas kita menjadi sekecil mungkin.

Mari kita lihat entitas Pelanggan kami . Bagaimana jika kita menyusunnya sedikit, dan mengekstrak semua bidang telepon ke entitas ContactPhone dan berada di bawah 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; }

Kode itu bersih dan, yang lebih penting, kami mencapai sesuatu. Sekarang, kami dapat memperbarui entitas kami tanpa harus mengambil dan mengisi semua data telepon .

Menangani entitas kecil dan terbatas memungkinkan kami memperbarui hanya bidang yang diperlukan.

Satu-satunya ketidaknyamanan dari pendekatan ini adalah kita harus merancang entitas kita dengan kesadaran, tanpa jatuh ke dalam perangkap rekayasa yang berlebihan.

5. Kueri Kustom

Pendekatan lain yang dapat kita terapkan adalah menentukan kueri khusus untuk pembaruan parsial.

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 melakukan pramuat data kita dan kemudian memperbaruinya atau mendefinisikan pernyataan kustom kita, tetapi ingatlah untuk menyadari kekurangan yang disiratkan oleh pendekatan ini dan cara mengatasinya.

Seperti biasa, kode sumber untuk artikel ini tersedia di GitHub.