Mekanisme Penyerahan - Sihir Khusus Kuba

CPAP


Tampilan, atau pandangan, adalah salah satu konsep platform CUBA, bukan yang paling umum di dunia kerangka kerja web. Untuk memahaminya berarti menyelamatkan diri dari kesalahan bodoh ketika, karena pemuatan data yang tidak lengkap, aplikasi tiba-tiba berhenti bekerja. Mari kita lihat apa representasi (pun intended) dan mengapa itu sebenarnya nyaman.


Masalah data yang dibongkar


Mari kita mengambil area subjek yang lebih sederhana dan mempertimbangkan masalah menggunakan contohnya. Misalkan kita memiliki entitas Pelanggan yang merujuk ke entitas CustomerType dalam hubungan banyak ke satu, dengan kata lain, pembeli memiliki tautan ke jenis tertentu yang menjelaskannya: misalnya, “uang tunai sapi”, “kakap”, dll. Entitas CustomerType memiliki atribut nama tempat nama tipe disimpan.


Dan, mungkin, semua pendatang baru (atau bahkan pengguna tingkat lanjut) di CUBA cepat atau lambat menerima kesalahan ini:


IllegalStateException: Cannot get unfetched attribute [type] from detached object com.rtcab.cev.entity.Customer-e703700d-c977-bd8e-1a40-74afd88915af [detached].


Tidak bisa mendapatkan kesalahan atribut yang tidak dapat diambil di UI CUBA


Akui saja, Anda juga melihatnya dengan mata kepala sendiri? Saya - ya, dalam seratus situasi yang berbeda. Dalam artikel ini, kita akan memeriksa penyebab masalah ini, mengapa ada, dan bagaimana menyelesaikannya.
Sebagai permulaan, pengantar kecil untuk konsep pandangan.


Apa itu pandangan?


Tampilan di CUBA pada dasarnya adalah kumpulan kolom dalam database yang harus dimuat bersama dalam satu permintaan.


Misalkan kita ingin membuat UI dengan tabel pelanggan, di mana kolom pertama adalah nama pelanggan, dan yang kedua adalah nama tipe dari atribut customerType (seperti pada tangkapan layar di atas). Adalah logis untuk mengasumsikan bahwa dalam model data ini kita akan memiliki dua tabel terpisah dalam database, satu untuk entitas Pelanggan , yang lain untuk CustomerType . SELECT * from CEV_CUSTOMER akan mengembalikan kami data hanya dari satu tabel ( name atribut, dll.). Tentunya, untuk mendapatkan data juga dari tabel lain, kami akan menggunakan GABUNG.


Dalam kasus menggunakan kueri SQL klasik menggunakan BERGABUNG, hierarki asosiasi (atribut referensi) berkembang dari grafik ke daftar datar.
Catatan Penerjemah: dengan kata lain, hubungan antara tabel dihapus, dan hasilnya disajikan dalam array data tunggal yang mewakili gabungan tabel.


Dalam kasus CUBA, ORM digunakan, yang tidak kehilangan informasi tentang hubungan antar entitas dan menyajikan hasil kueri dalam bentuk grafik integral dari data yang diminta. Dalam hal ini, JPQL, objek analog dari SQL, digunakan sebagai bahasa query.


Namun demikian, data masih perlu dibongkar dari database dan diubah menjadi entitas graph. Untuk ini, mekanisme pemetaan objek-relasional (yang JPA) memiliki dua pendekatan utama untuk query ke database.


Pemuatan malas vs sangat ingin mengambil


Pemuatan malas dan pemuatan serakah adalah dua strategi yang memungkinkan untuk mendapatkan data dari database. Perbedaan mendasar antara keduanya adalah ketika data dari tabel tertaut dimuat. Contoh kecil untuk pemahaman yang lebih baik:


