parszab.hu Introduction in English.
  • Főoldal
  • Tanulmányok
  • SysAdmin
  • Java EE
    Java EE
    • SVN demók
    • Design patternek
    • Web Start intra...
    • H2 RDBMS
    • JasperReports
    • Abbot egyedül
    • Spring Framework
    • Hibernate Search
  • Albumok

Hibernate Search

Tartalom

  • 1.   Az alapok
  • 2.   A demó
  • 3.   Entitások indexelése
  • 4.   Keresés és lekérdezések
  • 5.   Luke

A Hibernate Search egy a Hibernate csapat által készített eszköz, amelynek célja, hogy összekösse a objektum-relációs leképezés (ORM) és a szöveg alapú keresés világát. Utóbbinak legismertebb polgára az Apache Lucene, amelyre a Hibernate Search is épül. Az alábbi ismertetőben ennek a használatába szeretnék bevezetést kínálni.

Az itt bemutatott példához ismét tartozik futtatható demó, amely SVN-ből tölthető le.

1.   Az alapok

A Hibernate projekt zászlóshajója a Hibernate ORM eszköz. Segítségével java objektumaink és az adatbázis táblái közötti relációt kezelhetjük, úgy, hogy végeredményben az adatbázissal lehet, hogy nem is kell foglalkoznunk: végig megmaradhatunk a programozási nyelv objektum orientált tartományában. Az adatbázis elérés konfigurálása után elég, ha POJO (Plain Old Java Object) enitásainkat (perzisztens objektumainkat) lekódoljuk, a megfelelő annotációkkal ellátjuk: innentől kezdve a perzisztencia eszköz felé definiált interfészen keresztül (Hibernate Session vagy Entity Manager a JPA szabványban) menthetjük, beolvashatjuk, frissíthetjük vagy törölhetjk (CRUD) az objektumokat. Nem kis részben a Hibernate sikerének és személyesen fejlesztőinek is köszönhető, hogy az ORM filozófia a Java EE világába is utat talált, mikor a JPA leváltotta 2.1-es EJB-ig élő, ám általánosan nehézkesnek tartott EntityBean kezelést.

Az objektum-relációs leképezésre szolgáló eszközök használata során a két világ közötti koncepcionális különbségekből bizonyos problémák adódnak, amelyekkel nem árt, ha tisztában vagyunk. Egyik tipikus példa, hogy a relációs modell nem ismeri a leszármazás fogalmát, amely az objektum-orientált programozás egyik alapvető eleme. Ennek kezelésére az adatbázis szintjén definiálnunk kell, hogyan kívánjuk a leszármazott objektumokat tárolni (közös táblában, illetve külön vagy kapcsolt táblákban), és ez a kezdeti döntésünk hatással lesz alkalmazásunk majdani működésére is (pl. teljesítményben). Hasonló elvi probléma, hogy a relációs lekérdezések nyelve sokkal hatékonyabb adathozzáférést biztosít, amennyiben egyszerre különböző adatcsoportokból (tábla v. objektum) is szeretnénk adatokat lekérdezni. Vegyünk példaképp két sok oszlopos táblát: customer és address. SQL-ben hatékonyan le tudjuk kérdezni például azt két oszlopban, melyik vásárló melyik városban lakik. Ez az objektumok világában sokkal nehézkesebb: a vásárló objektum lehet, hogy egy több mélységű objektum gráf gyökere, melyeket paramétereikkel együtt a példányosítás során szintén fel kell tölteni. Ahhoz, hogy a vásárlók + városok (vagy bizonyos városokban lakó vásárlók) listájához hozzájussunk, elképzelhető, hogy számos objektumot példányosítanunk kell (pl. a nem releváns találatokat és nem releváns attribútumokat is) az adatbázis adatai alapján, és így sokkal költségesebb lesz a művelet.

