ElasticSearchRepository和ElasticSearchTemplate的使用

【转载】本文转自CSDN 天涯泪小武

Spring-data-elasticsearch是Spring提供的操作ElasticSearch的数据层,封装了大量的基础操作,通过它可以很方便的操作ElasticSearch的数据。

版本说明

ElasticSearch目前最新的已到5.5.1

spring data elasticsearch elasticsearch
3.0.0.RC1 5.5.0
3.0.0.M4 5.4.0
2.0.4.RELEASE 2.4.0
2.0.0.RELEASE 2.2.0
1.4.0.M1 1.7.3
1.3.0.RELEASE 1.5.2
1.2.0.RELEASE 1.4.4
1.1.0.RELEASE 1.3.2
1.0.0.RELEASE 1.1.1

这有一个对应关系,不过不太完整,我目前使用的SpringBoot版本1.5.4对应的spring-data-ElasticSearch是2.1.4,在图上就没有体现。

但是可以预见对应的ElasticSearch应该在2.4.*往上,但应该是不支持5.4.0及以上。

注意:我这篇例子,所使用的ElasticSearch版本就是最新的5.5.1,SpringBoot版本是1.5.4,经初步试验,插入及查询都没问题。估计是5.5.*的新特性之类的会无法使用,基本操作应该都没问题。

ElasticSearchRepository的基本使用

@NoRepositoryBean  
public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID> {  
    <S extends T> S index(S var1);  
    Iterable<T> search(QueryBuilder var1);  
    Page<T> search(QueryBuilder var1, Pageable var2);  
    Page<T> search(SearchQuery var1);  
    Page<T> searchSimilar(T var1, String[] var2, Pageable var3);  
    void refresh();  
    Class<T> getEntityClass();  
}  

我们是通过继承ElasticsearchRepository来完成基本的CRUD及分页操作的,和普通的JPA没有什么区别。

ElasticsearchRepository继承了ElasticsearchCrudRepository extends PagingAndSortingRepository.

先看看普通查询:

public interface BookRepository extends Repository<Book, String> {  
    List<Book> findByNameAndPrice(String name, Integer price);  
    List<Book> findByNameOrPrice(String name, Integer price);  
    Page<Book> findByName(String name,Pageable page);  
    Page<Book> findByNameNot(String name,Pageable page);  
    Page<Book> findByPriceBetween(**int** price,Pageable page);  
    Page<Book> findByNameLike(String name,Pageable page);  
    @Query("{\"bool\" : {\"must\" : {\"term\" : {\"message\" : \"?0\"}}}}")  
    Page<Book> findByMessage(String message, Pageable pageable);  
}  

这个没什么特点,就是普通的JPA查询,这个很熟悉,通过上面的JPA查询就能完成很多的基本操作了。

插入数据也很简单:

@Autowired  
private SampleElasticsearchRepository repository;  
String documentId = "123456";  
SampleEntity sampleEntity = **new** SampleEntity();  
sampleEntity.setId(documentId);  
sampleEntity.setMessage("some message");  
repository.save(sampleEntity);  

还可以批量插入数据:

@Autowired  
private SampleElasticsearchRepository repository;  
String documentId = "123456";  
SampleEntity sampleEntity1 = **new** SampleEntity();  
sampleEntity1.setId(documentId);  
sampleEntity1.setMessage("some message");  
String documentId2 = "123457"  
SampleEntity sampleEntity2 = **new** SampleEntity();  
sampleEntity2.setId(documentId2);  
sampleEntity2.setMessage("test message");  
List<SampleEntity> sampleEntities = Arrays.asList(sampleEntity1, sampleEntity2);  
//bulk index  
repository.save(sampleEntities);  

特殊情况下,ElasticsearchRepository里面有几个特殊的search方法,这些是ES特有的,和普通的JPA区别的地方,用来构建一些ES查询的。

主要是看QueryBuilder和SearchQuery两个参数,要完成一些特殊查询就主要看构建这两个参数。

我们先来看看它们之间的类关系


image.png