Ingat adegan dari buku "The Hobbit, atau Round Trip," di mana sekelompok gnome di perusahaan Gandalf dan Bilbo mencoba untuk meminta menginap di rumah Beorn? Gandalf memerintahkan para kurcaci untuk tampil ketat secara bergantian dan hanya setelah dia dengan hati-hati menyetujui Beorn dan mulai menyajikannya satu per satu agar tidak mengejutkan pemiliknya dengan kebutuhan menampung 15 tamu sekaligus.


Gandalf dan Kurcaci di Rumah Beorn


Jadi, Gandalf dan para gnome di rumah Beorn ... Ini mungkin bukan hal pertama yang terlintas dalam pikiran ketika berpikir tentang unduhan yang malas dan serakah, tetapi pasti ada kesamaan. Gandalf bertindak dengan bijak di sini, karena ia menyadari keterbatasan. Dia dapat dikatakan secara sadar memilih pemuatan malas dari gnome, karena dia mengerti bahwa mengunduh semua data sekaligus akan menjadi operasi yang terlalu berat untuk database ini. Namun, setelah gnome ke-8, Gandalf beralih ke pemuatan serakah dan memuat banyak gnome yang tersisa, karena dia memperhatikan bahwa terlalu sering mengakses ke basis data mulai membuatnya gugup.


Moralnya adalah bahwa pemuatan yang malas dan serakah memiliki pro dan kontra mereka. Apa yang harus diterapkan dalam setiap situasi tertentu, Anda memutuskan.


Minta Masalah N +1


Masalah permintaan N + 1 sering muncul jika Anda tanpa berpikir menggunakan pemuatan malas ke mana pun Anda pergi. Untuk mengilustrasikannya, mari kita lihat sepotong kode Grails. Ini tidak berarti bahwa di Grails semuanya memuat dengan malas (pada kenyataannya, Anda memilih sendiri metode boot). Di Grails, kueri ke database secara default mengembalikan instance entitas dengan semua atribut dari tabelnya. Intinya, SELECT * FROM Pet dieksekusi.


Jika Anda ingin masuk lebih dalam ke hubungan antar entitas, Anda harus melakukannya post factum. Berikut ini sebuah contoh:


 function getPetOwnerNamesForPets(String nameOfPet) { def pets = Pet.findAll(sort:"name") { name == nameOfPet } def ownerNames = [] pets.each { ownerNames << it.owner.name } return ownerNames.join(", ") } 

Grafik it.owner.name sini dengan satu baris: it.owner.name . Pemilik adalah hubungan yang tidak dimuat dalam permintaan asli ( Pet.findAll ). Jadi, setiap kali baris ini dipanggil, GORM akan melakukan sesuatu seperti SELECT * FROM Person WHERE id='…' . Pemuatan malas air murni.


Jika Anda menghitung jumlah total kueri SQL, Anda mendapatkan N (satu pemilik untuk setiap panggilan it.owner ) +1 (untuk Pet.findAll asli). Jika Anda ingin melihat lebih dalam grafik entitas terkait, kemungkinan database Anda akan dengan cepat menemukan batasnya.


Sebagai pengembang, Anda tidak mungkin memperhatikan hal ini, karena dari sudut pandang Anda, Anda hanya melihat-lihat grafik objek. Sarang tersembunyi ini dalam satu baris pendek menyebabkan database sangat menyakitkan dan membuat pemuatan malas terkadang berbahaya.


Mengembangkan analogi hobi, masalah N + 1 dapat memanifestasikan dirinya sebagai berikut: bayangkan bahwa Gandalf tidak mampu menyimpan nama-nama gnome dalam ingatannya. Karena itu, memperkenalkan kurcaci satu per satu, ia dipaksa untuk mundur ke kelompoknya dan meminta namanya kepada kurcaci. Dengan informasi ini, dia kembali ke Beorn dan mewakili Thorin. Kemudian ia mengulangi manuver ini untuk Bifur, Bofur, Fili, Kili, Dori, Nori, Ori, Oin, Gloyn, Balin, Dvalin dan Bombur.


Katai N + 1 Masalah