Persze bizonyos mértékben itt is van lehetőség optimalizálásra, de az elvi probléma akkor is megmarad: hatékony lekérdezéseket nem lehet az objektumok szintjén definiálni és végrehajtani. Részben ennek a kezelésére is megoldást kínál a Hibernate Search, amelynek segítésével entitásainkat indexek formájában, hatékonyan kereshető módon tudjuk elmenteni és lekérdezni. Ezek az indexek denormalizálják az adatokat, minden egyes keresni kívánt objektum gráfja (a keresés tervezett mélységének megfelelően) egyetlen kereshető, szöveg alapú Lucene dokumentumba kerül. Az így felépülő indexen futtathatjuk a szöveges keresést, és a találatokból visszakapott ID-k alapján tudhatjuk, melyek a megfelelt, és immár példányosítható entitások. A Hibernate Search adaléka a Lucenehez (amely az indexelés és a keresés logikáját már megvalósítja), hogy az indexelést egyszerűen, az entitásaink (@Entity-k) annotálásával érhetjük el, illetve hogy az indexben történt keresés után egy Entity Manager wrapperen keresztül egyből a perzisztens adatokból feltöltött entityket kapjuk vissza, nem kell ezzel a kódolás során foglalkoznunk.

Az online dokumentációban található ábra jól szemlélteti a működést: a kliensek az index felé futtatnak egy keresést, majd az ID alapján az adatbázisból az eszköz lekéri az perzisztens adatokat és átadja a felöltött @Entity-ket:

Lucene és Hibernate Search

Számos adatbáziskezelő biztosít egyébként hasonló full text search megoldást (pl. Oracle, PostgreSQL), ezek használatához azonban RDBMS specifikus kódot kell írnunk, elvész a transzparencia az alkalmazás felé, és a kód nem lesz hordozható. Illetve az ORM előnyei is nagyrészt elvesznek.

2.   A demó

Hibernate Search demó

A leírás megértéséhez készített kis demó alkalmazást bárki letöltheti SVN-ből, és maga is kipróbálhatja. Javaslom, hogy tegye is! Sokkal hamarabb lesz világos minden a forráskódot olvasva.

A működés a következő: A demó a harmadik fülön érhető el. Az alkalmazás indításakor a program inicializálja a Spring konténert, amely a Hibernate ORM beállításáért és kezeléséért felelős (hogy hogyan történik mindez, arról itt található ismertető). Ennek során a build könyvtárban létre is jön egy H2 adatbázis (hstest könyvtár) a táblákkal együtt, ahova az entitásainkat elmentjük majd. (Ha bármikor új adatbázissal szeretnéd kipróbálni a demót, akkor azt kell törölni.) Az adatok létrehozásához az Adatfeltöltés gombot kell benyomni, ekkor a HsData osztályban található kód fut le, amely létrehoz 500 Article entitást, a kapcsolódó objektumokkal együtt. Ez bármikor újra futtatható több adat kreálásához. Mikor elmentődnek az egyes entitások, akkor a Hibernate Search indexeli is őket -- az index szintén a build könyvtárba kerül. (build/lucene -- újrakezdésnél ezt is törölni kell!)

A létrehozott indexben a felületen beírt szövegre tudunk keresni. A Lekérdezés gomb segítségével klasszikus Hibernate Search queryt futtatunk, amely a talált objektumokat (teszthez illő módon elég ésszerűtlenül) mind példányosítja, és azokat adja vissza. A Projection gomb segítségével ú.n. projectiont futtathatunk, amely csak a Lucene indexig jut el, és a találatokra view objektumot ad vissza, amit mi a felületen megjeleníthetnénk (pl. egy JTable-ben). Minél több az adatunk, látjuk, annál hatékonyabb a projection -- tegyük azonban hozzá, ez az előny elvész, ha sima lekérdezés esetén pagináljuk (laponként, kisebb adagokban kérjük le) a találati listát, ilyenkor ugyanis kevesebb (pl. 10-20) példány lesz majd csak létrehozva egyszerre.