从这个关系中可以看到ES的search方法需要的参数SearchQuery是一个接口,有一个实现类叫NativeSearchQuery,实际使用中,我们的主要任务就是构建NativeSearchQuery来完成一些复杂的查询的。

image.png

我们可以看到要构建NativeSearchQuery,主要是需要几个构造参数

public NativeSearchQuery(QueryBuilder query, QueryBuilder filter, List<SortBuilder> sorts, Field[] highlightFields) {  
    this.query = query;  
    this.filter = filter;  
    this.sorts = sorts;  
    this.highlightFields = highlightFields;  
}  

当然了,我们没必要实现所有的参数。

可以看出来,大概是需要QueryBuilder,filter,和排序的SortBuilder,和高亮的字段。

一般情况下,我们不是直接是new NativeSearchQuery,而是使用NativeSearchQueryBuilder。

通过NativeSearchQueryBuilder.withQuery(QueryBuilder1).withFilter(QueryBuilder2).withSort(SortBuilder1).withXXXX().build();这样的方式来完成NativeSearchQuery的构建。

image.png
image.png

从名字就能看出来,QueryBuilder主要用来构建查询条件、过滤条件,SortBuilder主要是构建排序。

譬如,我们要查询距离某个位置100米范围内的所有人、并且按照距离远近进行排序:

double lat = 39.929986;  
double lon = 116.395645;  

Long nowTime = System.currentTimeMillis();  
//查询某经纬度100米范围内  
GeoDistanceQueryBuilder builder = QueryBuilders.geoDistanceQuery("address").point(lat, lon)  
    .distance(100, DistanceUnit.METERS);  
GeoDistanceSortBuilder sortBuilder = SortBuilders.geoDistanceSort("address")  
    .point(lat, lon)  
    .unit(DistanceUnit.METERS)  
    .order(SortOrder.ASC);  
Pageable pageable = **new** PageRequest(0, 50);  
NativeSearchQueryBuilder builder1 = **new** NativeSearchQueryBuilder().withFilter(builder).withSort(sortBuilder).withPageable(pageable);  
SearchQuery searchQuery = builder1.build();  

要完成字符串的查询:

SearchQuery searchQuery = **new** NativeSearchQueryBuilder().withQuery(QueryBuilders.queryStringQuery("spring boot OR 书籍")).build();  

要构建QueryBuilder,我们可以使用工具类QueryBuilders,里面有大量的方法用来完成各种各样的QueryBuilder的构建,字符串的、Boolean型的、match的、地理范围的等等。

要构建SortBuilder,可以使用SortBuilders来完成各种排序。

然后就可以通过NativeSearchQueryBuilder来组合这些QueryBuilder和SortBuilder,再组合分页的参数等等,最终就能得到一个SearchQuery了。

至此,我们明白了ElasticSearchRepository里那几个search查询方法需要的参数的含义和构建方式了。

ElasticSearchTemplate的使用

ElasticSearchTemplate更多是对ESRepository的补充,里面提供了一些更底层的方法。

image.png

这里主要是一些查询相关的,同样是构建各种SearchQuery条件。

也可以完成add操作

String documentId = "123456";  
SampleEntity sampleEntity = **new** SampleEntity();  
sampleEntity.setId(documentId);  
sampleEntity.setMessage("some message");  
IndexQuery indexQuery = **new** IndexQueryBuilder().withId(sampleEntity.getId()).withObject(sampleEntity).build();  
elasticsearchTemplate.index(indexQuery);  

add主要是通过index方法来完成,需要构建一个IndexQuery对象


image.png

构建这个对象,主要是设置一下id,就是你的对象的id,Object就是对象本身,indexName和type就是在你的对象javaBean上声明的

image.png

其他的字段自行发掘含义,构建完IndexQuery后就可以通过Template的index方法插入了。

template里还有各种deleteIndex,delete,update等方法,用到的时候就查查看吧。

下面讲一个批量插入的方法,我们经常需要往ElasticSearch中插入大量的测试数据来完成测试搜索,一条一条插肯定是不行的,ES提供了批量插入数据的功能——bulk。

前面讲过JPA的save方法也可以save(List)批量插值,但适用于小数据量,要完成超大数据的插入就要用ES自带的bulk了,可以迅速插入百万级的数据。