Sangat mudah untuk membayangkan bahwa skenario seperti itu tidak akan mungkin terjadi: penerima apa yang ingin menunggu informasi yang diminta begitu lama? Karena itu, Anda sebaiknya tidak menggunakan pendekatan ini tanpa berpikir dan bergantung secara buta pada pengaturan default mapper kegigihan Anda.


Memecahkan masalah kueri N + 1 menggunakan tampilan CUBA


Di CUBA, Anda kemungkinan besar tidak akan pernah menemui masalah permintaan N + 1, karena platform memutuskan untuk tidak menggunakan pemuatan malas tersembunyi sama sekali. Sebaliknya, CUBA memperkenalkan konsep representasi. Tampilan adalah deskripsi atribut mana yang harus dipilih dan dimuat bersama dengan instance entitas. Sesuatu seperti


 SELECT pet.name, person.name FROM Pet pet JOIN Person person ON pet.owner == person.id 

Di satu sisi, tampilan menggambarkan kolom yang perlu dimuat dari tabel utama ( Pet ) (alih-alih memuat semua atribut melalui *), di sisi lain, itu menggambarkan kolom yang harus dimuat dari tabel c-JOIN.


Anda dapat membayangkan tampilan CUBA sebagai tampilan SQL untuk OR-Mapper: prinsip operasi kira-kira sama.


Di platform CUBA, Anda tidak bisa menjalankan kueri melalui DataManager tanpa menggunakan tampilan. Dokumentasi memberikan contoh:


 @Inject private DataManager dataManager; private Book loadBookById(UUID bookId) { LoadContext<Book> loadContext = LoadContext.create(Book.class) .setId(bookId).setView("book.edit"); return dataManager.load(loadContext); } 

Di sini kami ingin mengunduh buku dengan ID-nya. Metode setView("book.edit") , ketika membuat konteks Load, menunjukkan tampilan buku mana yang harus dimuat dari database. Jika Anda tidak melewatkan tampilan apa pun, pengelola data menggunakan salah satu dari tiga tampilan standar yang dimiliki setiap entitas: tampilan _local . Lokal di sini mengacu pada atribut yang tidak merujuk tabel lain, semuanya sederhana.


Memecahkan masalah dengan IllegalStateException melalui tampilan


Sekarang kita memiliki sedikit pemahaman tentang konsep representasi, mari kita kembali ke contoh pertama dari awal artikel dan mencoba untuk mencegah melempar pengecualian.


Pesan IllegalStateException: Tidak dapat memperoleh atribut yang tidak dapat diambil [] dari objek terpisah hanya berarti bahwa Anda mencoba untuk menampilkan beberapa atribut yang tidak termasuk dalam tampilan yang memuat entitas.


Seperti yang Anda lihat, di deskriptor layar browse saya menggunakan tampilan _local , dan ini adalah seluruh masalahnya:


 <dsContext> <groupDatasource id="customersDs" class="com.rtcab.cev.entity.Customer" view="_local"> <query> <![CDATA[select e from cev$Customer e]]> </query> </groupDatasource> </dsContext> 

Untuk menghilangkan kesalahan, pertama-tama Anda harus memasukkan jenis pelanggan dalam tampilan. Karena kita tidak dapat mengubah tampilan default _local , kita dapat membuat milik kita sendiri. Di Studio, ini bisa dilakukan, misalnya, sebagai berikut (klik kanan pada entitas> buat tampilan):


Membuat tampilan di Studio


baik secara langsung di deskriptor views.xml aplikasi kita:


 <view class="com.rtcab.cev.entity.Customer" extends="_local" name="customer-view"> <property name="type" view="_minimal"/> </view> 

Setelah itu, kami mengubah tautan ke tampilan di layar jelajahi, seperti ini:


 <groupDatasource id="customersDs" class="com.rtcab.cev.entity.Customer" view="customer-view"> <query> <![CDATA[select e from cev$Customer e]]> </query> </groupDatasource> 

Ini sepenuhnya menyelesaikan masalah, dan sekarang data tautan ditampilkan di layar tampilan pelanggan.


