Pendahuluan Querydsl

1. Perkenalan

Ini adalah artikel pengantar untuk membantu Anda memahami dan menjalankan Querydsl API untuk persistensi data.

Tujuannya di sini adalah untuk memberi Anda alat praktis untuk menambahkan Querydsl ke dalam proyek Anda, memahami struktur dan tujuan kelas yang dihasilkan, dan mendapatkan pemahaman dasar tentang cara menulis kueri database yang aman bagi tipe untuk skenario yang paling umum.

2. Tujuan Querydsl

Kerangka kerja pemetaan objek-relasional merupakan inti dari Enterprise Java. Ini mengkompensasi ketidaksesuaian antara pendekatan berorientasi objek dan model database relasional. Mereka juga memungkinkan pengembang untuk menulis kode persistensi dan logika domain yang lebih rapi dan ringkas.

Namun, salah satu pilihan desain tersulit untuk kerangka kerja ORM adalah API untuk membuat kueri yang benar dan aman jenis.

Salah satu framework Java ORM yang paling banyak digunakan, Hibernate (dan juga standar JPA yang terkait erat), mengusulkan bahasa kueri berbasis string HQL (JPQL) yang sangat mirip dengan SQL. Kelemahan yang jelas dari pendekatan ini adalah kurangnya keamanan tipe dan tidak adanya pemeriksaan kueri statis. Selain itu, dalam kasus yang lebih kompleks (misalnya, saat kueri perlu dibuat saat runtime bergantung pada beberapa kondisi), membuat kueri HQL biasanya melibatkan penggabungan string yang biasanya sangat tidak aman dan rawan kesalahan.

Standar JPA 2.0 membawa peningkatan dalam bentuk API Kueri Kriteria - metode baru dan aman untuk membuat kueri yang memanfaatkan kelas metamodel yang dihasilkan selama prapemrosesan anotasi. Sayangnya, karena merupakan terobosan pada intinya, API Query Kriteria akhirnya sangat bertele-tele dan praktis tidak dapat dibaca. Berikut adalah contoh dari tutorial EE Jakarta untuk membuat kueri sesederhana SELECT p FROM Pet p :

EntityManager em = ...; CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(Pet.class); Root pet = cq.from(Pet.class); cq.select(pet); TypedQuery q = em.createQuery(cq); List allPets = q.getResultList();

Tidak heran jika perpustakaan Querydsl yang lebih memadai segera muncul, berdasarkan ide yang sama dari kelas metadata yang dihasilkan, namun diimplementasikan dengan API yang lancar dan dapat dibaca.

3. Pembuatan Kelas Querydsl

Mari mulai dengan membuat dan menjelajahi metaclass ajaib yang menjelaskan API Querydsl yang lancar.

3.1. Menambahkan Querydsl ke Maven Build

Menyertakan Querydsl dalam proyek Anda semudah menambahkan beberapa dependensi ke file build Anda dan mengonfigurasi plugin untuk memproses anotasi JPA. Mari kita mulai dengan dependensi. Versi pustaka Querydsl harus diekstrak ke properti terpisah di dalam file bagian, sebagai berikut (untuk versi terbaru pustaka Querydsl, periksa repositori Pusat Maven):

 4.1.3 

Selanjutnya, tambahkan dependensi berikut ke bagian dari file pom.xml Anda :

  com.querydsl querydsl-apt ${querydsl.version} provided   com.querydsl querydsl-jpa ${querydsl.version}  

The querydsl-apt ketergantungan adalah alat pengolahan penjelasan (APT) - pelaksanaan sesuai API Java yang memungkinkan pengolahan penjelasan di file sumber sebelum mereka pindah ke tahap kompilasi. Alat ini menghasilkan apa yang disebut Q-jenis - kelas yang secara langsung berhubungan dengan kelas entitas aplikasi Anda, tetapi diawali dengan huruf Q. Misalnya, jika Anda memiliki Pengguna kelas ditandai dengan @ Entity penjelasan dalam aplikasi Anda, maka tipe-Q yang dihasilkan akan berada di file sumber QUser.java .

The disediakan lingkup querydsl-apt ketergantungan berarti bahwa toples ini harus dibuat tersedia hanya pada waktu membangun, tetapi tidak termasuk ke dalam artefak aplikasi.

