JOOQ Bukan Pengganti untuk Hibernate. Mereka Menyelesaikan Masalah Berbeza
Saya pada asalnya menulis artikel ini dalam bahasa Rusia. Jadi, jika anda penutur asli, anda boleh membacanya melalui pautan ini.
Pada tahun lalu atau lebih, saya telah menjumpai artikel dan ceramah yang mencadangkan bahawa JOOQ ialah alternatif moden dan unggul kepada Hibernate. Hujah biasanya termasuk:
- JOOQ membolehkan anda mengesahkan segala-galanya pada masa penyusunan, manakala Hibernate tidak!
- Hibernate menjana pertanyaan pelik dan tidak selalu optimum, manakala dengan JOOQ, semuanya telus!
- Entiti hibernate boleh berubah, yang tidak baik. JOOQ membenarkan semua entiti menjadi tidak berubah (hello, pengaturcaraan berfungsi)!
- JOOQ tidak melibatkan sebarang "sihir" dengan anotasi!
Biar saya nyatakan terlebih dahulu bahawa saya menganggap JOOQ sebagai perpustakaan yang sangat baik (khususnya perpustakaan, bukan rangka kerja seperti Hibernate). Ia cemerlang dalam tugasnya — bekerja dengan SQL dalam cara yang ditaip secara statik untuk menangkap kebanyakan ralat pada masa penyusunan.
Namun, apabila saya mendengar hujah bahawa masa Hibernate telah berlalu dan kita kini harus menulis segala-galanya menggunakan JOOQ, saya rasa seperti mengatakan era pangkalan data hubungan sudah berakhir dan kita hanya perlu menggunakan NoSQL sekarang. Kedengaran lucu? Namun, tidak lama dahulu, perbincangan seperti itu agak serius.
Saya percaya isu ini terletak pada salah faham tentang masalah teras yang ditangani oleh kedua-dua alat ini. Dalam artikel ini, saya berhasrat untuk menjelaskan soalan-soalan ini. Kami akan meneroka:
- Apakah Skrip Transaksi?
- Apakah corak Model Domain?
- Apakah masalah khusus yang Hibernate dan JOOQ selesaikan?
- Mengapa satu tidak menggantikan yang lain, dan bagaimana ia boleh wujud bersama?
Skrip Transaksi
Cara paling mudah dan paling intuitif untuk bekerja dengan pangkalan data ialah corak Skrip Transaksi. Secara ringkasnya, anda menyusun semua logik perniagaan anda sebagai satu set perintah SQL digabungkan menjadi satu transaksi. Biasanya, setiap kaedah dalam kelas mewakili operasi perniagaan dan terhad kepada satu transaksi.
Andaikan kami sedang membangunkan aplikasi yang membenarkan penceramah menyerahkan ceramah mereka ke persidangan (untuk memudahkan, kami hanya akan merekodkan tajuk ceramah). Mengikuti corak Skrip Transaksi, kaedah untuk menghantar ceramah mungkin kelihatan seperti ini (menggunakan JDBI untuk SQL):
@Service @RequiredArgsConstructor public class TalkService { private final Jdbi jdbi; public TalkSubmittedResult submitTalk(Long speakerId, String title) { var talkId = jdbi.inTransaction(handle -> { // Count the number of accepted talks by the speaker var acceptedTalksCount = handle.select("SELECT count(*) FROM talk WHERE speaker_id = :id AND status = 'ACCEPTED'") .bind("id", speakerId) .mapTo(Long.class) .one(); // Check if the speaker is experienced var experienced = acceptedTalksCount >= 10; // Determine the maximum allowable number of submitted talks var maxSubmittedTalksCount = experienced ? 5 : 3; var submittedTalksCount = handle.select("SELECT count(*) FROM talk WHERE speaker_id = :id AND status = 'SUBMITTED'") .bind("id", speakerId) .mapTo(Long.class) .one(); // If the maximum number of submitted talks is exceeded, throw an exception if (submittedTalksCount >= maxSubmittedTalksCount) { throw new CannotSubmitTalkException("Submitted talks count is maximum: " + maxSubmittedTalksCount); } return handle.createUpdate( "INSERT INTO talk (speaker_id, status, title) " + "VALUES (:id, 'SUBMITTED', :title)" ).bind("id", speakerId) .bind("title", title) .executeAndReturnGeneratedKeys("id") .mapTo(Long.class) .one(); }); return new TalkSubmittedResult(talkId); } }
Dalam kod ini:
- Kami mengira berapa banyak ceramah yang telah diserahkan oleh penceramah.
- Kami menyemak sama ada bilangan maksimum yang dibenarkan bagi ceramah yang dihantar telah melebihi.
- Jika semuanya okey, kami buat ceramah baharu dengan status DISERAHKAN.
Terdapat keadaan perlumbaan yang berpotensi di sini, tetapi untuk kesederhanaan, kami tidak akan menumpukan pada itu.
Kebaikan pendekatan ini:
- SQL yang sedang dilaksanakan adalah mudah dan boleh diramal. Ia mudah untuk mengubah suai untuk peningkatan prestasi jika perlu.
- Kami hanya mengambil data yang diperlukan daripada pangkalan data.
- Dengan JOOQ, kod ini boleh ditulis dengan lebih ringkas, padat dan dengan penaipan statik!
Keburukan:
- Adalah mustahil untuk menguji logik perniagaan dengan ujian unit sahaja. Anda memerlukan ujian penyepaduan (dan beberapa daripadanya).
- Jika domain itu rumit, pendekatan ini boleh membawa kepada kod spageti dengan cepat.
- Terdapat risiko pertindihan kod, yang boleh membawa kepada pepijat yang tidak dijangka semasa sistem berkembang.
Pendekatan ini sah dan masuk akal jika perkhidmatan anda mempunyai logik yang sangat mudah yang tidak dijangka menjadi lebih kompleks dari semasa ke semasa. Walau bagaimanapun, domain selalunya lebih besar. Oleh itu, kita memerlukan alternatif.
Model Domain
Idea corak Model Domain ialah kami tidak lagi mengikat logik perniagaan kami terus kepada arahan SQL. Sebaliknya, kami mencipta objek domain (dalam konteks Java, kelas) yang menerangkan tingkah laku dan menyimpan data tentang entiti domain.
Dalam artikel ini, kami tidak akan membincangkan perbezaan antara model anemia dan kaya. Jika anda berminat, saya telah menulis bahagian terperinci mengenai topik itu.
Senario (perkhidmatan) perniagaan hendaklah menggunakan objek ini sahaja dan elakkan daripada terikat dengan pertanyaan pangkalan data tertentu.
Sudah tentu, pada hakikatnya, kami mungkin mempunyai gabungan interaksi dengan objek domain dan pertanyaan pangkalan data langsung untuk memenuhi keperluan prestasi. Di sini, kami membincangkan pendekatan klasik untuk melaksanakan Model Domain, di mana pengkapsulan dan pengasingan tidak dilanggar.
Sebagai contoh, jika kita bercakap tentang entiti Speaker dan Talk, seperti yang dinyatakan sebelum ini, objek domain mungkin kelihatan seperti ini:
@Service @RequiredArgsConstructor public class TalkService { private final Jdbi jdbi; public TalkSubmittedResult submitTalk(Long speakerId, String title) { var talkId = jdbi.inTransaction(handle -> { // Count the number of accepted talks by the speaker var acceptedTalksCount = handle.select("SELECT count(*) FROM talk WHERE speaker_id = :id AND status = 'ACCEPTED'") .bind("id", speakerId) .mapTo(Long.class) .one(); // Check if the speaker is experienced var experienced = acceptedTalksCount >= 10; // Determine the maximum allowable number of submitted talks var maxSubmittedTalksCount = experienced ? 5 : 3; var submittedTalksCount = handle.select("SELECT count(*) FROM talk WHERE speaker_id = :id AND status = 'SUBMITTED'") .bind("id", speakerId) .mapTo(Long.class) .one(); // If the maximum number of submitted talks is exceeded, throw an exception if (submittedTalksCount >= maxSubmittedTalksCount) { throw new CannotSubmitTalkException("Submitted talks count is maximum: " + maxSubmittedTalksCount); } return handle.createUpdate( "INSERT INTO talk (speaker_id, status, title) " + "VALUES (:id, 'SUBMITTED', :title)" ).bind("id", speakerId) .bind("title", title) .executeAndReturnGeneratedKeys("id") .mapTo(Long.class) .one(); }); return new TalkSubmittedResult(talkId); } }
Di sini, kelas Penceramah mengandungi logik perniagaan untuk menghantar ceramah. Interaksi pangkalan data diasingkan, membenarkan model domain memfokus pada peraturan perniagaan.
Andaikan antara muka repositori ini:
@AllArgsConstructor public class Speaker { private Long id; private String firstName; private String lastName; private List<Talk> talks; public Talk submitTalk(String title) { boolean experienced = countTalksByStatus(Status.ACCEPTED) >= 10; int maxSubmittedTalksCount = experienced ? 3 : 5; if (countTalksByStatus(Status.SUBMITTED) >= maxSubmittedTalksCount) { throw new CannotSubmitTalkException( "Submitted talks count is maximum: " + maxSubmittedTalksCount); } Talk talk = Talk.newTalk(this, Status.SUBMITTED, title); talks.add(talk); return talk; } private long countTalksByStatus(Talk.Status status) { return talks.stream().filter(t -> t.getStatus().equals(status)).count(); } } @AllArgsConstructor public class Talk { private Long id; private Speaker speaker; private Status status; private String title; private int talkNumber; void setStatus(Function<Status, Status> fnStatus) { this.status = fnStatus.apply(this.status); } public enum Status { SUBMITTED, ACCEPTED, REJECTED } }
Kemudian SpeakerService boleh dilaksanakan dengan cara ini:
public interface SpeakerRepository { Speaker findById(Long id); void save(Speaker speaker); }
Kebaikan Model Domain:
- Objek domain dipisahkan sepenuhnya daripada butiran pelaksanaan (iaitu, pangkalan data). Ini menjadikannya mudah untuk diuji dengan ujian unit biasa.
- Logik perniagaan dipusatkan dalam objek domain. Ini sangat mengurangkan risiko logik merebak ke seluruh aplikasi, tidak seperti dalam pendekatan Skrip Transaksi.
- Jika mahu, objek domain boleh dibuat tidak berubah sepenuhnya, yang meningkatkan keselamatan apabila bekerja dengannya (anda boleh menghantarnya kepada sebarang kaedah tanpa perlu risau tentang pengubahsuaian yang tidak disengajakan).
- Medan dalam objek domain boleh digantikan dengan Objek Nilai, yang bukan sahaja meningkatkan kebolehbacaan tetapi juga memastikan kesahihan medan pada masa tugasan (anda tidak boleh membuat Objek Nilai dengan kandungan tidak sah).
Ringkasnya, terdapat banyak kelebihan. Walau bagaimanapun, terdapat satu cabaran penting. Menariknya, dalam buku mengenai Reka Bentuk Dipacu Domain, yang sering mempromosikan corak Model Domain, masalah ini sama ada tidak disebut langsung atau hanya disentuh secara ringkas.
Masalahnya ialah bagaimanakah anda menyimpan objek domain ke pangkalan data dan kemudian membacanya kembali? Dalam erti kata lain, bagaimana anda melaksanakan repositori?
Kini, jawapannya sudah jelas. Hanya gunakan Hibernate (atau lebih baik lagi, Spring Data JPA) dan selamatkan masalah anda. Tetapi mari bayangkan kita berada dalam dunia di mana rangka kerja ORM belum dicipta. Bagaimanakah kami akan menyelesaikan masalah ini?
Pemetaan manual
Untuk melaksanakan SpeakerRepository saya juga menggunakan JDBI:
@Service @RequiredArgsConstructor public class TalkService { private final Jdbi jdbi; public TalkSubmittedResult submitTalk(Long speakerId, String title) { var talkId = jdbi.inTransaction(handle -> { // Count the number of accepted talks by the speaker var acceptedTalksCount = handle.select("SELECT count(*) FROM talk WHERE speaker_id = :id AND status = 'ACCEPTED'") .bind("id", speakerId) .mapTo(Long.class) .one(); // Check if the speaker is experienced var experienced = acceptedTalksCount >= 10; // Determine the maximum allowable number of submitted talks var maxSubmittedTalksCount = experienced ? 5 : 3; var submittedTalksCount = handle.select("SELECT count(*) FROM talk WHERE speaker_id = :id AND status = 'SUBMITTED'") .bind("id", speakerId) .mapTo(Long.class) .one(); // If the maximum number of submitted talks is exceeded, throw an exception if (submittedTalksCount >= maxSubmittedTalksCount) { throw new CannotSubmitTalkException("Submitted talks count is maximum: " + maxSubmittedTalksCount); } return handle.createUpdate( "INSERT INTO talk (speaker_id, status, title) " + "VALUES (:id, 'SUBMITTED', :title)" ).bind("id", speakerId) .bind("title", title) .executeAndReturnGeneratedKeys("id") .mapTo(Long.class) .one(); }); return new TalkSubmittedResult(talkId); } }
Pendekatannya mudah. Untuk setiap repositori, kami menulis pelaksanaan berasingan yang berfungsi dengan pangkalan data menggunakan mana-mana perpustakaan SQL (seperti JOOQ atau JDBI).
Pada pandangan pertama (dan mungkin juga yang kedua), penyelesaian ini mungkin kelihatan agak bagus. Pertimbangkan ini:
- Kod ini kekal sangat telus, sama seperti dalam pendekatan Skrip Transaksi.
- Tiada lagi isu dengan menguji logik perniagaan hanya melalui ujian integrasi. Ini hanya diperlukan untuk pelaksanaan repositori (dan mungkin beberapa senario E2E).
- Kod pemetaan ada di hadapan kami. Tiada sihir Hibernate terlibat. Menemui pepijat? Cari garisan yang betul dan betulkan.
Keperluan untuk Hibernate
Perkara menjadi lebih menarik di dunia nyata, di mana anda mungkin menghadapi senario seperti ini:
- Objek domain mungkin perlu menyokong pewarisan.
- Sekumpulan medan boleh digabungkan menjadi Objek Nilai yang berasingan (Terbenam dalam JPA/Hibernate).
- Sesetengah medan tidak seharusnya dimuatkan setiap kali anda mengambil objek domain, tetapi hanya apabila diakses, untuk meningkatkan prestasi (pemuatan malas).
- Boleh wujud hubungan yang kompleks antara objek (satu-ke-banyak, banyak-ke-banyak, dsb.).
- Anda perlu memasukkan hanya medan yang telah berubah dalam pernyataan KEMASKINI kerana medan lain jarang berubah dan tiada gunanya menghantarnya melalui rangkaian (anotasi DynamicUpdate).
Selain itu, anda perlu mengekalkan kod pemetaan semasa logik perniagaan dan objek domain anda berkembang.
Jika anda cuba mengendalikan setiap mata ini sendiri, anda akhirnya akan mendapati diri anda (terkejut!) menulis rangka kerja seperti Hibernate anda — atau lebih berkemungkinan, versi yang lebih ringkas daripadanya.
Matlamat JOOQ dan Hibernate
JOOQ menangani kekurangan penaipan statik semasa menulis pertanyaan SQL. Ini membantu mengurangkan bilangan ralat pada peringkat penyusunan. Dengan penjanaan kod terus daripada skema pangkalan data, sebarang kemas kini pada skema akan segera menunjukkan tempat kod itu perlu diperbaiki (ia tidak akan disusun).
Hibernate menyelesaikan masalah memetakan objek domain ke pangkalan data hubungan dan sebaliknya (membaca data daripada pangkalan data dan memetakannya ke objek domain).
Oleh itu, tidak masuk akal untuk berhujah bahawa Hibernate lebih teruk atau JOOQ lebih baik. Alat ini direka untuk tujuan yang berbeza. Jika aplikasi anda dibina berdasarkan paradigma Skrip Transaksi, JOOQ sudah pasti pilihan yang ideal. Tetapi jika anda ingin menggunakan corak Model Domain dan mengelakkan Hibernate, anda perlu berurusan dengan kegembiraan pemetaan manual dalam pelaksanaan repositori tersuai. Sudah tentu, jika majikan anda membayar anda untuk membina satu lagi pembunuh Hibernate, tiada soalan di sana. Tetapi kemungkinan besar, mereka mengharapkan anda menumpukan pada logik perniagaan, bukan kod infrastruktur untuk pemetaan objek ke pangkalan data.
Dengan cara ini, saya percaya gabungan Hibernate dan JOOQ berfungsi dengan baik untuk CQRS. Anda mempunyai aplikasi (atau sebahagian logiknya) yang melaksanakan arahan, seperti operasi CREATE/UPDATE/DELETE — di sinilah Hibernate sesuai dengan sempurna. Sebaliknya, anda mempunyai perkhidmatan pertanyaan yang membaca data. Di sini, JOOQ adalah cemerlang. Ia menjadikan membina pertanyaan kompleks dan mengoptimumkannya lebih mudah berbanding dengan Hibernate.
Bagaimana dengan DAO dalam JOOQ?
Memang benar. JOOQ membolehkan anda menjana DAO yang mengandungi pertanyaan standard untuk mengambil entiti daripada pangkalan data. Anda juga boleh melanjutkan DAO ini dengan kaedah anda. Selain itu, JOOQ akan menjana entiti yang boleh diisi menggunakan setter, serupa dengan Hibernate, dan dihantar ke kaedah sisipan atau kemas kini dalam DAO. Bukankah itu seperti Data Musim Bunga?
Untuk kes mudah, ini memang boleh berfungsi. Walau bagaimanapun, ia tidak jauh berbeza daripada melaksanakan repositori secara manual. Masalahnya serupa:
- Entiti tidak akan mempunyai sebarang perhubungan: tiada ManyToOne, tiada OneToMany. Hanya lajur pangkalan data, yang menjadikan penulisan logik perniagaan lebih sukar.
- Entiti dijana secara individu. Anda tidak boleh menyusunnya ke dalam hierarki warisan.
- Hakikat bahawa entiti dijana bersama DAO bermakna anda tidak boleh mengubah suainya mengikut kehendak anda. Contohnya, menggantikan medan dengan Objek Nilai, menambah perhubungan kepada entiti lain atau mengumpulkan medan ke dalam Boleh Dibenam tidak akan dapat dilakukan kerana menjana semula entiti akan menimpa perubahan anda. Ya, anda boleh mengkonfigurasi penjana untuk mencipta entiti secara berbeza sedikit, tetapi pilihan penyesuaian adalah terhad (dan tidak semudah menulis kod sendiri).
Jadi, jika anda ingin membina model domain yang kompleks, anda perlu melakukannya secara manual. Tanpa Hibernate, tanggungjawab untuk pemetaan akan jatuh sepenuhnya kepada anda. Sudah tentu, menggunakan JOOQ lebih menyenangkan daripada JDBI, tetapi prosesnya masih memerlukan tenaga kerja.
Malah Lukas Eder, pencipta JOOQ, menyebut dalam blognya bahawa DAO telah ditambahkan pada perpustakaan kerana ia adalah corak yang popular, bukan kerana dia semestinya mengesyorkan menggunakannya.
Kesimpulan
Terima kasih kerana membaca artikel. Saya peminat tegar Hibernate dan menganggapnya sebagai rangka kerja yang sangat baik. Walau bagaimanapun, saya faham bahawa sesetengah orang mungkin mendapati JOOQ lebih mudah. Perkara utama artikel saya ialah Hibernate dan JOOQ bukan saingan. Alat ini boleh wujud bersama walaupun dalam produk yang sama jika ia membawa nilai.
Jika anda mempunyai sebarang ulasan atau maklum balas tentang kandungan, saya berbesar hati untuk membincangkannya. Selamat hari yang produktif!
Sumber
- JDBI
- Skrip Transaksi
- Model Domain
- Artikel saya – Model Domain Kaya dengan But Spring dan Hibernate
- Corak repositori
- Objek Nilai
- JPA Terbenam
- JPA DynamicUpdate
- CQRS
- Lukas Eder: Kepada DAO atau tidak kepada DAO
Atas ialah kandungan terperinci JOOQ Bukan Pengganti untuk Hibernate. Mereka Menyelesaikan Masalah Berbeza. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

Video Face Swap
Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas

Penyelesaian masalah dan penyelesaian kepada perisian keselamatan syarikat yang menyebabkan beberapa aplikasi tidak berfungsi dengan baik. Banyak syarikat akan menggunakan perisian keselamatan untuk memastikan keselamatan rangkaian dalaman. …

Pemprosesan pemetaan medan dalam dok sistem sering menemui masalah yang sukar ketika melaksanakan sistem dok: bagaimana untuk memetakan medan antara muka sistem dengan berkesan ...

Penyelesaian untuk menukar nama kepada nombor untuk melaksanakan penyortiran dalam banyak senario aplikasi, pengguna mungkin perlu menyusun kumpulan, terutama dalam satu ...

Apabila menggunakan Mybatis-Plus atau Rangka Kerja ORM yang lain untuk operasi pangkalan data, sering diperlukan untuk membina syarat pertanyaan berdasarkan nama atribut kelas entiti. Sekiranya anda secara manual setiap kali ...

Mula musim bunga menggunakan versi IntelliJideaultimate ...

Penukaran objek dan tatasusunan Java: Perbincangan mendalam tentang risiko dan kaedah penukaran jenis cast yang betul Banyak pemula Java akan menemui penukaran objek ke dalam array ...

Penjelasan terperinci mengenai reka bentuk jadual SKU dan SPU di platform e-dagang Artikel ini akan membincangkan isu reka bentuk pangkalan data SKU dan SPU dalam platform e-dagang, terutamanya bagaimana menangani jualan yang ditentukan pengguna ...

Bagaimanakah penyelesaian caching Redis menyedari keperluan senarai kedudukan produk? Semasa proses pembangunan, kita sering perlu menangani keperluan kedudukan, seperti memaparkan ...
