Proyeksi JPA / Hibernate

1. Ikhtisar

Dalam tutorial ini, kita akan belajar bagaimana memproyeksikan properti entitas menggunakan JPA dan Hibernate .

2. Entitas

Pertama, mari kita lihat entitas yang akan kita gunakan di seluruh artikel ini:

@Entity public class Product { @Id private long id; private String name; private String description; private String category; private BigDecimal unitPrice; // setters and getters }

Ini adalah kelas entitas sederhana yang mewakili produk dengan berbagai properti.

3. Proyeksi JPA

Meskipun spesifikasi JPA tidak menyebutkan proyeksi secara eksplisit, ada banyak kasus di mana kami menemukannya dalam konsep.

Biasanya, kueri JPQL memiliki kelas entitas kandidat. Kueri, saat dieksekusi, membuat objek dari kelas kandidat - mengisi semua propertinya menggunakan data yang diambil.

Tapi, dimungkinkan untuk mengambil subset dari properti entitas, atau, yaitu, proyeksi data kolom.

Selain data kolom, kita juga dapat memproyeksikan hasil fungsi pengelompokan.

3.1. Proyeksi Kolom Tunggal

Misalkan kita ingin membuat daftar nama semua produk. Di JPQL, kita dapat melakukan ini dengan hanya memasukkan nama di klausa pilih :

Query query = entityManager.createQuery("select name from Product"); List resultList = query.getResultList();

Atau, kita dapat melakukan hal yang sama dengan CriteriaBuilder :

CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery query = builder.createQuery(String.class); Root product = query.from(Product.class); query.select(product.get("name")); List resultList = entityManager.createQuery(query).getResultList();

Karena kita memproyeksikan satu kolom yang kebetulan tipe String , kami berharap untuk mendapatkan daftar String s dalam hasil. Oleh karena itu, kami telah menetapkan kelas kandidat sebagai String dalam metode createQuery () .

Karena kami ingin memproyeksikan pada satu properti, kami telah menggunakan metode Query.select () . Apa yang terjadi di sini adalah properti mana yang kita inginkan, jadi dalam kasus kita, kita akan menggunakan properti nama dari entitas Produk kita .

Sekarang, mari kita lihat contoh keluaran yang dihasilkan oleh dua kueri di atas:

Product Name 1 Product Name 2 Product Name 3 Product Name 4

Perhatikan bahwa jika kita menggunakan properti id dalam proyeksi alih-alih nama , kueri akan mengembalikan daftar objek Long .

3.2. Proyeksi Multi-Kolom

Untuk memproyeksikan pada banyak kolom menggunakan JPQL, kita hanya perlu menambahkan semua kolom yang diperlukan ke klausa pemilihan :

Query query = session.createQuery("select id, name, unitPrice from Product"); List resultList = query.getResultList();

Namun, saat menggunakan CriteriaBuilder , kita harus melakukan hal-hal yang sedikit berbeda:

CriteriaBuilder builder = session.getCriteriaBuilder(); CriteriaQuery query = builder.createQuery(Object[].class); Root product = query.from(Product.class); query.multiselect(product.get("id"), product.get("name"), product.get("unitPrice")); List resultList = entityManager.createQuery(query).getResultList();

Di sini, kami telah menggunakan metode multiselect () alih-alih select () . Dengan menggunakan metode ini, kita dapat menentukan beberapa item untuk dipilih.

Perubahan signifikan lainnya adalah penggunaan Object [] . Saat kita memilih beberapa item, kueri mengembalikan larik objek dengan nilai untuk setiap item yang diproyeksikan. Ini juga terjadi dengan JPQL.

Mari kita lihat seperti apa datanya saat kita mencetaknya:

[1, Product Name 1, 1.40] [2, Product Name 2, 4.30] [3, Product Name 3, 14.00] [4, Product Name 4, 3.90]

Seperti yang bisa kita lihat, data yang dikembalikan agak rumit untuk diproses. Tapi, untungnya, kita bisa mendapatkan JPA untuk mengisi data ini ke dalam kelas khusus.

Selain itu, kita bisa menggunakan CriteriaBuilder.tuple () atau CriteriaBuilder.construct () untuk mendapatkan hasil sebagai daftar objek Tuple atau objek dari kelas kustom masing-masing.

3.3. Memproyeksikan Fungsi Agregat

Terlepas dari data kolom, terkadang kami ingin mengelompokkan data dan menggunakan fungsi agregat, seperti menghitung dan rata - rata.

Katakanlah kita ingin mencari jumlah produk di setiap kategori. Kita bisa melakukan ini menggunakan fungsi agregat count () di JPQL:

Query query = entityManager.createQuery("select p.category, count(p) from Product p group by p.category");

Atau kita bisa menggunakan CriteriaBuilder :

CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery query = builder.createQuery(Object[].class); Root product = query.from(Product.class); query.multiselect(product.get("category"), builder.count(product)); query.groupBy(product.get("category"));

Di sini, kita telah menggunakan CriteriaBuilder 's count () metode.

Menggunakan salah satu cara di atas akan menghasilkan daftar larik objek:

[category1, 2] [category2, 1] [category3, 1]

Apart from count(), CriteriaBuilder provides various other aggregate functions:

  • avg – Calculates the average value for a column in a group
  • max – Calculates the maximum value for a column in a group
  • min – Calculates the minimum value for a column in a group
  • least – Finds the least of the column values (for example, alphabetically or by date)
  • sum – Calculates the sum of the column values in a group

4. Hibernate Projections

Unlike JPA, Hibernate provides org.hibernate.criterion.Projection for projecting with a Criteria query. It also provides a class called org.hibernate.criterion.Projections, a factory for Projection instances.

4.1. Single-Column Projections

First, let's see how we can project a single column. We'll use the example we saw earlier:

Criteria criteria = session.createCriteria(Product.class); criteria = criteria.setProjection(Projections.property("name")); 

We've used the Criteria.setProjection() method to specify the property that we want in the query result. Projections.property() does the same work for us as Root.get() did when indicating the column to select.

4.2. Multi-Column Projections

To project multiple columns, we'll have to first create a ProjectionList. ProjectionList is a special kind of Projection that wraps other projections to allow selecting multiple values.

We can create a ProjectionListusing the Projections.projectionList() method, like showing the Product‘s id and name:

Criteria criteria = session.createCriteria(Product.class); criteria = criteria.setProjection( Projections.projectionList() .add(Projections.id()) .add(Projections.property("name")));

4.3. Projecting Aggregate Functions

Just like CriteriaBuilder, the Projections class also provides methods for aggregate functions.

Let's see how we can implement the count example we saw earlier:

Criteria criteria = session.createCriteria(Product.class); criteria = criteria.setProjection( Projections.projectionList() .add(Projections.groupProperty("category")) .add(Projections.rowCount()));

It's important to note that we didn't directly specify the GROUP BY in the Criteria object. Calling groupProperty triggers this for us.

Apart from the rowCount() function, Projections also provides the aggregate functions we saw earlier.

4.4. Using an Alias for a Projection

An interesting feature of the Hibernate Criteria API is the use of an alias for a projection.

This is especially useful when using an aggregate function, as we can then refer to the alias in the Criterion and Order instances:

Criteria criteria = session.createCriteria(Product.class); criteria = criteria.setProjection(Projections.projectionList() .add(Projections.groupProperty("category")) .add(Projections.alias(Projections.rowCount(), "count"))); criteria.addOrder(Order.asc("count"));

5. Conclusion

In this article, we saw how to project entity properties using JPA and Hibernate.

Penting untuk dicatat bahwa Hibernate telah menghentikan API Kriteria dari versi 5.2 dan seterusnya untuk mendukung API KriteriaQuery JPA . Tapi, ini hanya karena tim Hibernate tidak punya waktu untuk menyimpan dua API yang berbeda, yang melakukan hal yang sama, sinkron.

Dan tentu saja, kode yang digunakan dalam artikel ini dapat ditemukan di GitHub.