Inleiding tot zoeken in slaapstand

1. Overzicht

In dit artikel bespreken we de basisprincipes van Hibernate Search, hoe u deze configureert en implementeren we enkele eenvoudige zoekopdrachten.

2. Basisprincipes van zoeken in slaapstand

Wanneer we zoekfunctionaliteit in volledige tekst moeten implementeren, is het altijd een pluspunt om tools te gebruiken waarmee we al goed thuis zijn.

In het geval dat we Hibernate en JPA al gebruiken voor ORM, zijn we nog maar één stap verwijderd van Hibernate Search.

Hibernate Search integreert Apache Lucene, een krachtige en uitbreidbare full-text zoekmachine-bibliotheek geschreven in Java. Dit combineert de kracht van Lucene met de eenvoud van Hibernate en JPA.

Simpel gezegd, we hoeven alleen maar wat extra annotaties toe te voegen aan onze domeinklassen, en de tool zorgt voor zaken als database- / indexsynchronisatie.

Hibernate Search biedt ook een Elasticsearch-integratie; Omdat het zich echter nog in een experimenteel stadium bevindt, zullen we ons hier op Lucene concentreren.

3. Configuraties

3.1. Afhankelijkheden van Maven

Voordat we aan de slag gaan, moeten we eerst de nodige afhankelijkheden toevoegen aan onze pom.xml:

 org.hibernate hibernate-search-orm 5.8.2.Final 

Eenvoudigheidshalve gebruiken we H2 als onze database:

 com.h2database h2 1.4.196 

3.2. Configuraties

We moeten ook specificeren waar Lucene de index moet opslaan.

Dit kan via de accommodatie slaapstand.search.default.directory_provider.

We zullen kiezen bestandssysteem, wat de meest eenvoudige optie is voor onze use case. Meer opties staan ​​vermeld in de officiële documentatie. Bestandssysteem-master/bestandssysteem-slave en infinispan zijn opmerkelijk voor geclusterde toepassingen, waarbij de index tussen knooppunten moet worden gesynchroniseerd.

We moeten ook een standaard basismap definiëren waarin indexen worden opgeslagen:

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

4. De modelklassen

Na de configuratie zijn we nu klaar om ons model te specificeren.

Bovenop de PPV-annotaties @Entiteit en @Tafel, we moeten een @Indexed annotatie. Het vertelt Hibernate Search dat de entiteit Product zal worden geïndexeerd.

Daarna moeten we de vereiste attributen als doorzoekbaar definiëren door een @Veld annotatie:

@Entity @Indexed @Table (name = "product") openbare klasse Product {@Id privé int id; @Field (termVector = TermVector.YES) private String productName; @Field (termVector = TermVector.YES) private String beschrijving; @Field privé int geheugen; // getters, setters en constructeurs}

De termVector = TermVector.YES attribuut zal later vereist zijn voor de "More Like This" -query.

5. Opbouwen van de Lucene Index

Voordat u met de eigenlijke zoekopdrachten begint, we moeten Lucene activeren om de index in eerste instantie op te bouwen:

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

Na deze eerste build zorgt Hibernate Search ervoor dat de index up-to-date blijft. Ik e. we kunnen entiteiten maken, manipuleren en verwijderen via de EntityManager zoals gewoonlijk.

Opmerking: we moeten ervoor zorgen dat entiteiten volledig toegewijd zijn aan de database voordat ze kunnen worden ontdekt en geïndexeerd door Lucene (dit is trouwens ook de reden waarom de eerste import van testgegevens in onze voorbeeldcodetestcases wordt geleverd in een speciale JUnit-testcase, geannoteerd met @Commit).

6. Opstellen en uitvoeren van query's

Nu zijn we klaar om onze eerste query te maken.

In de volgende sectie, we laten de algemene workflow zien voor het voorbereiden en uitvoeren van een query.

Daarna maken we enkele voorbeeldquery's voor de belangrijkste querytypen.

6.1. Algemene workflow voor het maken en uitvoeren van een query

Het voorbereiden en uitvoeren van een query bestaat in het algemeen uit vier stappen:

In stap 1 moeten we een PPV krijgen FullTextEntityManager en van daaruit a QueryBuilder:

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

In stap 2 zullen we een Lucene-query maken via de Hibernate-query DSL:

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

In stap 3 verpakken we de Lucene-query in een Hibernate-query:

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

Ten slotte voeren we in stap 4 de query uit:

Lijstresultaten = jpaQuery.getResultList ();

Opmerking: standaard sorteert Lucene de resultaten op relevantie.

Stappen 1, 3 en 4 zijn hetzelfde voor alle typen zoekopdrachten.

In het volgende zullen we ons concentreren op stap 2, i. e. hoe u verschillende soorten query's kunt maken.

6.2. Trefwoordquery's

De meest basale use-case is zoeken naar een specifiek woord.

Dit is wat we eigenlijk al deden in de vorige sectie:

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

Hier, trefwoord () geeft aan dat we op zoek zijn naar een specifiek woord, op het veld() vertelt Lucene waar ze moet zoeken en bij elkaar passen() waarnaar te zoeken.

6.3. Fuzzy Queries

Fuzzy-zoekopdrachten werken als zoekopdrachten met trefwoorden, behalve dat we kunnen een limiet van 'vaagheid' definiëren, waarboven Lucene de twee voorwaarden als overeenkomend zal accepteren.

Door withEditDistanceUpTo (), we kunnen bepalen hoeveel een term mag afwijken van de andere. Het kan worden ingesteld op 0, 1 en 2, waarbij de standaardwaarde 2 (Opmerking: deze beperking is afkomstig van de implementatie van Lucene).

Door metPrefixLength (), kunnen we de lengte van het voorvoegsel definiëren die genegeerd zal worden door de vaagheid:

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

6.4. Jokertekens

Hibernate Search stelt ons ook in staat om jokertekens uit te voeren, i. e. zoekopdrachten waarvan een deel van een woord onbekend is.

Hiervoor kunnen we '?” voor één teken, en "*” voor elke tekenreeks:

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

6.5. Zinsneden

Als we op meer dan één woord willen zoeken, kunnen we zinsdeelzoekopdrachten gebruiken. We kunnen beide kijken voor exacte of benaderende zinnen, gebruik makend van uitdrukking() en metSlop (), indien nodig. De slop-factor definieert het aantal andere woorden dat in de zin is toegestaan:

Query phraseQuery = queryBuilder .phrase () .withSlop (1) .onField ("description") .sentence ("met draadloos opladen") .createQuery ();

6.6. Eenvoudige Query String Queries

Bij de vorige zoekopdrachttypen moesten we het zoekopdrachttype expliciet specificeren.

Als we de gebruiker wat meer macht willen geven, kunnen we eenvoudige queryreeksen gebruiken: daardoor kan hij zijn eigen queries tijdens runtime definiëren.

De volgende soorten zoekopdrachten worden ondersteund:

  • boolean (EN met "+", OF met "|", NIET met "-")
  • voorvoegsel (voorvoegsel *)
  • zin ("een zin")
  • prioriteit (met haakjes)
  • vaag (vaag ~ 2)
  • near-operator voor zoekopdrachten naar woordgroepen ("een woordgroep" ~ 3)

In het volgende voorbeeld worden fuzzy-, zin- en booleaanse zoekopdrachten gecombineerd:

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

6.7. Bereikquery's

Bereikquery's zoeken naar eenwaarde tussen gegeven grenzen. Dit kan worden toegepast op getallen, datums, tijdstempels en tekenreeksen:

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

6.8. Meer zoals deze vragen

Ons laatste type zoekopdracht is de "Meer zoals dit”- vraag. Hiervoor bieden we een entiteit, en Hibernate Search retourneert een lijst met vergelijkbare entiteiten, elk met een gelijkenisscore.

Zoals eerder vermeld, de termVector = TermVector.YES attribuut in onze modelklasse is vereist voor dit geval: het vertelt Lucene om de frequentie voor elke term op te slaan tijdens het indexeren.

Op basis hiervan wordt de gelijkenis berekend op het moment dat de query wordt uitgevoerd:

Vraag moreLikeThisQuery = queryBuilder .moreLikeThis () .comparingField ("productName"). BoostedTo (10f) .andField ("description"). BoostedTo (1f) .toEntity (entiteit) .createQuery (); Lijstresultaten = (Lijst) fullTextEntityManager .createFullTextQuery (moreLikeThisQuery, Product.class) .setProjection (ProjectionConstants.THIS, ProjectionConstants.SCORE) .getResultList ();

6.9. Zoeken in meer dan één veld

Tot nu toe hebben we alleen zoekopdrachten gemaakt voor het zoeken naar één kenmerk, met behulp van op het veld().

Afhankelijk van de use case, we kunnen ook zoeken naar twee of meer attributen:

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

Bovendien, we kunnen elk kenmerk specificeren om afzonderlijk te zoeken, e. g. als we een boost willen definiëren voor één attribuut:

Vraag moreLikeThisQuery = queryBuilder .moreLikeThis () .comparingField ("productName"). BoostedTo (10f) .andField ("description"). BoostedTo (1f) .toEntity (entiteit) .createQuery ();

6.10. Query's combineren

Ten slotte ondersteunt Hibernate Search ook het combineren van zoekopdrachten met behulp van verschillende strategieën:

  • MOET: de query moet de overeenkomende elementen van de subquery bevatten
  • MOET: de query moet de overeenkomende elementen van de subquery bevatten
  • MOET NIET: de query mag niet de overeenkomende elementen van de subquery bevatten

De aggregaties zijn vergelijkbaar met de booleaanse EN, OF en NIET. De namen zijn echter anders om te benadrukken dat ze ook een impact hebben op de relevantie.

Bijvoorbeeld een MOET tussen twee zoekopdrachten is vergelijkbaar met boolean OF: als een van de twee zoekopdrachten een overeenkomst heeft, wordt deze overeenkomst geretourneerd.

Als beide zoekopdrachten echter overeenkomen, heeft de overeenkomst een hogere relevantie dan wanneer slechts één zoekopdracht overeenkomt:

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

7. Conclusie

In dit artikel hebben we de basisprincipes van Hibernate Search besproken en laten zien hoe de belangrijkste typen zoekopdrachten kunnen worden geïmplementeerd. Meer geavanceerde onderwerpen zijn te vinden in de officiële documentatie.

Zoals altijd is de volledige broncode van de voorbeelden beschikbaar op GitHub.