Pengantar Pencarian Hibernasi

1. Ikhtisar

Di artikel ini, kita akan membahas dasar-dasar Pencarian Hibernasi, cara mengkonfigurasinya, dan kita akan menerapkan beberapa kueri sederhana.

2. Dasar-dasar Pencarian Hibernasi

Kapan pun kami harus menerapkan fungsi pencarian teks lengkap, menggunakan alat yang sudah kami kuasai selalu merupakan nilai tambah.

Jika kami sudah menggunakan Hibernate dan JPA untuk ORM, kami hanya selangkah lagi dari Hibernate Search.

Hibernate Search mengintegrasikan Apache Lucene, pustaka mesin pencari teks lengkap berperforma tinggi dan dapat diperluas yang ditulis dalam Java . Ini menggabungkan kekuatan Lucene dengan kesederhanaan Hibernate dan JPA.

Sederhananya, kita hanya perlu menambahkan beberapa penjelasan tambahan ke kelas domain kita, dan alat tersebut akan menangani hal-hal seperti sinkronisasi database / indeks.

Hibernate Search juga menyediakan integrasi Elasticsearch; Namun, karena ini masih dalam tahap percobaan, kami akan fokus pada Lucene di sini.

3. Konfigurasi

3.1. Dependensi Maven

Sebelum memulai, pertama-tama kita perlu menambahkan dependensi yang diperlukan ke pom.xml kita :

 org.hibernate hibernate-search-orm 5.8.2.Final 

Demi kesederhanaan, kami akan menggunakan H2 sebagai database kami:

 com.h2database h2 1.4.196 

3.2. Konfigurasi

Kami juga harus menentukan di mana Lucene harus menyimpan indeks.

Ini dapat dilakukan melalui properti hibernate.search.default.directory_provider .

Kami akan memilih sistem file , yang merupakan opsi paling mudah untuk kasus penggunaan kami. Opsi lainnya tercantum dalam dokumentasi resmi. Filesystem-master / filesystem-slave dan infinispan penting untuk aplikasi berkerumun, di mana indeks harus disinkronkan antar node.

Kami juga harus menentukan direktori dasar default di mana indeks akan disimpan:

hibernate.search.default.directory_provider = filesystem hibernate.search.default.indexBase = /data/index/default

4. Kelas Model

Setelah konfigurasi, kami sekarang siap untuk menentukan model kami.

Di atas JPA penjelasan @ Entity dan @ table , kita harus menambahkan @Indexed penjelasan. Ini memberitahu Hibernate Search bahwa Produk entitas akan diindeks.

Setelah itu, kita harus mendefinisikan atribut yang diperlukan sebagai dapat dicari dengan menambahkan anotasi @Field :

@Entity @Indexed @Table(name = "product") public class Product { @Id private int id; @Field(termVector = TermVector.YES) private String productName; @Field(termVector = TermVector.YES) private String description; @Field private int memory; // getters, setters, and constructors }

The termVector = TermVector.YES atribut akan diminta untuk “Lebih Seperti Ini” query kemudian.

5. Membangun Indeks Lucene

Sebelum memulai kueri sebenarnya, kita harus memicu Lucene untuk membuat indeks terlebih dahulu :

FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager); fullTextEntityManager.createIndexer().startAndWait();

Setelah pembuatan awal ini, Hibernate Search akan menjaga indeks tetap mutakhir . I. e. kita dapat membuat, memanipulasi dan menghapus entitas melalui EntityManager seperti biasa.

Catatan: kita harus memastikan bahwa entitas berkomitmen penuh ke database sebelum mereka dapat ditemukan dan diindeks oleh Lucene (omong-omong, ini juga alasan mengapa impor data uji awal dalam contoh kasus uji kode kami hadir dalam JUnit khusus kasus uji, dianotasi dengan @Commit ).

6. Membangun dan Menjalankan Query

Sekarang, kami siap untuk membuat kueri pertama kami.

Di bagian berikut, kami akan menunjukkan alur kerja umum untuk mempersiapkan dan menjalankan kueri.

Setelah itu, kami akan membuat beberapa contoh kueri untuk jenis kueri yang paling penting.

6.1. Alur Kerja Umum untuk Membuat dan Menjalankan Query

Mempersiapkan dan menjalankan kueri secara umum terdiri dari empat langkah :

Pada langkah 1, kita harus mendapatkan JPA FullTextEntityManager dan dari situ QueryBuilder :

FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager); QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory() .buildQueryBuilder() .forEntity(Product.class) .get();

Pada langkah 2, kita akan membuat kueri Lucene melalui kueri Hibernate DSL:

org.apache.lucene.search.Query query = queryBuilder .keyword() .onField("productName") .matching("iphone") .createQuery();

Pada langkah 3, kita akan membungkus kueri Lucene menjadi kueri Hibernate:

org.hibernate.search.jpa.FullTextQuery jpaQuery = fullTextEntityManager.createFullTextQuery(query, Product.class);

Terakhir, di langkah 4 kita akan menjalankan kueri:

List results = jpaQuery.getResultList();

Catatan : secara default, Lucene mengurutkan hasil menurut relevansi.

Langkah 1, 3 dan 4 sama untuk semua tipe kueri.

Berikut ini, kita akan fokus pada langkah 2, yaitu bagaimana membuat tipe query yang berbeda.

6.2. Kueri Kata Kunci