在ElasticSearchTemplate里也提供了对应的方法

public void bulkIndex(List<IndexQuery> queries) {  
    BulkRequestBuilder bulkRequest = this.client.prepareBulk();  
    Iterator var3 = queries.iterator();  
    while(var3.hasNext()) {  
        IndexQuery query = (IndexQuery)var3.next();  
        bulkRequest.add(this.prepareIndex(query));  
    }  
    BulkResponse bulkResponse = (BulkResponse)bulkRequest.execute().actionGet();  
    if (bulkResponse.hasFailures()) {  
        Map<String, String> failedDocuments = **new** HashMap();  
        BulkItemResponse[] var5 = bulkResponse.getItems();  
        int var6 = var5.length;  
        for(int var7 = 0; var7 < var6; ++var7) {  
            BulkItemResponse item = var5[var7];  
            if (item.isFailed()) {  
                failedDocuments.put(item.getId(), item.getFailureMessage());  
            }  
        }  
        throw new ElasticsearchException("Bulk indexing has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages [" + failedDocuments + "]", failedDocuments);  
    }  
}  
public void bulkUpdate(List<UpdateQuery> queries) {  
    BulkRequestBuilder bulkRequest = this.client.prepareBulk();  
    Iterator var3 = queries.iterator();  
    while(var3.hasNext()) {  
        UpdateQuery query = (UpdateQuery)var3.next();  
        bulkRequest.add(**this**.prepareUpdate(query));  
    }  
    BulkResponse bulkResponse = (BulkResponse)bulkRequest.execute().actionGet();  
    if (bulkResponse.hasFailures()) {  
        Map<String, String> failedDocuments = new HashMap();  
        BulkItemResponse[] var5 = bulkResponse.getItems();  
        int var6 = var5.length;  
        for(int var7 = 0; var7 < var6; ++var7) {  
            BulkItemResponse item = var5[var7];  
            if (item.isFailed()) {  
                failedDocuments.put(item.getId(), item.getFailureMessage());  
            }  
        }  
        throw new ElasticsearchException("Bulk indexing has failures. Use ElasticsearchException.getFailedDocuments() for detailed messages [" + failedDocuments + "]", failedDocuments);  
    }  
}

和index插入单条数据一样,这里需要的是List<IndexQuery>仅此而已,是不是很简单。

public void bulkIndex(List<Person> personList) {  
    int counter = 0;  
    try {  
        if (!elasticsearchTemplate.indexExists(PERSON_INDEX_NAME)) {  
            elasticsearchTemplate.createIndex(PERSON_INDEX_TYPE);  
        }  
        List<IndexQuery> queries = new ArrayList<>();  
        for (Person person : personList) {  
            IndexQuery indexQuery = **new** IndexQuery();  
            indexQuery.setId(person.getId() + "");  
            indexQuery.setObject(person);  
            indexQuery.setIndexName(PERSON_INDEX_NAME);  
            indexQuery.setType(PERSON_INDEX_TYPE);  
            //上面的那几步也可以使用IndexQueryBuilder来构建  
            //IndexQuery index = new IndexQueryBuilder().withId(person.getId() + "").withObject(person).build();  
            queries.add(indexQuery);  
            if (counter % 500 == 0) {  
                elasticsearchTemplate.bulkIndex(queries);  
                queries.clear();  
                System.out.println("bulkIndex counter : " + counter);  
            }  
            counter++;  
        }  
        if (queries.size() > 0) {  
            elasticsearchTemplate.bulkIndex(queries);  
        }  
        System.out.println("bulkIndex completed.");  
    } catch (Exception e) {  
        System.out.println("IndexerService.bulkIndex e;" + e.getMessage());  
        throw e;  
    }  
}  

这里是创建了100万个Person对象,每到500就用bulkIndex插入一次,速度飞快,以秒的速度插入了百万数据。

OK,这篇主要是讲一些ElasticSearchRepository和ElasticSearchTemplate的用法,构造QueryBuilder的方式。下一篇用实例来看一下,在百万或者更大量级的数据中查询距离某个坐标100米范围内的所有数据。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容