Pustaka querydsl-jpa adalah Querydsl itu sendiri, dirancang untuk digunakan bersama dengan aplikasi JPA.

Untuk mengonfigurasi plugin pemrosesan anotasi yang memanfaatkan querydsl-apt , tambahkan konfigurasi plugin berikut ke pom Anda - di dalam elemen:

 com.mysema.maven apt-maven-plugin 1.1.3    process   target/generated-sources/java com.querydsl.apt.jpa.JPAAnnotationProcessor    

Plugin ini memastikan bahwa tipe-Q dihasilkan selama tujuan proses pembuatan Maven. The outputDirectory poin properti konfigurasi ke direktori dimana Q-jenis file sumber akan dihasilkan. Nilai properti ini akan berguna nanti, saat Anda akan menjelajahi Q-files.

Anda juga harus menambahkan direktori ini ke folder sumber proyek, jika IDE Anda tidak melakukannya secara otomatis - lihat dokumentasi IDE favorit Anda tentang cara melakukannya.

Untuk artikel ini kita akan menggunakan model JPA sederhana dari layanan blog, yang terdiri dari Pengguna dan BlogPosts mereka dengan hubungan satu-ke-banyak di antara mereka:

@Entity public class User { @Id @GeneratedValue private Long id; private String login; private Boolean disabled; @OneToMany(cascade = CascadeType.PERSIST, mappedBy = "user") private Set blogPosts = new HashSet(0); // getters and setters } @Entity public class BlogPost { @Id @GeneratedValue private Long id; private String title; private String body; @ManyToOne private User user; // getters and setters }

Untuk menghasilkan tipe-Q untuk model Anda, cukup jalankan:

mvn compile

3.2. Menjelajahi Kelas yang Dihasilkan

Sekarang masuk ke direktori yang ditentukan dalam properti outputDirectory dari apt-maven-plugin ( target / generated-sources / java dalam contoh kami). Anda akan melihat paket dan struktur kelas yang secara langsung mencerminkan model domain Anda, kecuali semua kelas dimulai dengan huruf Q ( QUser dan QBlogPost dalam kasus kami).

Buka file QUser.java . Ini adalah titik masuk Anda untuk membangun semua kueri yang memiliki Pengguna sebagai entitas root. Hal pertama yang akan Anda perhatikan adalah anotasi @Generated yang berarti file ini dibuat secara otomatis dan tidak boleh diedit secara manual. Jika Anda mengubah salah satu kelas model domain Anda, Anda harus menjalankan kompilasi mvn lagi untuk membuat ulang semua tipe-Q yang sesuai.

Selain dari beberapa konstruktor QUser yang ada dalam file ini, Anda juga harus memperhatikan instance final statis publik dari kelas QUser :

public static final QUser user = new QUser("user");

Ini adalah contoh yang dapat Anda gunakan di sebagian besar kueri Querydsl Anda ke entitas ini, kecuali jika Anda perlu menulis beberapa kueri yang lebih kompleks, seperti menggabungkan beberapa contoh tabel yang berbeda dalam satu kueri.

Hal terakhir yang harus diperhatikan adalah bahwa untuk setiap bidang kelas entitas ada bidang * Path yang sesuai di tipe-Q, seperti ID NumberPath , login StringPath dan blogPost SetPath di kelas QUser (perhatikan bahwa nama bidang sesuai dengan Set jamak). Bidang ini digunakan sebagai bagian dari API kueri lancar yang akan kita temui nanti.

4. Query Dengan Querydsl

4.1. Querying dan Filtering Sederhana

To build a query, first we’ll need an instance of a JPAQueryFactory, which is a preferred way of starting the building process. The only thing that JPAQueryFactory needs is an EntityManager, which should already be available in your JPA application via EntityManagerFactory.createEntityManager() call or @PersistenceContext injection.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("com.baeldung.querydsl.intro"); EntityManager em = entityManagerFactory.createEntityManager(); JPAQueryFactory queryFactory = new JPAQueryFactory(em);

Now let’s create our first query:

QUser user = QUser.user; User c = queryFactory.selectFrom(user) .where(user.login.eq("David")) .fetchOne();

Notice we’ve defined a local variable QUser user and initialized it with QUser.user static instance. This is done purely for brevity, alternatively you may import the static QUser.user field.

The selectFrom method of the JPAQueryFactory starts building a query. We pass it the QUser instance and continue building the conditional clause of the query with the .where() method. The user.login is a reference to a StringPath field of the QUser class that we’ve seen before. The StringPath object also has the .eq() method that allows to fluently continue building the query by specifying the field equality condition.

Finally, to fetch the value from the database into persistence context, we end the building chain with the call to the fetchOne() method. This method returns null if the object can’t be found, but throws a NonUniqueResultException if there are multiple entities satisfying the .where() condition.

4.2. Ordering and Grouping

Now let’s fetch all users in a list, sorted by their login in ascension order.

List c = queryFactory.selectFrom(user) .orderBy(user.login.asc()) .fetch();

This syntax is possible because the *Path classes have the .asc() and .desc() methods. You can also specify several arguments for the .orderBy() method to sort by multiple fields.

Now let’s try something more difficult. Suppose we need to group all posts by title and count duplicating titles. This is done with the .groupBy() clause. We’ll also want to order the titles by resulting occurrence count.

NumberPath count = Expressions.numberPath(Long.class, "c"); List userTitleCounts = queryFactory.select( blogPost.title, blogPost.id.count().as(count)) .from(blogPost) .groupBy(blogPost.title) .orderBy(count.desc()) .fetch();

We selected the blog post title and count of duplicates, grouping by title and then ordering by aggregated count. Notice we first created an alias for the count() field in the .select() clause, because we needed to reference it in the .orderBy() clause.

4.3. Complex Queries With Joins and Subqueries

Let’s find all users that wrote a post titled “Hello World!” For such query we could use an inner join. Notice we’ve created an alias blogPost for the joined table to reference it in the .on() clause:

QBlogPost blogPost = QBlogPost.blogPost; List users = queryFactory.selectFrom(user) .innerJoin(user.blogPosts, blogPost) .on(blogPost.title.eq("Hello World!")) .fetch();

Now let’s try to achieve the same with subquery:

List users = queryFactory.selectFrom(user) .where(user.id.in( JPAExpressions.select(blogPost.user.id) .from(blogPost) .where(blogPost.title.eq("Hello World!")))) .fetch();

As we can see, subqueries are very similar to queries, and they are also quite readable, but they start with JPAExpressions factory methods. To connect subqueries with the main query, as always, we reference the aliases defined and used earlier.

4.4. Modifying Data

JPAQueryFactory allows not only constructing queries, but also modifying and deleting records. Let’s change the user's login and disable the account:

queryFactory.update(user) .where(user.login.eq("Ash")) .set(user.login, "Ash2") .set(user.disabled, true) .execute();

We can have any number of .set() clauses we want for different fields. The .where() clause is not necessary, so we can update all the records at once.

To delete the records matching a certain condition, we can use a similar syntax:

queryFactory.delete(user) .where(user.login.eq("David")) .execute();

The .where() clause is also not necessary, but be careful, because omitting the .where() clause results in deleting all of the entities of a certain type.

You may wonder, why JPAQueryFactory doesn’t have the .insert() method. This is a limitation of JPA Query interface. The underlying javax.persistence.Query.executeUpdate() method is capable of executing update and delete but not insert statements. To insert data, you should simply persist the entities with EntityManager.

If you still want to take advantage of a similar Querydsl syntax for inserting data, you should use SQLQueryFactory class that resides in the querydsl-sql library.

5. Conclusion

In this article we’ve discovered a powerful and type-safe API for persistent object manipulation that is provided by Querydsl.

Kami telah belajar menambahkan Querydsl ke proyek dan menjelajahi tipe-Q yang dihasilkan. Kami juga telah membahas beberapa kasus penggunaan umum dan menikmati keringkasan dan keterbacaannya.

Semua kode sumber untuk contoh dapat ditemukan di repositori github.

Terakhir, tentu saja ada lebih banyak fitur yang disediakan Querydsl, termasuk bekerja dengan SQL mentah, koleksi non-persisten, database NoSQL, dan pencarian teks lengkap - dan kami akan menjelajahi beberapa di antaranya di artikel mendatang.