A felülete kiírtjuk még az egyes műveletek futási idejét, hogy könnyebb legyen az összehasonlítás.

3.   Entitások indexelése

Hogy mindez így szépen működni tudjon, az első lépés (ha a nulladik az alap alkalmazás: felület, JPA, entitások stb. összerakása) az indexelés beállítása.

Lássuk először is, hogy mi az, amire majd keresni szeretnénk, lássuk az entity csomag tartalmát:

hu.parszab.javaee.hsearch.entity

Kitalált indexelendő entitásunk az Article lesz, amelyet az alkalmazás számára kereshetővé kell tenni. Minden Article tartalmazza címét, szövegét, illetve egyéb objektumokkal definiált kapcsolatait. (A text mezőt nem töltöm fel a teszt során, ahhoz nagyobb szótár kellene, de ennek a mi szempontunkból nem is lesz igazán jelentősége.) Hogy többféle asszociációs forma indexelését is kipróbálhassunk, kétféle @Embeddable objektumot kapcsoltunk a cikkekhez, illetve két külön perzisztens osztályt (@Entity) is. Utóbbiak @OneToMany illetve @ManyToOne kapcsolódnak az Article-höz, de fontos különbség még, hogy míg az Author saját jogon is létezni képes (azaz pl. egy szerző nem törlődik, ha cikke törlődik), addig az Article és Comment kapcsolata kompozíció (azaz kommentár önmagában, cikk nélkül nem létezhet). Akkor lehet hasznos a Comment-et ily módon mégis entitásként (és nem @Embeddable-ként) kezelni, mikor külön is akarunk vele perzisztencia műveletet végezni, pl. törölni akarjuk anélkül, hogy a tartalmazó Article entitást példányosítani akarnánk, hogy rajta keresztül érjük el. Érdemes megjegyezni továbbá, hogy az @Embeddable objektumok közül mivel a Tag-ekből lista tárolódik az Article-ben, ezért a Hibernate specifikus @CollectionOfElements annotációt kellett használnunk, mivel a JPA nem tud ilyet.

Lássuk most az indexelés beállítását az Article osztályon:

@Entity
@Indexed
//alapértelmezett, angol nyelvhez, kisbetűsít, kiveszi a töltelékszavakat
//@Analyzer(impl=StandardAnalyzer.class)
public class Article {

    @Id
    @GeneratedValue
    @DocumentId
    private long id;

    @Fields({
        //A termvector megadására statisztikai célú adatfeldolgozás is történik
        @Field(termVector=TermVector.YES),
        //Sorbarendezéshez nem tokenizált változat
        @Field(name="title_sort", index=Index.UN_TOKENIZED, store=Store.YES)
    })
    @Boost(2.5f)
    private String title;

    @Embedded
    @CollectionOfElements //Hibernate megoldás, JPA nem tudja!
    @IndexedEmbedded
    private List<Tag> tagList = new ArrayList<Tag>(3);

    @ManyToOne
    @IndexedEmbedded
    private Author author;

    @Field
    @DateBridge(resolution=Resolution.DAY)
    @Temporal(TemporalType.DATE)
    private Date creationDate;

    @Embedded
    @IndexedEmbedded
    private Description description;

