Spring Data Elasticsearch是Spring Data项目下的一个子模块。
查看 Spring Data的官网:http://projects.spring.io/spring-data/
Spring Data 的使命是给各种数据访问提供统一的编程接口,不管是关系型数据库(如MySQL),还是非关系数据库(如Redis),或者类似Elasticsearch这样的索引数据库。从而简化开发人员的代码,提高开发效率。
Spring Boot与Elasticsearch的对应版本:
引入依赖:
org.springframework.boot spring-boot-starter-parent 2.4.1 org.springframework.boot spring-boot-starter-data-elasticsearch
Elasticsearch的版本为7.16,所以选择2.4以上版本的SpringBoot。
application.yml
es: address: 127.0.0.1 port: 9200 scheme: http username: elastic password: 123456
@Configuration public class ElasticSearchConfig extends AbstractElasticsearchConfiguration { @Value("${es.address}") String address; @Value("${es.port}") Integer port; @Value("${es.scheme}") String scheme; @Value("${es.username}") String username; @Value("${es.password}") String password; @Override public RestHighLevelClient elasticsearchClient() { final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password)); RestClientBuilder restClientBuilder = RestClient.builder(new HttpHost(address, port, scheme)) .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() { @Override public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpAsyncClientBuilder) { return httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider); } }); RestHighLevelClient esClient = new RestHighLevelClient(restClientBuilder); return esClient; } }
@Configuration public class ElasticSearchConfig extends AbstractElasticsearchConfiguration { @Value("${es.address}") String address; @Value("${es.port}") Integer port; @Value("${es.scheme}") String scheme; @Value("${es.username}") String username; @Value("${es.password}") String password; @Override public RestHighLevelClient elasticsearchClient() { RestClientBuilder builder = null; builder = RestClient.builder(new HttpHost(address, port, scheme)); RestHighLevelClient client = new RestHighLevelClient(builder); return client; } }
@Document(indexName = "book", createIndex = true) public class Book { @Id @Field(type = FieldType.Long) private Long id; @Field(type = FieldType.Keyword, store = true) private String name; @Field(type = FieldType.Text, store = false, analyzer = "ik_smart") private String describe; @Field(type = FieldType.Text, analyzer = "ik_smart") private String author; @Field(type = FieldType.Double) private Double price; @Field(type = FieldType.Date, format = DateFormat.basic_date ) private Date createTime; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescribe() { return describe; } public void setDescribe(String describe) { this.describe = describe; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } @Override public String toString() { return "Book{" + "id=" + id + ", name='" + name + '\'' + ", describe='" + describe + '\'' + ", author='" + author + '\'' + ", price=" + price + ", createTime=" + createTime + '}'; } }
public interface BookRespository extends ElasticsearchRepository{ }
CrudRepository:
提供以Id为参数的CRUD功能:
@NoRepositoryBean public interface CrudRepositoryextends Repository { S save(S entity);IterablesaveAll(Iterableentities); OptionalfindById(ID id); boolean existsById(ID id); Iterable findAll(); Iterable findAllById(Iterable ids); long count(); void deleteById(ID id); void delete(T entity); void deleteAll(Iterable extends T> entities); void deleteAll(); }
PagingAndSortingRepository:
在CRUD之上,提供分页和排序功能:
@NoRepositoryBean public interface PagingAndSortingRepositoryextends CrudRepository { Iterable findAll(Sort sort); Page findAll(Pageable pageable); }
ElasticsearchRepository:
大部分方法在7.x中已经废弃:
@NoRepositoryBean public interface ElasticsearchRepositoryextends PagingAndSortingRepository { @Deprecated default S index(S entity) { return save(entity); } @DeprecatedS indexWithoutRefresh(S entity); @Deprecated Iterablesearch(QueryBuilder query); @Deprecated Page search(QueryBuilder query, Pageable pageable); Page search(Query searchQuery); Page searchSimilar(T entity, @Nullable String[] fields, Pageable pageable); @Deprecated void refresh(); }
@Test public void add() { Book book = new Book(); book.setId(1l); book.setAuthor("罗贯中"); book.setName("三国演义"); book.setPrice(42.56); book.setCreateTime(new Date()); book.setDescribe("天下大势,分久必合,合久必分。"); bookRespository.save(book); }
@Test public void update() { Book book = new Book(); book.setId(1l); book.setAuthor("罗贯中"); book.setName("三国演义"); book.setPrice(55.55); book.setCreateTime(new Date()); book.setDescribe("天下大势,分久必合,合久必分。"); bookRespository.save(book); }
@Test public void add_all() { Listlist = new ArrayList<>(); Book book1 = new Book(); book1.setId(2l); book1.setAuthor("吴承恩"); book1.setName("西游记"); book1.setPrice(33.33); book1.setCreateTime(new Date()); book1.setDescribe("大师兄!师傅又丢了!"); list.add(book1); Book book2 = new Book(); book2.setId(3l); book2.setAuthor("曹雪芹"); book2.setName("红楼梦"); book2.setPrice(66.66); book2.setCreateTime(new Date()); book2.setDescribe("一朝春尽红颜老,花落人亡两不知。"); list.add(book2); Book book3 = new Book(); book3.setId(4l); book3.setAuthor("施耐庵"); book3.setName("水浒传"); book3.setPrice(35.22); book3.setCreateTime(new Date()); book3.setDescribe("招安"); list.add(book3); bookRespository.saveAll(list); }
@Test public void delete() { bookRespository.deleteById(1l); }
@Test public void findById() { Optionaloptional = bookRespository.findById(2l); System.out.println(optional.get()); }
@Test public void findAll() { Iterableall = bookRespository.findAll(); all.forEach(System.out::println); }
Spring Data 的另一个强大功能,是根据方法名称自动实现功能。
比如:方法名叫findByName,那么就是根据name查询,无需写实现类。
public interface BookRespository extends ElasticsearchRepository{ /** * 根据书名查询 * @param name * @return */ List findByName(String name); }
@Test public void findByName() { Listlist = bookRespository.findByName("红楼梦"); list.forEach(System.out::println); }
当然,方法名称要遵循一定的约定:
{ "query": { "bool": { "must": [ { "query_string": { "query": "?", "fields": [ "name" ] } }, { "query_string": { "query": "?", "fields": [ "price" ] } } ] } } }
{ "query": { "bool": { "should": [ { "query_string": { "query": "?", "fields": [ "name" ] } }, { "query_string": { "query": "?", "fields": [ "price" ] } } ] } } }
{ "query": { "bool": { "must": [ { "query_string": { "query": "?", "fields": [ "name" ] } } ] } } }
{ "query": { "bool": { "must_not": [ { "query_string": { "query": "?", "fields": [ "name" ] } } ] } } }
{ "query": { "bool": { "must": [ { "range": { "price": { "from": ?, "to": ?, "include_lower": true, "include_upper": true } } } ] } } }
{ "query": { "bool": { "must": [ { "range": { "price": { "from": null, "to": ?, "include_lower": true, "include_upper": false } } } ] } } }
{ "query": { "bool": { "must": [ { "range": { "price": { "from": null, "to": ?, "include_lower": true, "include_upper": true } } } ] } } }
{ "query": { "bool": { "must": [ { "range": { "price": { "from": ?, "to": null, "include_lower": false, "include_upper": true } } } ] } } }
{ "query": { "bool": { "must": [ { "range": { "price": { "from": ?, "to": null, "include_lower": true, "include_upper": true } } } ] } } }
{ "query": { "bool": { "must": [ { "range": { "price": { "from": null, "to": ?, "include_lower": true, "include_upper": true } } } ] } } }
{ "query": { "bool": { "must": [ { "range": { "price": { "from": ?, "to": null, "include_lower": true, "include_upper": true } } } ] } } }
{ "query": { "bool": { "must": [ { "query_string": { "query": "?*", "fields": [ "name" ] }, "analyze_wildcard": true } ] } } }
{ "query": { "bool": { "must": [ { "query_string": { "query": "?*", "fields": [ "name" ] }, "analyze_wildcard": true } ] } } }
EndingWith:findByNameEndingWith
{ "query": { "bool": { "must": [ { "query_string": { "query": "*?", "fields": [ "name" ] }, "analyze_wildcard": true } ] } } }
{ "query": { "bool": { "must": [ { "query_string": { "query": "*?*", "fields": [ "name" ] }, "analyze_wildcard": true } ] } } }
{ "query": { "bool": { "must": [ { "bool": { "must": [ { "terms": { "name": [ "?", "?" ] } } ] } } ] } } }
{ "query": { "bool": { "must": [ { "query_string": { "query": "\"?\" \"?\"", "fields": [ "name" ] } } ] } } }
{ "query": { "bool": { "must": [ { "bool": { "must_not": [ { "terms": { "name": [ "?", "?" ] } } ] } } ] } } }
{ "query": { "bool": { "must": [ { "query_string": { "query": "NOT(\"?\" \"?\")", "fields": [ "name" ] } } ] } } }
Not Supported Yet !
{ "query": { "bool": { "must": [ { "query_string": { "query": "true", "fields": [ "available" ] } } ] } } }
{ "query": { "bool": { "must": [ { "query_string": { "query": "false", "fields": [ "available" ] } } ] } } }
{ "query": { "bool": { "must": [ { "query_string": { "query": "true", "fields": [ "available" ] } } ] } }, "sort": [ { "name": { "order": "desc" } } ] }
按照价格区间查询:
ListfindByPriceBetween(Double from, Double to);
@Test public void findByPriceBetween() { Listlist = bookRespository.findByPriceBetween(20.00, 60.00); list.forEach(System.out::println); }
查询书名为三国演义或红楼梦,或者作者为吴承恩的书籍:
ListfindByNameInOrAuthorIn(List names, List authors);
@Test public void findByNameInOrAuthorIn() { Listnames = new ArrayList<>(); names.add("三国演义"); names.add("红楼梦"); List authors = new ArrayList<>(); authors.add("吴承恩"); List list = bookRespository.findByNameInOrAuthorIn(names, authors); list.forEach(System.out::println); }
基本方式是使用Respository的各种search方法,然后用QueryBuilder构建查询。4.0后已经废弃,推荐使用注解查询、ElasticsearchRestTemlate或者上面的方法名查询。
@Test public void bash_search() { MatchQueryBuilder builder = QueryBuilders.matchQuery("author", "吴承恩"); Iterablebooks = bookRespository.search(builder); books.forEach(System.out::println); }
@Test public void page_search() { NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); queryBuilder.withQuery(QueryBuilders.matchAllQuery()); //初始化分页参数 int page = 0; int size = 2; queryBuilder.withPageable(PageRequest.of(0, 2)); Pagebooks = bookRespository.search(queryBuilder.build()); //总条数 System.out.println(books.getTotalElements()); //总页数 System.out.println(books.getTotalPages()); //每页大小 System.out.println(books.getSize()); //当前页 System.out.println(books.getNumber()); //数据 books.getContent().forEach(System.out::println); }
@Test public void sort_search() { NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); queryBuilder.withQuery(QueryBuilders.matchAllQuery()); queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC)); Pagebooks = bookRespository.search(queryBuilder.build()); books.getContent().forEach(System.out::println); }
@Test public void bucket_agg() { NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); //不查询任何字段 queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); //添加一个terms聚合,名称为name_agg,字段为name queryBuilder.addAggregation(AggregationBuilders.terms("name_agg").field("name")); //查询 AggregatedPagebooks = (AggregatedPage ) bookRespository.search(queryBuilder.build()); //解析 ParsedStringTerms agg= (ParsedStringTerms) books.getAggregation("name_agg"); List extends Terms.Bucket> buckets = agg.getBuckets(); for (Terms.Bucket bucket : buckets) { System.out.println(bucket.getKeyAsString() + ":" + bucket.getDocCount()); } }
@Test public void avg_agg() { NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); //不查询任何字段 queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); //添加一个terms聚合,名称为name_agg,字段为name queryBuilder.addAggregation( AggregationBuilders.terms("name_agg").field("name") .subAggregation(AggregationBuilders.avg("price_avg").field("price")) ); //查询 AggregatedPagebooks = (AggregatedPage ) bookRespository.search(queryBuilder.build()); //获取聚合 ParsedStringTerms name_agg = (ParsedStringTerms) books.getAggregation("name_agg"); //获取桶 List extends Terms.Bucket> buckets = name_agg.getBuckets(); //遍历 for (Terms.Bucket bucket : buckets) { System.out.println(bucket.getKeyAsString() + ":" + bucket.getDocCount()); //获取子聚合 ParsedAvg price_avg = (ParsedAvg) bucket.getAggregations().asMap().get("price_avg"); System.out.println(price_avg.getValue()); } }
等值查询:
QueryBuilders.termQuery("name", "小李")
范围查询:
QueryBuilders.rangeQuery("age").gte(18).lte(50);
模糊查询:
QueryBuilders.boolQuery().must(QueryBuilders.wildcardQuery("name", "*小李*"));
多条件查询:
QueryBuilders.boolQuery() .must(QueryBuilders.termQuery("name", "小李")) .must(QueryBuilders.rangeQuery("age").gte(10).lte(50));
must查询:
Listlist = Arrays.asList("北京", "上海", "杭州"); QueryBuilders.boolQuery() .must(QueryBuilders.termQuery("name", "李明")) .must(QueryBuilders.termsQuery("address", list)) .must(QueryBuilders.rangeQuery("age").gte(10).lte(50));
should查询:
QueryBuilders.boolQuery() .should(QueryBuilders.wildcardQuery("name", "*小李*")) .should(QueryBuilders.termQuery("address", "北京"));
bool组合查询:
QueryBuilders.boolQuery() .must(QueryBuilders.termQuery("sex", "男")) .should(QueryBuilders.wildcardQuery("name", "*小李*")) .should(QueryBuilders.termQuery("address", "北京")) .minimumShouldMatch(1);
有值查询:
QueryBuilders.boolQuery() .must(QueryBuilders.existsQuery("name")) .mustNot(QueryBuilders.existsQuery("tag"));
使用@Query注解查询,设置为注释参数的String必须是有效的Elasticsearch JSON查询。
JSON字符串中使用?idnex进行参数占位,?表示该位置为参数,index表示参数下标,从0开始。
根据作者查询:
/** * { * "query": { * "match": { * "author": "曹雪芹" * } * } * } * @param name * @return */ @Query("{\n" + " \"match\": {\n" + " \"author\": \"?0\"\n" + " }\n" + "}") List> listBookByName(String name);
@Test public void annotation_test1() { List> list = bookRespository.listBookByName("曹雪芹"); for (SearchHit hit : list) { System.out.println(hit.getScore()); System.out.println(hit.getContent()); } }
根据价格范围查询:
/** * { * "query": { * "range": { * "price": { * "gte": 10, * "lte": 40 * } * } * } * } * @param from * @param to * @return */ @Query("{\n" + " \"range\": {\n" + " \"price\": {\n" + " \"gte\": \"?0\",\n" + " \"lte\": \"?1\"\n" + " }\n" + " }\n" + "}") List> listBookByPriceRange(Double from, Double to);
@Test public void annotation_test2() { List> list = bookRespository.listBookByPriceRange(10d, 40d); for (SearchHit hit : list) { System.out.println(hit.getScore()); System.out.println(hit.getContent()); } }
@Highlight注解用于设置高亮查询,子注解@HighlightField用于指定高亮字段,@HighlightParameters用于配置高亮选项。
根据作者查询,并高亮作者:
/** * { * "query": { * "match": { * "author": "罗贯中" * } * }, * "highlight": { * "pre_tags": [""], * "post_tags": [""], * "fields": { * "author": {} * } * } * } */ @Query("{\n" + " \"match\": {\n" + " \"author\": \"?0\"\n" + " }\n" + "}") @Highlight( fields = { @HighlightField(name = "author") }, parameters = @HighlightParameters(preTags = "", postTags = "") ) List> listBookByNameHighlight(String name);
@Test public void annotation_highlight() { List> list = bookRespository.listBookByNameHighlight("罗贯中"); for (SearchHit hit : list) { System.out.println(hit.getScore()); System.out.println(hit.getContent()); List hitHighlightField = hit.getHighlightField("author"); hitHighlightField.forEach(System.out::println); } }
1.2039728 Book{id=1, name='三国演义', describe='天下大势,分久必合,合久必分。', author='罗贯中', price=55.55, createTime=Fri Oct 27 08:00:00 CST 2023} 罗贯中
ElasticsearchOperations是Spring Data Elasticsearch抽象出来的操作接口,有两个实现类:
ElasticsearchTemplate底层依赖于Transport Client,在4.0版本后已被标记为废弃。
配置:
@Configuration public class TransportClientConfig extends ElasticsearchConfigurationSupport { @Bean public Client elasticsearchClient() throws UnknownHostException { Settings settings = Settings.builder().put("cluster.name", "elasticsearch").build(); TransportClient client = new PreBuiltTransportClient(settings); client.addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); return client; } @Bean(name = {"elasticsearchOperations", "elasticsearchTemplate"}) public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException { return new ElasticsearchTemplate(elasticsearchClient()); } }
ElasticsearchRestTemplate底层依赖于High Level REST Client,所以配置也使用AbstractElasticsearchConfiguration,无须再次配置。
ElasticsearchRestTemplate中定义了一系列方法:
public IndexOperations indexOps(Class> clazz); public IndexOperations indexOps(IndexCoordinates index);
public String doIndex(IndexQuery query, IndexCoordinates index); publicT get(String id, Class clazz, IndexCoordinates index); public List multiGet(Query query, Class clazz, IndexCoordinates index); protected boolean doExists(String id, IndexCoordinates index); public void bulkUpdate(List queries, BulkOptions bulkOptions, IndexCoordinates index); public String delete(String id, @Nullable String routing, IndexCoordinates index); public void delete(Query query, Class> clazz, IndexCoordinates index); @Deprecated public void delete(DeleteQuery deleteQuery, IndexCoordinates index); public UpdateResponse update(UpdateQuery query, IndexCoordinates index); public List doBulkOperation(List> queries, BulkOptions bulkOptions, IndexCoordinates index);
public long count(Query query, @Nullable Class> clazz, IndexCoordinates index); publicSearchHits search(Query query, Class clazz, IndexCoordinates index); public SearchScrollHits searchScrollStart(long scrollTimeInMillis, Query query, Class clazz, IndexCoordinates index); public SearchScrollHits searchScrollContinue(@Nullable String scrollId, long scrollTimeInMillis, Class clazz, IndexCoordinates index); public void searchScrollClear(List scrollIds); public SearchResponse suggest(SuggestBuilder suggestion, IndexCoordinates index);
@Test public void createIndex() { IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Student.class); //是否存在 if (!indexOperations.exists()) { //根据绑定的实体类注解获取设置信息 Document settings = indexOperations.createSettings(); //创建索引 indexOperations.create(settings); //或者直接调用无参的create(),内部自动创建settings // indexOperations.create(); //根据实体类注解获取映射关系 Document mapping = indexOperations.createMapping(); //将mapping添加到索引中 indexOperations.putMapping(mapping); } }
@Test public void deleteIndex() { IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Student.class); if (indexOperations.exists()) { indexOperations.delete(); } }
@Test public void createIndex() { IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Student.class); //是否存在 if (!indexOperations.exists()) { //根据绑定的实体类注解获取设置信息 Document settings = indexOperations.createSettings(); //创建索引 indexOperations.create(settings); //或者直接调用无参的create(),内部自动创建settings // indexOperations.create(); //根据实体类注解获取映射关系 Document mapping = indexOperations.createMapping(); //将mapping添加到索引中 indexOperations.putMapping(mapping); } }
@Test public void aliasTest() { IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Student.class); //一系列别名操作 AliasActions actions = new AliasActions(); //新增别名 actions.add( new AliasAction.Add( AliasActionParameters.builder() .withAliases("a") .withIndices("student") .build()) ); //删除别名 actions.add( new AliasAction.Remove( AliasActionParameters.builder() .withAliases("a") .withIndices("student") .build()) ); boolean flag = indexOperations.alias(actions); System.out.println(flag); //通过别名查询,返回key为包含该别名的index的map Map> map = indexOperations.getAliases("a"); Set data = map.get("student"); data.forEach(e -> System.out.println(e.getAlias())); //通过index查询,返回返回key为包含该别名的index的map Map > map1 = indexOperations.getAliasesForIndex("student"); Set data1 = map1.get("student"); data1.forEach(e -> System.out.println(e.getAlias())); }
@Test public void templateTest() { IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Student.class); //新增模板 PutTemplateRequest request = PutTemplateRequest.builder("my-template", "pattern1", "pattern2") .withSettings( Document.create().append("index.number_of_shards", 3) ) .withMappings( Document.parse("{\n" + " \"_source\": {\n" + " \"enabled\": false\n" + " }\n" + "}") ) .withOrder(1) .withVersion(1) .build(); indexOperations.putTemplate(request); //获取模板 if (indexOperations.existsTemplate("my-template")) { TemplateData template = indexOperations.getTemplate("my-template"); System.out.println(template.getSettings().toJson()); System.out.println(template.getMapping().toJson()); } //删除模板 if (indexOperations.existsTemplate("my-template")) { boolean flag = indexOperations.deleteTemplate("my-template"); System.out.println(flag); } }
@Test public void add() { Student student = new Student(); student.setAge(23); student.setData("123"); student.setDesc("华为手机"); student.setId("1"); student.setName("张三"); Student save = elasticsearchRestTemplate.save(student); System.out.println(save); }
@Test public void addAll(){ Listlist = new ArrayList<>(); list.add(new Student("2","李四","苹果手机","1",22)); list.add(new Student("3","王五","oppo手机","2",24)); list.add(new Student("4","赵六","voio手机","3",25)); list.add(new Student("5","田七","小米手机","4",26)); Iterable result = elasticsearchRestTemplate.save(list); System.out.println(result); }
@Test public void update(){ Student student = new Student(); student.setId("1"); student.setAge(23); student.setData("99"); student.setDesc("华为手机AND苹果手机"); student.setName("张三"); Student save = elasticsearchRestTemplate.save(student); System.out.println(save); }
@Test public void update2(){ //脚本更新 String script = "ctx._source.age = 27;ctx._source.desc = 'oppo手机and苹果电脑'"; UpdateResponse update = elasticsearchRestTemplate.update( UpdateQuery.builder("3").withScript(script).build(), IndexCoordinates.of("student") ); System.out.println(update.getResult()); //部分文档更新 UpdateResponse update1 = elasticsearchRestTemplate.update( UpdateQuery.builder("3").withDocument(Document.create().append("age", 99)).build(), IndexCoordinates.of("student") ); System.out.println(update1.getResult()); }
/** * 根据主键查查询 */ @Test public void searchById(){ Student student = elasticsearchRestTemplate.get("3", Student.class); System.out.println(student); }
@Test public void deleteById() { String id = elasticsearchRestTemplate.delete("5", Student.class); System.out.println(id); }
在SearchOperations中定义的几乎所有方法都使用Query参数,该参数定义了要执行的查询以进行搜索。Query是一个接口,Spring Data Elasticsearch提供了三个实现:
基于CriteriaQuery的查询允许创建查询来搜索数据,而无需了解Elasticsearch查询的语法或基础知识。它们允许用户通过简单地链接和组合指定搜索文档必须满足的条件的条件对象来构建查询。
@Test public void criteriaQuery() { Criteria criteria = new Criteria("author").matches("吴承恩"); Query query = new CriteriaQuery(criteria); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); } }
@Test public void simple_criteria() { //EQUALS 等值查询 Criteria criteria = new Criteria("price").is(33.33); //EXISTS 存在查询 criteria = new Criteria("name").exists(); //BETWEEN 范围查询 criteria = new Criteria("price").between(20.0, 40.0); //CONTAINS 包含查询 支持keyword和text类型 criteria = new Criteria("describe").contains("师傅"); //ENDS_WITH 以..结尾 支持keyword和text类型 criteria = new Criteria("name").endsWith("记"); //STARTS_WITH 以..开始 支持keyword和text类型 criteria = new Criteria("name").startsWith("西"); //EXPRESSION 支持es的原生expression查询 criteria = new Criteria("name").expression("*游记"); //FUZZY 模糊查询 criteria = new Criteria("name").fuzzy("东游记"); //MATCH 匹配查询 默认使用OR运算符 criteria = new Criteria("describe").matches("丢了"); //MATCH_ALL 匹配查询 默认使用AND运算符 criteria = new Criteria("describe").matchesAll("丢了"); //IN 多值查询 仅支持keyword类型 criteria = new Criteria("name").in("三国演义", "西游记"); //NOT_IN 仅支持keyword类型 criteria = new Criteria("name").notIn("三国演义"); //LESS < criteria = new Criteria("price").lessThan(40.0); //LESS_EQUAL <= criteria = new Criteria("price").lessThanEqual(35.22); //GREATER > criteria = new Criteria("price").greaterThan(50.0); //GREATER_EQUAL >= criteria = new Criteria("price").greaterThanEqual(55.55); Query query = new CriteriaQuery(criteria); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); } }
就是构建条件链
And逻辑:
@Test public void and_criteria() { //条件:(20.0 < price < 80.0) && (name=三国演义) //组合条件时,默认情况下使用AND逻辑: Criteria criteria = new Criteria("price").greaterThan(20.0).lessThan(80.0); //等价于 criteria = new Criteria("price").greaterThan(20.0) .and("price").lessThan(80.0); //条件:(20.0 < price < 80.0) && (name=三国演义) && (author=罗贯中) criteria = new Criteria("price").between(20.0, 80.0) .and("name").is("三国演义") .and("author").is("罗贯中"); Query query = new CriteriaQuery(criteria); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); } }
or逻辑:
@Test public void or_criteria() { //条件:(name=三国演义) OR (author=曹雪芹) Criteria criteria = new Criteria("name").is("三国演义") .or("author").matches("曹雪芹"); //条件:(name=三国演义) OR (name=西游记) criteria = new Criteria("name").matches("三国演义") .or("name").matches("西游记"); Query query = new CriteriaQuery(criteria); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); } }
and和or逻辑组合:
@Test public void and_or_criteria() { //条件:(name=三国演义) && (author=罗贯中) || (name=西游记) Criteria criteria = new Criteria("name").is("三国演义") .and("author").matches("罗贯中") .or("name").is("西游记"); Query query = new CriteriaQuery(criteria); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); } }
如果要创建嵌套查询,则需要为此使用子查询。subCriteria()使用AND逻辑连接条件。
可以简单的理解为:每一个subCriteria()就开启了一个(),这个()和父条件之间使用AND连接。
@Test public void sub_criteria() { //示例一 //条件: (20.0 < price < 80.0) && (name=三国演义 || author=吴承恩) Criteria criteria = new Criteria("price").between(20.0, 80.0) .subCriteria(//添加子查询条件,默认and连接 new Criteria()//空条件 .or("name").is("三国演义")//以为第一个是空条件,所以此处or或and都可以 .or("author").is("吴承恩")//此处必须为or ); //上面构建的条件最原始的形态是:(20.0 < price < 80.0) && (空条件 || name=三国演义 || author=吴承恩) //示例二 //条件:(name=三国演义 && author=罗贯中) || (name=西游记 && author=吴承恩) criteria = new Criteria("name").is("三国演义") .subCriteria( new Criteria("author").is("罗贯中") ) .or( new Criteria("name").is("西游记") .subCriteria( new Criteria("author").is("吴承恩") ) ); //上面构建的条件最原始的形态是:(name=三国演义 && (author=罗贯中)) || (name=西游记 && (author=吴承恩)) //因为subCriteria使用and连接,所以只能这样写,非常反人类 Query query = new CriteriaQuery(criteria); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); } }
略…
不推荐使用CriteriaQuery进行GEO查询。
将JSON字符串作为一个Elasticsearch查询。
@Test public void string_query1() { /** * { * "query": { * "match": { * "author": "吴承恩" * } * } * } */ Query query = new StringQuery("{\n" + " \"match\": {\n" + " \"author\": \"吴承恩\"\n" + " }\n" + "}"); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); } }
Book{id=2, name='西游记', describe='大师兄!师傅又丢了!', author='吴承恩', price=33.33, createTime=Fri Oct 27 08:00:00 CST 2023}
@Test public void string_query2() { /** * { * "query": { * "bool": { * "must": [ * { * "range": { * "price": { * "gte": 20.0, * "lte": 80.0 * } * } * } * ], * "should": [ * { * "terms": { * "name": [ * "三国演义", * "西游记" * ] * } * } * ], * "must_not": [ * { * "match": { * "author": "曹雪芹" * } * } * ] * } * } * } */ Query query = new StringQuery("{\n" + " \"bool\": {\n" + " \"must\": [\n" + " {\n" + " \"range\": {\n" + " \"price\": {\n" + " \"gte\": 20.0,\n" + " \"lte\": 80.0\n" + " }\n" + " }\n" + " }\n" + " ],\n" + " \"should\": [\n" + " {\n" + " \"terms\": {\n" + " \"name\": [\n" + " \"三国演义\",\n" + " \"西游记\"\n" + " ]\n" + " }\n" + " }\n" + " ],\n" + " \"must_not\": [\n" + " {\n" + " \"match\": {\n" + " \"author\": \"曹雪芹\"\n" + " }\n" + " }\n" + " ]\n" + " }\n" + "}"); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); } }
Book{id=1, name='三国演义', describe='天下大势,分久必合,合久必分。', author='罗贯中', price=55.55, createTime=Fri Oct 27 08:00:00 CST 2023} Book{id=2, name='西游记', describe='大师兄!师傅又丢了!', author='吴承恩', price=33.33, createTime=Fri Oct 27 08:00:00 CST 2023} Book{id=4, name='水浒传', describe='招安', author='施耐庵', price=35.22, createTime=Fri Oct 27 08:00:00 CST 2023}
@Test public void string_query3() { /** * { * "query": { * "range": { * "price": { * "gte": 20.0, * "lte": 80.0 * } * } * }, * "from": 0, * "size": 2 * } */ Query query = new StringQuery("{\n" + " \"range\": {\n" + " \"price\": {\n" + " \"gte\": 20.0,\n" + " \"lte\": 80.0\n" + " }\n" + " }\n" + "}", PageRequest.of(0, 2)); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); } }
Book{id=1, name='三国演义', describe='天下大势,分久必合,合久必分。', author='罗贯中', price=55.55, createTime=Fri Oct 27 08:00:00 CST 2023} Book{id=2, name='西游记', describe='大师兄!师傅又丢了!', author='吴承恩', price=33.33, createTime=Fri Oct 27 08:00:00 CST 2023}
@Test public void string_query4() { /** * { * "query": { * "range": { * "price": { * "gte": 20.0, * "lte": 80.0 * } * } * }, * "sort": [ * { * "price": { * "order": "desc" * } * } * ], * "from": 0, * "size": 2 * } */ Query query = new StringQuery("{\n" + " \"range\": {\n" + " \"price\": {\n" + " \"gte\": 20.0,\n" + " \"lte\": 80.0\n" + " }\n" + " }\n" + "}", PageRequest.of(0, 2), Sort.by(Sort.Direction.DESC, "price")); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); List
Book{id=3, name='红楼梦', describe='一朝春尽红颜老,花落人亡两不知。', author='曹雪芹', price=66.66, createTime=Fri Oct 27 08:00:00 CST 2023} 66.66 Book{id=1, name='三国演义', describe='天下大势,分久必合,合久必分。', author='罗贯中', price=55.55, createTime=Fri Oct 27 08:00:00 CST 2023} 55.55
@Test public void string_query4() { /** * { * "query": { * "match": { * "describe": "师傅" * } * }, * "highlight": { * "pre_tags": [""], * "post_tags": [""], * "fields": { * "describe": {} * } * } * } */ Query query = new StringQuery("{\n" + " \"match\": {\n" + " \"describe\": \"师傅\"\n" + " }\n" + "}"); HighlightQuery highlightQuery = new HighlightQuery( new HighlightBuilder() .field("describe") .preTags("") .postTags("") ); query.setHighlightQuery(highlightQuery); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); List highlightField = hit.getHighlightField("describe"); highlightField.forEach(System.out::println); } }
Book{id=2, name='西游记', describe='大师兄!师傅又丢了!', author='吴承恩', price=33.33, createTime=Fri Oct 27 08:00:00 CST 2023} 师傅又丢了!
NativeSearchQuery是在具有复杂查询或无法使用Criteria API表示的查询时使用的类,例如在构建查询和使用聚合时。它允许使用Elasticsearch库中所有不同的QueryBuilder实现,因此被命名为“原生”。
@Test public void native_query1() { /** * { * "query": { * "match": { * "author": "曹雪芹" * } * } * } */ NativeSearchQuery query = new NativeSearchQuery( QueryBuilders.matchQuery("author", "曹雪芹") ); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); } }
Book{id=3, name='红楼梦', describe='一朝春尽红颜老,花落人亡两不知。', author='曹雪芹', price=66.66, createTime=Fri Oct 27 08:00:00 CST 2023}
@Test public void native_query2() { /** * { * "query": { * "bool": { * "must": [ * { * "range": { * "price": { * "gte": 20.0, * "lte": 80.0 * } * } * } * ], * "should": [ * { * "terms": { * "name": [ * "三国演义", * "西游记" * ] * } * } * ], * "must_not": [ * { * "match": { * "author": "曹雪芹" * } * } * ] * } * } * } */ NativeSearchQuery query = new NativeSearchQuery( QueryBuilders.boolQuery() .must( QueryBuilders.rangeQuery("price").gte(20.0).lte(80.0) ) .should( QueryBuilders.termsQuery("name", "三国演义", "西游记") ) .mustNot( QueryBuilders.matchQuery("author", "曹雪芹") ) ); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); } }
Book{id=1, name='三国演义', describe='天下大势,分久必合,合久必分。', author='罗贯中', price=55.55, createTime=Fri Oct 27 08:00:00 CST 2023} Book{id=2, name='西游记', describe='大师兄!师傅又丢了!', author='吴承恩', price=33.33, createTime=Fri Oct 27 08:00:00 CST 2023} Book{id=4, name='水浒传', describe='招安', author='施耐庵', price=35.22, createTime=Fri Oct 27 08:00:00 CST 2023}
@Test public void native_query3() { /** * { * "query": { * "range": { * "price": { * "gte": 20.0, * "lte": 80.0 * } * } * }, * "from": 0, * "size": 2 * } */ NativeSearchQuery query = new NativeSearchQuery( QueryBuilders.rangeQuery("price").gte(20.0).lte(80.0) ); query.setPageable(PageRequest.of(0, 2)); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); } }
Book{id=1, name='三国演义', describe='天下大势,分久必合,合久必分。', author='罗贯中', price=55.55, createTime=Fri Oct 27 08:00:00 CST 2023} Book{id=2, name='西游记', describe='大师兄!师傅又丢了!', author='吴承恩', price=33.33, createTime=Fri Oct 27 08:00:00 CST 2023}
@Test public void native_query4() { /** * { * "query": { * "range": { * "price": { * "gte": 20.0, * "lte": 80.0 * } * } * }, * "sort": [ * { * "price": { * "order": "desc" * } * } * ], * "from": 0, * "size": 2 * } */ NativeSearchQuery query = new NativeSearchQuery( QueryBuilders.rangeQuery("price").gte(20.0).lte(80.0) ) .addSort(Sort.by(Sort.Direction.DESC, "price")) .setPageable(PageRequest.of(0, 2)); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); List
Book{id=3, name='红楼梦', describe='一朝春尽红颜老,花落人亡两不知。', author='曹雪芹', price=66.66, createTime=Fri Oct 27 08:00:00 CST 2023} 66.66 Book{id=1, name='三国演义', describe='天下大势,分久必合,合久必分。', author='罗贯中', price=55.55, createTime=Fri Oct 27 08:00:00 CST 2023} 55.55
@Test public void native_query5() { /** * { * "query": { * "match": { * "describe": "师傅" * } * }, * "highlight": { * "pre_tags": [""], * "post_tags": [""], * "fields": { * "describe": {} * } * } * } */ NativeSearchQuery query = new NativeSearchQuery( QueryBuilders.matchQuery("describe", "师傅") ); query.setHighlightQuery( new HighlightQuery( new HighlightBuilder() .field("describe") .preTags("") .postTags("") ) ); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); List > list = hits.getSearchHits(); for (SearchHit hit : list) { System.out.println(hit.getContent()); List highlightField = hit.getHighlightField("describe"); highlightField.forEach(System.out::println); } }
Book{id=2, name='西游记', describe='大师兄!师傅又丢了!', author='吴承恩', price=33.33, createTime=Fri Oct 27 08:00:00 CST 2023} 师傅又丢了!
@Test public void native_query6() { /** * { * "size": 0, * "aggs": { * "name_count": { * "terms": { * "field": "name" * } * } * } * } */ NativeSearchQuery query = new NativeSearchQuery( QueryBuilders.matchAllQuery() ); query.addAggregation( AggregationBuilders.terms("name_agg").field("name") ); SearchHitshits = elasticsearchRestTemplate.search(query, Book.class, IndexCoordinates.of("book")); Aggregations aggregations = hits.getAggregations(); ParsedStringTerms terms = aggregations.get("name_agg"); List extends Terms.Bucket> buckets = terms.getBuckets(); buckets.forEach(e -> System.out.println(e.getKeyAsString() + ":" + e.getDocCount())); }
三国演义:1 水浒传:1 红楼梦:1 西游记:1