Elasticsearch & Spring Data

M. Enes Oral
Kod Gemisi
Published in
6 min readSep 24, 2020

Elasticsearch büyük veri yığınları içerisinde tam metin (full-text) aramada ve bu büyük veri yığınlarının analiz edilmesinde kullanılan Apache Lucene tabanlı, Java ile yazılmış açık kaynak kodlu bir arama ve analitik motorudur.

Indexler üzerinden arama yaparak çok hızlı bir şekilde sonuç üretebilmektedir. Dolayısıyla big data kavramının geçtiği yerlerde adından sıkça söz ettirmektedir.

Dokümanların indexlenme ve aranma süreci

Spring Data Elasticsearch

Spring Data, geliştiricileri veritabanı işlemleri için yazılan boilerplate kodlardan kurtarmaktadır. Repository arayüzünü extend edip, önceden tanımlanmış formatlarda metotlar tanımlayarak veritabanı işlemleri gerçekleştirilebilmektedir.

Demo

Bu demoda Elasticsearch için gerekli konfigürasyonları yaptıktan sonra Spring Data Elasticsearch API’ı kullanarak nasıl tam metin arama yapabileceğimize, arama yaparken yazım yanlışlarına rağmen nasıl sonuç üretebileceğimize, nasıl custom analyzer oluşturabileceğimize ve ayrıca birkaç genel kullanım senaryosuna daha değineceğiz.

Öncelikle 9200 portunu dinleyen bir Elasticsearch instance’i başlatalım.

docker run -d --name es791 -p 9200:9200 -e “discovery.type=single-node” elasticsearch:7.9.1

Daha sonra Elasticsearch Java API’ı kullanabilmek için ilgili bağımlılığı ekleyelim.

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
<version>4.0.3.RELEASE</version>
</dependency>

ElasticsearchConfig.java

Elasticsearch instance’ına nasıl bağlanılacağını tanımlamak için Elasticsearch API tarafından sağlanan RestHighLevelClient’ı kullanıyoruz.

Ayrıca oluşturulacak olan repository’lerin istenilen işlevleri sağlayabilmesi için uygulamanın ayağa kalktığı sınıf @EnableElasticsearchRepositories anotasyonu ile işaretlenip parametre olarak repository’nin yer aldığı paketin yolu verilir. Böylelikle Spring tarafından gerekli konfigürasyonlar yapılacaktır.

Company.java

Elasticsearch’teki her bir kayıt doküman olarak geçmektedir. Bunu belirtmek için model, @Document anotasyonu ile işaretlenip kaydedileceği index’i belirtmek için ise indexName parametresi veriliyor.

Ayrıca employees koleksiyonu FieldType.Nested olarak işaretleniyor. Bu Elasticsearch’te bir Company dokümanının içerisinde Employee alanlarının bulundurulmasına olanak sağlıyor.

CompanyRepository.java

Şimdi ise önceden belirttiğimiz repository interface’ini oluşturalım. Bunun için ElasticSearchRepository interface’ini extend etmemiz gerekiyor. Generic type’ları ilgili doküman ve bu dokümanın primary key tipi olarak belirtiyoruz. Bu adımlar sonucunda Elasticsearch üzerinde CRUD işlemleri yapabilir düzeye geliniyor.

Ayrıca Employee ismine göre Company arama işlevinin nasıl sağlanabileceğini bir query method bir de custom query oluşturarak görelim. Elasticsearch’te işlemler JSON üzerinden gerçekleştiği için custom query’de bu şekilde yazılmaktadır.

CompanyController.java

ElasticSearchRepository, PagingAndSortingRepository’yi extend ettiğinden pagination ve sorting işlemleri de yerleşik olarak gelmektedir. Pagination, metoda verilen PageRequest parametresi ile sağlanmaktadır. Aşağıda 1. sayfa maksimum 20 veri olacak şekilde döndürülmektedir.

Şimdi Company oluşturabileceğimiz bir POST endpoint’i oluşturuyor ve bu endpointi kullanarak iki farklı Company kaydı gerçekleştiriyoruz.

ElasticsearchOperations.java

Şimdi ise Company sınıfının description field’ına göre arama işlevinin nasıl sağlanabileceğine bakalım. Bunu CompanyRepository sınıfında bir metot tanımlayarak yapabiliriz. Fakat biz yazının devamında Elasticsearch Java API’ı tarafından sağlanan ElasticsearchOperations arayüzünü kullanarak -Spring tarafından bu arayüze default olarak ElasticsearchRestTemplate enjekte edilmektedir- bu işlevi nasıl sağlayabileceğimize bakalım.

Aşağıda matchQuery metodu kullanılarak bir NativeSearchQuery nesnesi oluşturuluyor. Daha sonra oluşturulan bu nesne, elasticSearchOperations nesnesinin search metoduna parametre olarak verilerek arama işlevi sağlanmış oluyor.

Yukarıda oluşturduğumuz NativeSearchQuery default olarak OR operatörünü kullanmaktadır. Yani cümlenin içinde geçen kelimelere göre arama yapmaktadır. Oluşturduğumuz endpointe ‘kurumunuza özgü’ parametresini yollayarak istek yolladığımızda üretilen sonuç aşağıdaki gibidir.

‘özgü’ kelimesi her iki Company nesnesinin description field’ında geçtiği için her ikisi de arama sonucunda çıkmaktadır fakat ‘kurumunuza’ kelimesi sadece Kod Gemisi şirketinin description’ında geçtiği için arama sonucunda daha yüksek skor ile karşımıza çıkmaktadır.