    @OneToMany(cascade={CascadeType.ALL}, fetch=FetchType.EAGER)
    @IndexedEmbedded
    private List<Comment> commentList = new ArrayList<Comment>(3);;

Az első lépés, hogy indexelendő entitást megjelöljük a @Indexed annotációval. Megadhatunk (osztály és attribútum szinten is) Lucene Analyzer osztályokat, amelyek segítségével az indexelendő szöveget a Lucene feldolgozza, tokenizálja. A tokenizálás során a szöveg nyelvi (pl. toldalékolási) szabályok szerint kerül elemi bontásra a tárolás előtt. Így lesz lehetőség rá, hogy a kutya kifejezésre találatként visszatérjenek a kutyával, kutyához stb. alakok is. Ezzel a demóban nem igazán foglalkoztam, mivel sajnos magyar nyelven nincs jó analyzer.

Exkurzus: Ex bölcsészként had jegyezzem meg, hogy szégyen, hogy tucatnyi könnyen értelmetlennek tűnő, belterjes nyelvészeti projekt sok milliós költségvetése mellett nem jut arra keret, hogy egy jól használható, nyílt forráskódú, bárki számára elérhető, több programozási nyelvhez és eszközhöz illeszthető morfológiai elemző segédkönyvtárat készítsenek (dokumentációval, példákkal, karbantartással stb.) az akadémiai és egyetemi nyelvész hölgyek, urak. Voltak kezdemények, de sajnos, ha valaki ma szeretne alkalmazásába ilyen megoldást beilleszteni, nem nagyon van rá lehetősége, hogy megtegye. Itt kérek elnézést előre is, ha tévedek -- ha így van, kérem írják meg nekem, hol érhető el az emlegetett eszköz! Ám ha igazam van, akkor tényleg szégyen, hogy 2009-ben még mindig nincs ilyen program: az államilag, adófizetői pénzből fenntartott kutatásoknak kb. pont ez lenne az értelme! Kíváncsi lennék, Németh László, a döbbenetesen sikeres Hunspell projekt gazdája mennyi támogatást kapott munkája során állami forrásokból -- annyit sejtem, hogy nem, amennyit megérdemelne a Hunspell elterjedtsége alapján! (No, erről ennyit. Asszem egyszer erről írok majd külön, pontosabban is!)

Lucene in Action

Szóval, az osztály szintjén jeleztük, hogy indexelni szeretnénk a mentés során. Hogy mely mezőket, azt azt az egyes attribútumok deklarációján kell jelezzük. Szükség van egy dokumentum szintű egyedi azonosítóra, ez legtöbbször lehet az entity id-ja. Ide helyezzük a @DocumentId annotációt. A többi, indexelendő mezőre a @Field annotáció kerül. Mint látható, ennek több attribútuma is lehet. A legfontosabbak az index és a store. Az index (alapértelmezett értéke az index=Index.TOKENIZED) segítségével azt adhatjuk meg, hogy az indexelés során az adott mező értékét tokenizálva, vagy feldolgozatlanul vegye át az indexbe. Az alapértelmezett analyzer például kisbetűsít mindent, kiveszi az angol szövegből a töltelékszavakat (pl. the, or, and, a, an etc.), illetve tövesíti a toldalékolt alakokat (pl. többesszám). Ezért fontos egyébként, hogy majd a keresésnél is ugyanazt az analyzert használjuk, mint a feldolgozásnál.

A store attribútummal azt adhatjuk meg, hogy az indexbe tárolva legyen-e az eredeti alak, vagy ne. Vigyázzunk, erre a legritkább esetben van szükség! A Hibernate Search teljesen jól boldogul, ha a DocumentId-t letárolta -- ez alapján már be tudja olvasni a perzisztens adatokat, és bármelyik mező megjeleníthető. Mezők értékének tárolása komoly teljesítményromlással járhat, ezért érdemes vigyázni vele. Akkor van értelme például, ha a keresések eredményeinek megjelenítésénél view objektumokat használunk (lásd később!), mert így érünk el jobb teljesítményt. @Field annotációkból több is megadható: ezt tettük a title mezőn, ahol egyszer keresési célokból tokenizálva indexeltük a mező, majd sorba rendezés (szintén később lesz róla szó) céljaira tokenizálatlan formában is elmentettük az indexbe.

Az asszociációk kezelésére az @IndexedEmbedded annotációt használjuk: ezzel jelezzük az eszköz számára, hogy a hivatkozott entitás is tartalmaz indexelendő mezőket. Hogy abban mely mezőket kell majd indexelni, azt ott kell megadnuk. Az Author esetében például:

@Entity
public class Author {