Kasus penggunaan paling dasar adalah mencari kata tertentu .

Inilah yang sebenarnya sudah kami lakukan di bagian sebelumnya:

Query keywordQuery = queryBuilder .keyword() .onField("productName") .matching("iphone") .createQuery();

Here, keyword() specifies that we are looking for one specific word, onField() tells Lucene where to look and matching() what to look for.

6.3. Fuzzy Queries

Fuzzy queries are working like keyword queries, except that we can define a limit of “fuzziness”, above which Lucene shall accept the two terms as matching.

By withEditDistanceUpTo(), we can define how much a term may deviate from the other. It can be set to 0, 1, and 2, whereby the default value is 2 (note: this limitation is coming from the Lucene's implementation).

By withPrefixLength(), we can define the length of the prefix which shall be ignored by the fuzziness:

Query fuzzyQuery = queryBuilder .keyword() .fuzzy() .withEditDistanceUpTo(2) .withPrefixLength(0) .onField("productName") .matching("iPhaen") .createQuery();

6.4. Wildcard Queries

Hibernate Search also enables us to execute wildcard queries, i. e. queries for which a part of a word is unknown.

For this, we can use “?” for a single character, and “*” for any character sequence:

Query wildcardQuery = queryBuilder .keyword() .wildcard() .onField("productName") .matching("Z*") .createQuery();

6.5. Phrase Queries

If we want to search for more than one word, we can use phrase queries. We can either look for exact or for approximate sentences, using phrase() and withSlop(), if necessary. The slop factor defines the number of other words permitted in the sentence:

Query phraseQuery = queryBuilder .phrase() .withSlop(1) .onField("description") .sentence("with wireless charging") .createQuery();

6.6. Simple Query String Queries

With the previous query types, we had to specify the query type explicitly.

If we want to give some more power to the user, we can use simple query string queries: by that, he can define his own queries at runtime.

The following query types are supported:

  • boolean (AND using “+”, OR using “|”, NOT using “-“)
  • prefix (prefix*)
  • phrase (“some phrase”)
  • precedence (using parentheses)
  • fuzzy (fuzy~2)
  • near operator for phrase queries (“some phrase”~3)

The following example would combine fuzzy, phrase and boolean queries:

Query simpleQueryStringQuery = queryBuilder .simpleQueryString() .onFields("productName", "description") .matching("Aple~2 + \"iPhone X\" + (256 | 128)") .createQuery();

6.7. Range Queries

Range queries search for avalue in between given boundaries. This can be applied to numbers, dates, timestamps, and strings:

Query rangeQuery = queryBuilder .range() .onField("memory") .from(64).to(256) .createQuery();

6.8. More Like This Queries

Our last query type is the “More Like This” – query. For this, we provide an entity, and Hibernate Search returns a list with similar entities, each with a similarity score.

As mentioned before, the termVector = TermVector.YES attribute in our model class is required for this case: it tells Lucene to store the frequency for each term during indexing.

Based on this, the similarity will be calculated at query execution time:

Query moreLikeThisQuery = queryBuilder .moreLikeThis() .comparingField("productName").boostedTo(10f) .andField("description").boostedTo(1f) .toEntity(entity) .createQuery(); List results = (List) fullTextEntityManager .createFullTextQuery(moreLikeThisQuery, Product.class) .setProjection(ProjectionConstants.THIS, ProjectionConstants.SCORE) .getResultList();

6.9. Searching More Than One Field

Until now, we only created queries for searching one attribute, using onField().

Depending on the use case, we can also search two or more attributes:

Query luceneQuery = queryBuilder .keyword() .onFields("productName", "description") .matching(text) .createQuery();

Moreover, we can specify each attribute to be searched separately, e. g. if we want to define a boost for one attribute:

Query moreLikeThisQuery = queryBuilder .moreLikeThis() .comparingField("productName").boostedTo(10f) .andField("description").boostedTo(1f) .toEntity(entity) .createQuery();

6.10. Combining Queries

Finally, Hibernate Search also supports combining queries using various strategies:

  • SHOULD: the query should contain the matching elements of the subquery
  • MUST: the query must contain the matching elements of the subquery
  • MUST NOT: the query must not contain the matching elements of the subquery

The aggregations are similar to the boolean ones AND, OR and NOT. However, the names are different to emphasize that they also have an impact on the relevance.

Misalnya, HARUS antara dua kueri serupa dengan boolean ATAU: jika salah satu dari dua kueri memiliki kecocokan, kecocokan ini akan dikembalikan.

Namun, jika kedua kueri cocok, kecocokan akan memiliki relevansi yang lebih tinggi dibandingkan jika hanya satu kueri yang cocok:

Query combinedQuery = queryBuilder .bool() .must(queryBuilder.keyword() .onField("productName").matching("apple") .createQuery()) .must(queryBuilder.range() .onField("memory").from(64).to(256) .createQuery()) .should(queryBuilder.phrase() .onField("description").sentence("face id") .createQuery()) .must(queryBuilder.keyword() .onField("productName").matching("samsung") .createQuery()) .not() .createQuery();

7. Kesimpulan

Di artikel ini, kami membahas dasar-dasar Pencarian Hibernasi dan menunjukkan cara menerapkan jenis kueri yang paling penting. Topik lebih lanjut dapat ditemukan di dokumentasi resmi.

Seperti biasa, kode sumber lengkap dari contoh tersedia di GitHub.