Tampilan _Minimal dan nama contoh


Apa lagi yang layak disebutkan dalam konteks tampilan adalah tampilan _minimal . Pandangan lokal memiliki definisi yang sangat jelas: itu mencakup semua atribut entitas, yang merupakan atribut langsung dari tabel (yang bukan kunci asing).


Definisi representasi minimal tidak begitu jelas, tetapi juga cukup jelas.


CUBA memiliki konsep nama instance entitas - nama instance. Nama instance adalah setara dengan metode toString() di Java lama yang baik. Ini adalah representasi string entitas untuk ditampilkan pada UI dan untuk digunakan dalam tautan. Nama instance diatur menggunakan anotasi entitas NamePattern .


Ini digunakan seperti ini: @NamePattern("%s (%s)|name,code") . Kami memiliki dua hasil:


Nama instance mendefinisikan pemetaan entitas ke UI


Pertama-tama, nama instance menentukan apa dan dalam urutan apa akan ditampilkan di UI jika entitas merujuk ke entitas lain (sebagaimana Pelanggan merujuk pada CustomerType ).


Dalam kasus kami, tipe pelanggan akan ditampilkan sebagai nama instance CustomerType , di mana kode ditambahkan ke tanda kurung. Jika nama instance tidak disetel, nama kelas entitas dan ID dari instance spesifik akan ditampilkan - setuju bahwa ini sama sekali bukan apa yang ingin dilihat pengguna. Lihat tangkapan layar sebelum dan sesudah di bawah ini untuk contoh kedua kasus.


Referensi ke entitas yang tidak ditentukan nama contohnya


Referensi entitas dengan nama instance yang diberikan


Nama instance mendefinisikan atribut tampilan minimum


Hal kedua yang dipengaruhi oleh anotasi NamePattern adalah: semua atribut yang ditentukan setelah bilah vertikal secara otomatis membentuk tampilan _minimal . Sekilas, ini tampak jelas, karena data dalam beberapa bentuk perlu ditampilkan di UI, yang berarti bahwa Anda harus mengunduhnya terlebih dahulu dari database. Meskipun, jujur ​​saja, saya jarang memikirkan fakta ini.


Penting untuk dicatat di sini bahwa representasi minimal, jika dibandingkan dengan yang lokal, dapat berisi referensi ke entitas lain. Misalnya, untuk pembeli dari contoh di atas, saya menetapkan nama contoh, yang mencakup satu atribut lokal dari entitas Pelanggan ( name ) dan satu atribut referensi ( type ):


 @NamePattern("%s - %s|name,type") 

Representasi minimum dapat digunakan secara rekursif: (Pelanggan [Nama Instance] -> CustomerType [Instance Name])


Catatan Penerjemah: sejak publikasi artikel, tampilan sistem lainnya telah muncul - tampilan _base , yang mencakup semua atribut non-sistem lokal dan atribut yang ditentukan dalam anotasi @NamePattern (mis., Pada kenyataannya _minimal + _local ).


Kesimpulan


Sebagai kesimpulan, kami merangkum topik yang paling penting. Berkat pandangannya, dalam CUBA kami dapat secara eksplisit menunjukkan apa yang harus dimuat dari basis data. Tampilan menentukan apa yang akan dimuat dengan rakus, sementara sebagian besar kerangka kerja lainnya diam-diam melakukan pemuatan malas.


Representasi mungkin tampak seperti mekanisme yang rumit, tetapi dalam jangka panjang mereka membenarkan diri mereka sendiri.


Saya harap saya berhasil menjelaskan dengan cara yang mudah diakses apa sebenarnya pandangan misterius ini. Tentu saja, ada skenario yang lebih maju untuk penggunaannya, serta jebakan dalam bekerja dengan representasi secara umum dan dengan representasi minimal pada khususnya, tetapi saya akan menulis tentang ini di pos terpisah entah bagaimana.

Source: https://habr.com/ru/post/id425765/


All Articles