Pengenalan kepada Querydsl

1. Pengenalan

Ini adalah artikel pengantar untuk membuat anda aktif dan aktif dengan Querydsl API yang kuat untuk ketekunan data.

Tujuannya di sini adalah untuk memberi anda alat praktikal untuk menambahkan Querydsl ke dalam projek anda, memahami struktur dan tujuan kelas yang dihasilkan, dan mendapatkan pemahaman asas tentang cara menulis pertanyaan pangkalan data yang selamat untuk senario yang paling biasa.

2. Tujuan Querydsl

Kerangka pemetaan objek-relasional merupakan teras Enterprise Java. Ini mengimbangi ketidakcocokan antara pendekatan berorientasikan objek dan model pangkalan data relasional. Mereka juga membolehkan pembangun menulis kod dan logik domain ketekunan yang lebih bersih dan ringkas.

Walau bagaimanapun, salah satu pilihan reka bentuk yang paling sukar untuk rangka kerja ORM adalah API untuk membina pertanyaan yang betul dan selamat digunakan.

Salah satu kerangka ORM Java yang paling banyak digunakan, Hibernate (dan juga standard JPA yang berkait rapat), mengusulkan bahasa pertanyaan berdasarkan rentetan HQL (JPQL) yang sangat serupa dengan SQL. Kelemahan yang jelas dari pendekatan ini adalah kurangnya keselamatan jenis dan ketiadaan pemeriksaan pertanyaan statik. Juga, dalam kes-kes yang lebih kompleks (misalnya, apabila pertanyaan perlu dibuat pada waktu berjalan bergantung pada beberapa keadaan), membina pertanyaan HQL biasanya melibatkan gabungan tali yang biasanya sangat tidak selamat dan rentan ralat.

Piawaian JPA 2.0 membawa peningkatan dalam bentuk Kriteria Query API - kaedah baru dan selamat untuk membuat pertanyaan yang memanfaatkan kelas metamodel yang dihasilkan semasa praprosesan anotasi. Sayangnya, sebagai inti intinya, Criteria Query API akhirnya sangat verbose dan praktikalnya tidak dapat dibaca. Berikut adalah contoh dari tutorial EE Jakarta untuk menghasilkan pertanyaan semudah 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 hairanlah bahawa perpustakaan Querydsl yang lebih pantas segera muncul, berdasarkan idea yang sama mengenai kelas metadata yang dihasilkan, namun dilaksanakan dengan API yang lancar dan mudah dibaca.

3. Penjanaan Kelas Querydsl

Mari kita mulakan dengan menghasilkan dan meneroka metaclasses ajaib yang menyumbang kepada API Querydsl yang fasih.

3.1. Menambah Querydsl ke Maven Build

Menyertakan Querydsl dalam projek anda semudah menambahkan beberapa kebergantungan pada fail binaan anda dan mengkonfigurasi pemalam untuk memproses anotasi JPA. Mari mulakan dengan kebergantungan. Versi perpustakaan Querydsl harus diekstrak ke harta yang terpisah di dalam bahagian, seperti berikut (untuk versi terbaru pustaka Querydsl, periksa repositori Maven Central):

 4.1.3 

Seterusnya, tambahkan kebergantungan berikut ke bahagian fail pom.xml anda :

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

The querydsl-apt pergantungan adalah alat pemprosesan anotasi (APT) - pelaksanaan sepadan Java API yang membolehkan pemprosesan penjelasan dalam fail sumber sebelum mereka bergerak ke peringkat penyusunan. Alat ini menghasilkan jenis-Q yang disebut - kelas yang secara langsung berkaitan dengan kelas entiti aplikasi anda, tetapi diawali dengan huruf Q. Sebagai contoh, jika anda mempunyai kelas Pengguna yang ditandai dengan anotasi @Entity dalam aplikasi anda, maka Q-type yang dihasilkan akan berada dalam fail sumber QUser.java .

Yang disediakan skop querydsl-apt cara pergantungan bahawa balang ini perlu disediakan hanya pada masa membina, tetapi tidak termasuk ke dalam artifak permohonan.

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

Untuk mengkonfigurasi pemalam pemprosesan anotasi yang memanfaatkan querydsl-apt , tambahkan konfigurasi pemalam berikut ke pom anda - di dalam unsur:

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

Plugin ini memastikan bahawa jenis Q dihasilkan semasa tujuan proses Maven build. The outputDirectory mata harta konfigurasi untuk direktori di mana fail sumber Q-jenis akan dijana. Nilai harta tanah ini akan berguna di kemudian hari, apabila anda akan meneroka fail Q.

Anda juga harus menambahkan direktori ini ke folder sumber projek, jika IDE anda tidak melakukan ini secara automatik - rujuk dokumentasi untuk IDE kegemaran anda mengenai cara melakukannya.

Untuk artikel ini, kami akan menggunakan model JPA ringkas dari perkhidmatan blog, yang terdiri daripada Pengguna dan BlogPost mereka dengan hubungan satu-ke-banyak 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 jenis Q untuk model anda, jalankan:

mvn compile

3.2. Meneroka Kelas yang Dihasilkan

Sekarang pergi ke direktori yang ditentukan dalam properti outputDirectory apt-maven-plugin ( target / dihasilkan-sumber / java dalam contoh kami) Anda akan melihat pakej dan struktur kelas yang secara langsung mencerminkan model domain anda, kecuali semua kelas bermula dengan huruf Q ( QUser dan QBlogPost dalam kes kami).

Open the file QUser.java. This is your entry point to building all queries that have User as a root entity. First thing you’ll notice is the @Generated annotation which means that this file was automatically generated and should not be edited manually. Should you change any of your domain model classes, you will have to run mvn compile again to regenerate all of the corresponding Q-types.

Aside from several QUser constructors present in this file, you should also take notice of a public static final instance of the QUser class:

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

This is the instance that you can use in most of your Querydsl queries to this entity, except when you need to write some more complex queries, like joining several different instances of a table in a single query.

The last thing that should be noted is that for every field of the entity class there is a corresponding *Path field in the Q-type, like NumberPath id, StringPath login and SetPath blogPosts in the QUser class (notice that the name of the field corresponding to Set is pluralized). These fields are used as parts of fluent query API that we will encounter later on.

4. Querying With Querydsl

4.1. Simple Querying and Filtering

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 untuk memproyeksikan dan meneroka jenis Q yang dihasilkan. Kami juga telah membahas beberapa kes penggunaan biasa dan menikmati kesederhanaan dan kesediaan membaca.

Semua kod sumber untuk contoh boleh didapati di repositori github.

Akhirnya, tentu saja terdapat lebih banyak lagi ciri yang disediakan oleh Querydsl, termasuk bekerja dengan SQL mentah, koleksi tidak berterusan, pangkalan data NoSQL dan carian teks penuh - dan kami akan menerokainya di artikel yang akan datang.