    @Id
    @GeneratedValue
    private long id;

    @Field
    @Boost(.7f)
    private String firstName;

    @Field
    @Boost(.9f)
    private String lastName;

Mivel előfordulhat, hogy egy szerző nevét frissítjük, és ilyenkor a kapcsolódó cikkek indexelt adatainak is frissülniük kell, ezért még valamiről gondoskodnunk kell. (Ne feledjük, az adatok denormalizálódnak, azaz egy Article lucenes index Document-je maga tartalmazza stringként az összes hivatkozott objektum indexelt mezőinek értékét!) A Hibernate Search észre kell vegye Author entitás mentésekor, frissítésekor, hogy elképzelhető, hogy lesznek kapcsolódó Article-ök, amelyek indexét frissíteni kell. Hogyan tudjuk erre az eszközt figyelmeztetni? Először is: muszáj lesz a kapcsolatot kétirányúvá tennünk, és a hivatkozó mezőt meg kell jelölnünk egy speciális annotációval:

@OneToMany(mappedBy="author")
@ContainedIn
private List<Article> articleList;

(Eddigi tapasztalataim szerint ez egyébként még nem elég: pl. a Comment lista esetében ha törlünk egy commentet az adatbázisból (em.remove(comment)), akkor ettől a tartalmazó Article indexe nem frissül magától, a remove előtt az Article commentList-jéből is törölni kell az említett entitást. Ez ha nem tévedek, a Hibernate primary cache miatt van, és talán csak LAZY inicializálás esetén (nagy mocsár!), még kísérletezem vele, de itt érdemes vigyázni!)

Szólni kel még a @Boost annotációról. Ezzel azt szabályozhatjuk, hogy az egyes mezők milyen súllyal szerepeljenek majd a keresések során. Azaz például, ha a kutya szó megtalálható a cím mezőben előrébb szerepeljen majd a találati listában, mint ahol csak a tagekben, és még hátrébb, ott ahol csak a főszövegben fordul elő. Az alapértelmezett érték itt egyébként az 1f, azaz ennél nagyobb értékkel növeljük, kisebbel (0f-1f) csökkentjük a mező súlyát. (A mezők súlyozására egyébként a lucene queryk definiáláskor is van lehetőség.)

Végül még a @FieldBridge annotációkról pár szóban! Egy lucene index ú.n. dokumentumokból áll, a dokumentumok pedig szövegesen tárolnak minden értéket. Előfordul azonban, hogy a Java szintjén definiált objektumunk szöveggé alakítása nem magától ertetődik. Ilyen esetben ú.n. Bridge-ekre lesz szükség, amelyek az objektum-string átalakítást elvégzik (kétirányú bridgek esetében oda-vissza). Számos beépített bridget is találunk a Hibernate Searchben, plédául a @DateBridge annotációval megadhatjuk a dátum értékeket milyen pontosságig szeretnénk elmenteni (nap, óra, perc stb.). Saját bridgeket is írhatunk, ez esetben legtöbbször a StringBridge vagy a TwoWayStringBridge interfaceeket kell implementálnunk:

public interface StringBridge {
    String objectToString(Object object);
}

public interface TwoWayStringBridge extends StringBridge {
    Object stringToObject(String stringValue);
}

Az osztályunk nevét aztán a mezőhöz (vagy alkalmanként az egész indexelt osztályhoz) kell rendelnünk:

@FieldBridge(impl=MySpecialBridge.class)

@ClassBridge(name="title", index=Index.UN_TOKENIZED, impl=MySpecialClassBridge.class )

Saját bridgek implementálására egyébként meglepően kevésszer van szükség, mert vagy boldogulunk a beépítettekkel (dátumok), vagy a kapcsolódó osztály szintjén tudjuk definiálni mit szeretnénk letárolni. Akkor lehet egyébként hasznos a dolog, ha némi kis logikát is viszünk az indexelésbe, például olyan alakra hozzuk egyes mezők értékét, hogy a < és > operátorok string alakban értelmesen alkalmazhatók legyenek (pl. stringként tárolt számok esetében: 11 < 2, de 11 > 02!). A beépített dátum bridge hasonló módon működik, és így lehet például dátum intervallumokra keresni.

4.   Keresés és lekérdezések

A demóban a keresések az ArticleDao osztályban találhatók. Kétfélét írtam meg: sima keresést és ú.n. projectiont.

A sima keresés végén a Hibernate Search a megtalált entitások adatait lekéri az adatbázisból, példányosítja az objektumokat és azokat adja vissza:

public List<Article> findByKeword(String keyword) {
    FullTextQuery ftQuery = createFtQuery(keyword);

    @SuppressWarnings("unchecked")
    List<Article> articleList = ftQuery.getResultList();

    return articleList;
}

private FullTextQuery createFtQuery(String keyword) {
    FullTextEntityManager ftEm = Search.getFullTextEntityManager(em);

    String[] fields = {
            "title", "description.descriptionString",
            "tagList.tagString",
            "commentList.commentString", "commentList.title",
            "author.lastName", "author.firstName"
    };

    Analyzer analyzer = ftEm.getSearchFactory().getAnalyzer(Article.class);

    QueryParser parser = new MultiFieldQueryParser(fields, analyzer);

    org.apache.lucene.search.Query luceneQuery;

    keyword = QueryParser.escape(keyword);

    try {
        luceneQuery = parser.parse(keyword);
    } catch (ParseException e) {
        throw new RuntimeException("Unable to parse query: " + keyword, e);
    }

    FullTextQuery ftQuery = ftEm.createFullTextQuery(luceneQuery, Article.class);

    /*Sorbarendezés cím szerint
    SortField sortField = new SortField( "title_sort", SortField.STRING);
    Sort sort = new Sort(sortField);
    ftQuery.setSort(sort);
    */

    return ftQuery;
}

Pár fontosabb lépésről röviden:

Hibernate Search in Action
  • Mint látjuk, a full text keresés futtatására szolgáló speciális Enitiy Managert (tulajdonképpen egy wrappert) így kapjuk meg: Search.getFullTextEntityManager(em)
  • Először egy Lucene query-t (org.apache.lucene.search.Query) kell építenünk, amelynek számos módja van. Az itt választott megoldásban egy String tömbben adjuk meg, mely mezőkben szeretnénk keresni.
  • Hogy ugyanazt az analyzert (analyzereket) használjuk a keresésénél, mint amelyeket az indexelésnél, kérjük le az eszköztől: ftEm.getSearchFactory().getAnalyzer(Article.class)
  • A Lucene Query létrehozásához fel kell dolgozni a keresési kulcsszót. Ezt itt most a keresett szavak halmazának tekintjük, ezért escapeljük is a benne esetleg előforduló speciális karaktereket (QueryParser.escape(keyword)). A parszolt kulcsszó valójában lehetne egy lucene query definíció is (pl. title:"Do it right" AND text:go), erről bővebben lásd a Lucene dokumentációban
  • A Lucene Query-ből utána egy org.hibernate.search.jpa.FullTextQuery példányt kell kapnunk: ftEm.createFullTextQuery(luceneQuery, Article.class) -- itt adjuk meg, mely osztály példányait keressük majd. A FullTextQuery egyébként egy javax.persistence.Query implementáció, így nem is csoda, ha Article listát ad majd vissza nekünk: List<Article> articleList = ftQuery.getResultList()
  • Itt adhatjuk meg, ha az alapértelmezett, találati rangsor szerinti sorba rendezés helyett más, pl. cím szerinti sorrendben szeretnénk megkapni a listát.

Sokkal hatékonyabb lehet a keresésünk (demónk szerint 2000 entitásból 557 találat esetén kb. ötszörös a különbség), ha nem példányosítunk entitásokat, hanem csak az indexen futtatjuk a keresést, és az ott tárolt (!) adatokból hozunk létre a felületen is megjeleníthető view objektumokat, anélkül, hogy a teljes Article gráfot feltöltenénk minden találatra. Egy ilyen speciális keresés, népszerű nevén projection a következőképpen építhető fel:

public List<ArticleView> projectByKeword(String keyword) {
    FullTextQuery ftQuery = createFtQuery(keyword);

    @SuppressWarnings("unchecked")
    List<ArticleView> results = ftQuery.setProjection(
            "id", "title_sort", FullTextQuery.SCORE)
            .setResultTransformer(new AliasToBeanResultTransformer(ArticleView.class))
            .getResultList();

    return results;
}

Mint látjuk, a FullTextQuery létrehozásáig ugyanaz a teendő, utána készítjük el a projectiont, megadva hogy mely mezőket kérjük az indexből, és hogy ezeket mely view objektumokba szeretnénk betölteni. E betöltéshez írhatunk saját átalakítót (lásd a demó kódjában a ArticleDao.ProjectionToArticleViewTransformer osztályt), vagy használhatjuk a Hibernate Search heurisztikus eszközeit (mint fent). Ezeket a view objektumokat megjeleníthetjük a felületen (pl. egy táblázatban), hogy utána kényelmesen csak a felhasználó által kiválasztottat példányosítsuk az adatbázisból.

A Lucene query nyelv az itt bemutatottnál sokkal komplexebb, és a Hibernate Search is számos más lehetőséget kínál egy lekérdezés felépítésére. Logikai operátorok különböző kombinációit alkalmazhatjuk, tudunk intervallumosan lekérdezni pl. dátumokra, vagy használhatunk wildcard karaktereket, illetve definiálhatunk hasonlósági kereséseket. Akit ilyen mélységben érdekel a dolog, mindenképpen forduljon előbb a Lucene dokumentációjához, majd olvasgassa a Hibernate Searchét. Bátran ajánlok még egy-egy könyvet a két eszközhöz: a Lucene in Actiont, illetve a Hibernate Search in Actiont.

5.   Luke

Luke

Pár szóban had említsem még meg a Luke nevű eszközt, amely nélkülözhetetlen minden indexelni vágyó fejlesztő számára! A Luke segítségével a Lucene indexeket tudjuk megnyitni, át tudjuk nézni azok tartalmát, az indexelt mezőket, illetve, ami a legszebb, kereséseket tudunk futtatni az indexen, ellőrizve, jól képzeljük-e az írt query működését. Bárki, aki Lucennel, Hibernate Searchcsel foglalkozik, nagyon hasznos segédeszköznek fogja találni!

A mellékelt képen például egy olyan keresést futtatok a megnyitott Article indexen, amelyben a title mezőkben keressük a one értéket ÉS a tagList.tagString mezőben az indices karaktersorozatot. Két dolgot ez a példa is remekül illusztrál. Egyrészt, hogy hogyan denormalizálódnak az adatok a Lucene indexben: a Java szintjén külön objektumként (sőt, objektumok listájaként!) szereplő tag lista mindegyik tagString értéke belekerül az indexelt Article példány document-jébe. A másik tanulság, hogy milyen kényelmesen használható a lekérdezésekben a property logika: a tagekben a tagList.tagString kifejezéssel tudunk keresni.

Meglepően hosszúra sikerült ez a leírás, de szinte több idő volt megírni, mint elkészíteni a demó alkalmazást. A Hibernate Search kis beletanulás után intuitív, dinamikusan bővíthető, és rengeteg ügyes megoldást kínál, hogy fejlesztett alkalmazásunkat okosabbá, hatékonyabbá tegyük. Igaz, itt most számos funkcióról nem esett szó, sokra még csak utalni sem tudtam, de remélem, mégis lesz, akinek hasznosnak bizonyul a fenti ismertető!

Valid XHTML 1.0! Valid CSS! Creative Commons License Hacker