Belirli bir yüzde üzerinde eşleşen arama sonuçlarını filtrelemek istiyorsak minimumShouldMatch ile bu filtreleme işlemini gerçekleştirebiliriz.

Full-text Search

Şimdi kelimelere göre değil de full-text arama işlevinin nasıl sağlanabileceğine bakalım. Bunun için yapmamız gereken NativeSearchQuery’yi build ederken matchQuery metodunun operatorünü AND olarak belirlemektir.

Yukarıda oluşturduğumuz endpoint’e ‘kurumunuza özgü’ parametresini yollayarak istek gerçekleştirdiğimizde üretilen sonuç aşağıdaki gibi olacaktır. Bu sefer sadece Kod Gemisi karşımıza çıkmaktadır.

Fuziness

Kullanıcılar arama yaparken yazım hataları yapabilirler. Bu tür bir senaryoyu ele almak için fuziness kavramı kullanılmaktadır. Böylelikle arama için gönderilen text, kayıtlarla tamamen eşleşmiyorsa bile yine de bir sonuç üretmek mümkün olmaktadır.

Yukarıda NativeSearchQuery nesnesi oluştururken Fuziness.ONE ile arama eşleme işleminin bir harf değiştirerek de denenmesi belirtilmektedir. Ayrıca prefixLength(2) ile kelimelerin ilk iki harfinde bir değişikliklik yapılmaması belirtilmekte ve böylelikle kombinasyon sayısı azaltılmaktadır.

Yukarıda oluşturduğumuz bu endpointe ‘kurumuNuza özgÜ’ yerine ‘kurumuMuza özgĞ” parametresiyle istek yolladığımızda da Kod Gemisi sonucu karşımıza çıkmaktadır.

Phrase Search

Bir başka senaryo full-text arama yaparken kullanıcıların ifadeleri art arda yazmayabileceğidir. Bu gibi bir senaryo için matchPhraseQuery kullanılabilir. Ayrıca slop ile bir terimin kaç kere taşınacağı belirtilebilir.

Oluşturduğumuz bu endpoint’e ‘kurumunuza çözümler’ parametresi ile istek yolladığımızda aşağıdaki sonucu elde etmekteyiz. Çünkü burada ‘kurumunuza çözümler’ ile arama yapılırken ‘kurumunuza xxxx çözümler’ gibi girilen ifadelerin bir kelime öteye taşınabileceği belirtilmiştir.

Ek: Controller metotlarında oluşturulan logicler, custom bir repository içerisinde oluşturulup her yerde kullanılabilir hale getirilebilir. Şu yazıda Elasticsearch Java API tarafından sağlanan başka bir özellik custom repository oluşturularak kullanılmıştır.

Custom Analyzer

Yukarıda oluşturulan arama endpoint’lerine ozgu, cozumlerveya uretir şeklinde türkçe karakterler olmadan istek yapıldığında herhangi bir eşleşme olmamaktadır. Eşleşme sağlayabilmek için asciifolding filter’ına sahip custom bir analyzer oluşturmamız gerekmektedir.

Default olarak tüm String field’lar bir analyzer tarafından işlenmektedir. Default analyzer kaydedilecek cümleyi boşluklar veya noktalama işaretleri gibi genel ayırıcılara göre böler ve her kelimeyi küçük harflere çevirir. Ayrıca yaygın İngilizce kelimeleri (a, the, is…) yok sayar.

Asciifolding ise bir filtre türüdür. Temel Latin Unicode bloğunda yer almayan alfabetik, sayısal ve sembolik karakterleri varsa ASCII eşdeğerlerine dönüştürür. Örneğin ö harfini o olarak değiştirmektedir. Bu değişiklik kayıtlara yansımamaktadır, ara bir işlem olarak düşünebiliriz.

Not: Eğer uygulamayı yukarıdaki haliyle ayağa kaldırdıysanız indexleme işlemi önceki ayarlarda gerçekleştiği için aşağıda yapılacak değişiklikler uygulamanıza yansımayacaktır. Dolayısıyla index’i sıfırlamanız gerekmektedir.

Öncelikle aşağıdaki settings.json dosyası resources package’i altında oluşturulur. Burada custom_analyzer adında lowercase ve asciifolding filtrelerine sahip bir analyzer tanımı yapılmaktadır.

settings.json dosyasında yapılan ayarların işlenmesini istediğimiz index modellerine @Setting anotasyonu ile bu dosyanın yolu verilir. Ayrıca oluşturulan analyzer’ın uygulanmak istenildiği field’ların @Field anotasyonunun analyzer parametresi, oluşturulan analyzer ismi olarak belirtilir.

Uygulama ayağa kaldırıldığı zaman indexleme işlemi bu ayarlar da gözetilerek gerçekleşmektedir. localhost:9200/erp/_mappings adresine istek yapıldığında ilgili field’ların analyzer’ının custom_analyzer olarak maplendiği görülmektedir.

Oluşturulan custom_analyzer'ı test etmek için localhost:9200/erp/_analyze adresine aşağıdaki gibi bir istek gerçekleştirebiliriz. Aşağıda görüldüğü üzere Kurumunuza özgü text’i kurumunuza ve ozgu olarak istenilen şekilde filtrelenmektedir. Böylelikle bu keyword’ler ile arama yapılsa dahi eşleşme sağlanmış olacaktır.

Bu yazıda genel kullanım senaryolarını ele aldık. Gereksinimlerinize uygun sorgular üretebileceğiniz birçok sınıfı org.elasticsearch.index.query paketi altında bulabilirsiniz.

Ayrıca yazıda gerçekleştirilen demoya buradan ulaşabilirsiniz.

Referanslar

End Of File

--

--