参考文章:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-document-index.html
该文章,主要是介绍elasticsearch7.x的rest java客户端。
定义一个request
IndexRequest request = new IndexRequest(索引名称);
request.id(文档id);
String jsonString = "{" +
"\"user\":\"kimchy\"," +
"\"postDate\":\"2013-01-30\"," +
"\"message\":\"trying out Elasticsearch\"" +
"}";
request.source(jsonString, XContentType.JSON);
12345678
执行request命令,分同步异步,一般使用同步
IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
1
定义一个request
根据文档id进行删除
DeleteRequest request = new DeleteRequest(
索引名称,
文档id);
123
执行request命令
DeleteResponse deleteResponse = client.delete(request, RequestOptions.DEFAULT);
1
定义一个request
UpdateRequest request = new UpdateRequest(索引名称, 文档id);
String jsonString = "{" +
"\"updated\":\"2017-01-01\"," +
"\"reason\":\"daily update\"" +
"}";
request.doc(jsonString, XContentType.JSON);
123456
执行request命令
UpdateResponse updateResponse = client.update(request, RequestOptions.DEFAULT);
1
查有非常多方式,elasticsearch作为一个开源搜索引擎,全文检索,结构化检索,数据分析,所以有很强大的搜索功能
定义一个request
SearchRequest searchRequest = new SearchRequest();
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);
1234
同样具有分页功能
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.termQuery("user", "kimchy"));
sourceBuilder.from(0);
sourceBuilder.size(5);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
12345
执行request命令
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
1
elasticsearch的版本要与客户端的版本一致
按照es官方文档描述elasticsearch-rest-client还需要依赖
The High Level Java REST Client depends on the following artifacts and their transitive dependencies:
org.elasticsearch.client:elasticsearch-rest-client
org.elasticsearch:elasticsearch
1234
<properties>
<java.version>1.8java.version>
<elasticsearch>7.5.1elasticsearch>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.elasticsearchgroupId>
<artifactId>elasticsearchartifactId>
<version>${elasticsearch}version>
dependency>
<dependency>
<groupId>org.elasticsearch.clientgroupId>
<artifactId>elasticsearch-rest-clientartifactId>
<version>${elasticsearch}version>
dependency>
<dependency>
<groupId>org.elasticsearch.clientgroupId>
<artifactId>elasticsearch-rest-high-level-clientartifactId>
<version>${elasticsearch}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.62version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>commons-beanutilsgroupId>
<artifactId>commons-beanutilsartifactId>
<version>1.8.3version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
实际上官方文档说 只引入一个依赖就可,实测可以。
注意看一下版本号下面的依赖,我用的最新的4.1.1
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-elasticsearchartifactId>
<version>4.1.1version>
dependency>
下图整理出部分搜索相关的
参考大神文章:https://blog.csdn.net/weixin_42408648/article/details/108199320
单个匹配termQuery
//不分词查询 参数1: 字段名,参数2:字段查询值,因为不分词,所以汉字只能查询一个字,英语是一个单词.
QueryBuilder queryBuilder=QueryBuilders.termQuery("fieldName", "fieldlValue");
//分词查询,采用默认的分词器
QueryBuilder queryBuilder2 = QueryBuilders.matchQuery("fieldName", "fieldlValue");
多个匹配
//不分词查询,参数1: 字段名,参数2:多个字段查询值,因为不分词,所以汉字只能查询一个字,英语是一个单词.
QueryBuilder queryBuilder=QueryBuilders.termsQuery("fieldName", "fieldlValue1","fieldlValue2...");
//分词查询,采用默认的分词器
QueryBuilder queryBuilder= QueryBuilders.multiMatchQuery("fieldlValue", "fieldName1", "fieldName2", "fieldName3");
//匹配所有文件,相当于就没有设置查询条件
QueryBuilder queryBuilder=QueryBuilders.matchAllQuery();
1234567891011121314
//模糊查询常见的5个方法如下
//1.常用的字符串查询
QueryBuilders.queryStringQuery("fieldValue").field("fieldName");//左右模糊
//2.常用的用于推荐相似内容的查询
QueryBuilders.moreLikeThisQuery(new String[] {
"fieldName"}).addLikeText("pipeidhua");//如果不指定filedName,则默认全部,常用在相似内容的推荐上
//3.前缀查询 如果字段没分词,就匹配整个字段前缀
QueryBuilders.prefixQuery("fieldName","fieldValue");
//4.fuzzy query:分词模糊查询,通过增加fuzziness模糊属性来查询,如能够匹配hotelName为tel前或后加一个字母的文档,fuzziness 的含义是检索的term 前后增加或减少n个单词的匹配查询
QueryBuilders.fuzzyQuery("hotelName", "tel").fuzziness(Fuzziness.ONE);
//5.wildcard query:通配符查询,支持* 任意字符串;?任意一个字符
QueryBuilders.wildcardQuery("fieldName","ctr*");//前面是fieldname,后面是带匹配字符的字符串
QueryBuilders.wildcardQuery("fieldName","c?r?");
123456789101112
//闭区间查询
QueryBuilder queryBuilder0 = QueryBuilders.rangeQuery("fieldName").from("fieldValue1").to("fieldValue2");
//开区间查询
QueryBuilder queryBuilder1 = QueryBuilders.rangeQuery("fieldName").from("fieldValue1").to("fieldValue2").includeUpper(false).includeLower(false);//默认是true,也就是包含
//大于
QueryBuilder queryBuilder2 = QueryBuilders.rangeQuery("fieldName").gt("fieldValue");
//大于等于
QueryBuilder queryBuilder3 = QueryBuilders.rangeQuery("fieldName").gte("fieldValue");
//小于
QueryBuilder queryBuilder4 = QueryBuilders.rangeQuery("fieldName").lt("fieldValue");
//小于等于
QueryBuilder queryBuilder5 = QueryBuilders.rangeQuery("fieldName").lte("fieldValue");
123456789101112
QueryBuilders.boolQuery()
QueryBuilders.boolQuery().must();//文档必须完全匹配条件,相当于and
QueryBuilders.boolQuery().mustNot();//文档必须不匹配条件,相当于not
QueryBuilders.boolQuery().should();//至少满足一个条件,这个文档就符合should,相当于or
1234
采用spring对RestHighLevelClient进行初始化
@Configuration
public class ElasticsearchConfig {
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client=new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost", 9200, "http")));
return client;
}
}
123456789
/**
* es 的工具类
*
* @author czchen
* @version 1.0
* @date 2020/8/25 14:37
*/
@Slf4j
@Component
public class EsUtil {
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 关键字
*/
public static final String KEYWORD = ".keyword";
/**
* 创建索引
*
* @param index 索引
* @return
*/
public boolean createIndex(String index) throws IOException {
if(isIndexExist(index)){
log.error("Index is exits!");
return false;
}
//1.创建索引请求
CreateIndexRequest request = new CreateIndexRequest(index);
//2.执行客户端请求
CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
log.info("创建索引{}成功",index);
return response.isAcknowledged();
}
/**
* 删除索引
*
* @param index
* @return
*/
public boolean deleteIndex(String index) throws IOException {
if(!isIndexExist(index)) {
log.error("Index is not exits!");
return false;
}
//删除索引请求
DeleteIndexRequest request = new DeleteIndexRequest(index);
//执行客户端请求
AcknowledgedResponse delete = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
log.info("删除索引{}成功",index);
return delete.isAcknowledged();
}
/**
* 判断索引是否存在
*
* @param index
* @return
*/
public boolean isIndexExist(String index) throws IOException {
GetIndexRequest request = new GetIndexRequest(index);
boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
return exists;
}
/**
* 数据添加,正定ID
*
* @param jsonObject 要增加的数据
* @param index 索引,类似数据库
* @param id 数据ID, 为null时es随机生成
* @return
*/
public String addData(JSONObject jsonObject, String index, String id) throws IOException {
//创建请求
IndexRequest request = new IndexRequest(index);
//规则 put /test_index/_doc/1
request.id(id);
request.timeout(TimeValue.timeValueSeconds(1));
//将数据放入请求 json
IndexRequest source = request.source(jsonObject, XContentType.JSON);
//客户端发送请求
IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
log.info("添加数据成功 索引为: {}, response 状态: {}, id为: {}",index,response.status().getStatus(), response.getId());
return response.getId();
}
/**
* 数据添加 随机id
*
* @param jsonObject 要增加的数据
* @param index 索引,类似数据库
* @return
*/
public String addData(JSONObject jsonObject, String index) throws IOException {
return addData(jsonObject, index, UUID.randomUUID().toString().replaceAll("-", "").toUpperCase());
}
/**
* 通过ID删除数据
*
* @param index 索引,类似数据库
* @param id 数据ID
*/
public void deleteDataById(String index, String id) throws IOException {
//删除请求
DeleteRequest request = new DeleteRequest(index, id);
//执行客户端请求
DeleteResponse delete = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
log.info("索引为: {}, id为: {}删除数据成功",index, id);
}
/**
* 通过ID 更新数据
*
* @param object 要增加的数据
* @param index 索引,类似数据库
* @param id 数据ID
* @return
*/
public void updateDataById(Object object, String index, String id) throws IOException {
//更新请求
UpdateRequest update = new UpdateRequest(index, id);
//保证数据实时更新
//update.setRefreshPolicy("wait_for");
update.timeout("1s");
update.doc(JSON.toJSONString(object), XContentType.JSON);
//执行更新请求
UpdateResponse update1 = restHighLevelClient.update(update, RequestOptions.DEFAULT);
log.info("索引为: {}, id为: {}, 更新数据成功",index, id);
}
/**
* 通过ID 更新数据,保证实时性
*
* @param object 要增加的数据
* @param index 索引,类似数据库
* @param id 数据ID
* @return
*/
public void updateDataByIdNoRealTime(Object object, String index, String id) throws IOException {
//更新请求
UpdateRequest update = new UpdateRequest(index, id);
//保证数据实时更新
update.setRefreshPolicy("wait_for");
update.timeout("1s");
update.doc(JSON.toJSONString(object), XContentType.JSON);
//执行更新请求
UpdateResponse update1 = restHighLevelClient.update(update, RequestOptions.DEFAULT);
log.info("索引为: {}, id为: {}, 更新数据成功",index, id);
}
/**
* 通过ID获取数据
*
* @param index 索引,类似数据库
* @param id 数据ID
* @param fields 需要显示的字段,逗号分隔(缺省为全部字段)
* @return
*/
public Map<String,Object> searchDataById(String index, String id, String fields) throws IOException {
GetRequest request = new GetRequest(index, id);
if (StringUtils.isNotEmpty(fields)){
//只查询特定字段。如果需要查询所有字段则不设置该项。
request.fetchSourceContext(new FetchSourceContext(true,fields.split(","), Strings.EMPTY_ARRAY));
}
GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
Map<String, Object> map = response.getSource();
//为返回的数据添加id
map.put("id",response.getId());
return map;
}
/**
* 通过ID判断文档是否存在
* @param index 索引,类似数据库
* @param id 数据ID
* @return
*/
public boolean existsById(String index,String id) throws IOException {
GetRequest request = new GetRequest(index, id);
//不获取返回的_source的上下文
request.fetchSourceContext(new FetchSourceContext(false));
request.storedFields("_none_");
return restHighLevelClient.exists(request, RequestOptions.DEFAULT);
}
/**
* 获取低水平客户端
* @return
*/
public RestClient getLowLevelClient() {
return restHighLevelClient.getLowLevelClient();
}
/**
* 高亮结果集 特殊处理
* map转对象 JSONObject.parseObject(JSONObject.toJSONString(map), Content.class)
* @param searchResponse
* @param highlightField
*/
public List<Map<String, Object>> setSearchResponse(SearchResponse searchResponse, String highlightField) {
//解析结果
ArrayList<Map<String,Object>> list = new ArrayList<>();
for (SearchHit hit : searchResponse.getHits().getHits()) {
Map<String, HighlightField> high = hit.getHighlightFields();
HighlightField title = high.get(highlightField);
hit.getSourceAsMap().put("id", hit.getId());
Map<String, Object> sourceAsMap = hit.getSourceAsMap();//原来的结果
//解析高亮字段,将原来的字段换为高亮字段
if (title!=null){
Text[] texts = title.fragments();
String nTitle="";
for (Text text : texts) {
nTitle+=text;
}
//替换
sourceAsMap.put(highlightField,nTitle);
}
list.add(sourceAsMap);
}
return list;
}
/**
* 查询并分页
* @param index 索引名称
* @param query 查询条件
* @param size 文档大小限制
* @param from 从第几页开始
* @param fields 需要显示的字段,逗号分隔(缺省为全部字段)
* @param sortField 排序字段
* @param highlightField 高亮字段
* @return
*/
public List<Map<String, Object>> searchListData(String index,
SearchSourceBuilder query,
Integer size,
Integer from,
String fields,
String sortField,
String highlightField) throws IOException {
SearchRequest request = new SearchRequest(index);
SearchSourceBuilder builder = query;
if (StringUtils.isNotEmpty(fields)){
//只查询特定字段。如果需要查询所有字段则不设置该项。
builder.fetchSource(new FetchSourceContext(true,fields.split(","),Strings.EMPTY_ARRAY));
}
from = from <= 0 ? 0 : from*size;
//设置确定结果要从哪个索引开始搜索的from选项,默认为0
builder.from(from);
builder.size(size);
if (StringUtils.isNotEmpty(sortField)){
//排序字段,注意如果proposal_no是text类型会默认带有keyword性质,需要拼接.keyword
builder.sort(sortField+".keyword", SortOrder.ASC);
}
//高亮
HighlightBuilder highlight = new HighlightBuilder();
highlight.field(highlightField);
//关闭多个高亮
highlight.requireFieldMatch(false);
highlight.preTags("");
highlight.postTags("");
builder.highlighter(highlight);
//不返回源数据。只有条数之类的数据。
//builder.fetchSource(false);
request.source(builder);
SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
log.error("=="+response.getHits().getTotalHits());
if (response.status().getStatus() == 200) {
// 解析对象
return setSearchResponse(response, highlightField);
}
return null;
}
}
package cn.stylefeng.guns.modular.es.entity;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.stylefeng.guns.GunsApplication;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.Map;
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = GunsApplication.class)
public class UserTest {
@Resource
private ElasticsearchRestTemplate template;
@Resource
private RestHighLevelClient client;
@Test
public void testCreateIndexAndDoc() throws IOException {
//1.创建索引请求
CreateIndexRequest request = new CreateIndexRequest("user");
//2.执行客户端请求
CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
log.info("创建索引{}成功", response);
System.out.println("response.isAcknowledged() = " + response.isAcknowledged());
}
@Test
public void saveDoc() throws IOException {
String index = "1";
User user = new User();
user.setId(1L);
user.setAge(20);
user.setName("狗蛋");
user.setPassword("23243424");
//创建请求
IndexRequest request = new IndexRequest("user");
//规则 put /test_index/_doc/1
request.id(index);
request.timeout(TimeValue.timeValueSeconds(1));
//将数据放入请求 json
request.source(user, XContentType.JSON);
//客户端发送请求
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
log.info("添加数据成功 索引为: {}, response 状态: {}, id为: {}",index,response.status().getStatus(), response.getId());
log.info("=========={}",response.getId());
}
@Test
public void saveDoc2() throws IOException {
IndexRequest request = new IndexRequest("user");
User user = new User();
user.setId(1L);
user.setAge(20);
user.setName("狗蛋");
user.setPassword("23243424");
// String s = JSONUtil.pa
request.id("1");
String jsonString = "{" +
"\"id\":\"kimchy\"," +
"\"postDate\":\"2013-01-30\"," +
"\"message\":\"trying out Elasticsearch\"" +
"}";
// request.source(jsonString, XContentType.JSON);
Map<String, Object> stringObjectMap = BeanUtil.beanToMap(user);
request.source(stringObjectMap);
// request.source()
//客户端发送请求
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
// IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
log.info("添加数据成功 索引为: {}, response 状态: {}, id为: {}","user",response.status().getStatus(), response.getId());
log.info("=========={}",response.getId());
}
@Test
public void testStrToBean() {
User user = new User();
user.setId(1L);
user.setAge(20);
user.setName("狗蛋");
user.setPassword("23243424");
JSONObject jsonObject = JSONUtil.parseObj(user);
System.out.println(jsonObject.toString());
}
@Test
public void saveDocRandomId() throws IOException {
IndexRequest request = new IndexRequest("user");
User user = new User();
user.setId(2L);
user.setAge(30);
user.setName("狗蛋333");
user.setPassword("23dsfasf243424");
// request.id("1");
Map<String, Object> stringObjectMap = BeanUtil.beanToMap(user);
request.source(stringObjectMap);
// request.source()
//客户端发送请求
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
// IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
log.info("添加数据成功 索引为: {}, response 状态: {}, id为: {}","user",response.status().getStatus(), response.getId());
log.info("=========={}",response.getId());
}
}
因为是基于springboot,那就不再使用kibana进行建立索引,而是用springboot来创建.创建索引的api:
ElasticsearchRestTemplate.createIndex(实体类.class)
如,现在创建一个名为esBean的实体类
相当于建库:
@Document用于指定索引
@Field(type = FieldType.Text,analyzer = “ik_max_word”)用于指定类型
@Data
@NoArgsConstructor
@Document(indexName = "test")
public class esBean {
@Id
private int id;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String name;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String tags;
public esBean(int id,String name,String tags){
this.id=id;
this.name=name;
this.tags=tags;
}
}
12345678910111213141516171819
service层:
public class esService implements IesService {
@Autowired
private ElasticsearchRestTemplate template;
@Override
public void create(){
template.createIndex(esBean.class);
}
}
123456789
像这样,就会自动建立一个名为test的索引,表结果就是esBean的成员
存储数据
需要一个持久化层的接口去继承ElasticsearchRepository,接着在service层中,依赖注入,直接调用其saveAll()方法即可,如下:
持久层:(可以在这里做一些搜索查询)
@Service
public interface esMapper extends ElasticsearchRepository<esBean,Long> {
}
12345
service层:
可以看到,saveAll,findAll这些方法是原来就有的,无需自己定义
public class esService implements IesService {
@Autowired
private ElasticsearchRestTemplate template;
@Autowired
esMapper mapper;
@Override
public Iterator<esBean> findAll() {
return mapper.findAll().iterator();
}
public void create(){
template.createIndex(esBean.class);
}
@Override
public void saveAll(List<esBean> list) {
mapper.saveAll(list);
}
}
12345678910111213141516171819
controller层:
@Autowired
IesService iesService;
@GetMapping("/init1")
public String init1(){
iesService.create();
// id name tags
List<esBean>list=new ArrayList<>();
list.add(new esBean(1,"张三锕爱","很帅,有很多人喜欢他"));
list.add(new esBean(2,"李四酷狗","很帅,但是讨厌他"));
list.add(new esBean(3,"王五王二爷","很丑,有是喜欢他"));
list.add(new esBean(4,"张三王二婆","很帅,有没人喜欢他"));
iesService.saveAll(list);
return "success";
}
123456789101112131415
ElasticsearchRestTemplate的基本api
SearchQuery 总的查询
BoolQueryBuilder bool查询,可在后面加上must,mustNot,should等等
MatchQueryBuilder 匹配查询
TermQueryBuilder 倒排索引查询
HighlightBuilder 高亮查询,用于设置要高亮的field
SearchHit 查询结果
实体类:
@Data
@NoArgsConstructor
@Document(indexName = "test5")
public class esBean {
@Id
private int id;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String name;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String tags;
public esBean(int id,String name,String tags){
this.id=id;
this.name=name;
this.tags=tags;
}
}
1234567891011121314151617
持久层
@Service
public interface esMapper extends ElasticsearchRepository<esBean,Long> {
}
123456
service:
public interface IesService {
public Iterator<esBean> findAll();
public void saveAll(List<esBean> list);
public void create();
public AggregatedPage testForHigh() throws IOException;
}
123456
@Service
public class esService implements IesService {
@Autowired
private ElasticsearchRestTemplate template;
@Autowired
esMapper mapper;
@Override
public Iterator<esBean> findAll() {
return mapper.findAll().iterator();
}
public void create(){
template.createIndex(esBean.class);
}
@Override
public void saveAll(List<esBean> list) {
mapper.saveAll(list);
}
public AggregatedPage testForHigh() throws IOException {
String preTag = "";//google的色值
String postTag = "";
BoolQueryBuilder boolQueryBuilder=new BoolQueryBuilder()
.must(new MatchQueryBuilder("tags","很帅"));
SearchQuery searchQuery=new NativeSearchQueryBuilder(). //总的查询
withQuery(boolQueryBuilder). //设置bool查询
withHighlightFields(new HighlightBuilder.Field("name").preTags(preTag).postTags(postTag)).//设置高亮效果
withHighlightFields(new HighlightBuilder.Field("tags").preTags(preTag).postTags(postTag)).build();
AggregatedPage<esBean> ideas=template.queryForPage(searchQuery, esBean.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
List<esBean> list = new ArrayList<>();
for(SearchHit hit:searchResponse.getHits()){
//获取遍历查询结果
if(searchResponse.getHits().getHits().length<=0)return null;
esBean bean=new esBean();
Map map=hit.getSourceAsMap();
System.out.println(map);
bean.setId((Integer)map.get("id"));
bean.setName((String)map.get("name"));
HighlightField name=hit.getHighlightFields().get("name");
if(name!=null){
bean.setName(name.fragments()[0].toString()); //得到高亮的结果
}
HighlightField tags=hit.getHighlightFields().get("tags");
if(tags!=null){
bean.setTags(tags.fragments()[0].toString());
}
list.add(bean);
}
if(list.size()>0)return new AggregatedPageImpl<>((List<T>)list);
return null;
}
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> aClass) {
return null;
}
});
ideas.get().forEach(model->{
System.out.println(model);
});
return ideas;
}
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> aClass) {
return null;
}
});
ideas.get().forEach(model->{
System.out.println(model);
});
return ideas;
}
}
controller:
@RestController
@RequestMapping("/elastic")
public class ElasticController {
@Autowired
IesService iesService;
@GetMapping("/init1")
public String init1(){
iesService.create();
// id name tags
List<esBean>list=new ArrayList<>();
list.add(new esBean(1,"张三锕爱","很帅,有很多人喜欢他"));
list.add(new esBean(2,"李四酷狗","很帅,但是讨厌他"));
list.add(new esBean(3,"王五王二爷","很丑,有是喜欢他"));
list.add(new esBean(4,"张三王二婆","很帅,有没人喜欢他"));
iesService.saveAll(list);
return "success";
}
@GetMapping("/get1")
public AggregatedPage get1() throws IOException {
return iesService.testForHigh();
}
}
postman访问:
Spring Data 的强大之处,就在于你不用写任何DAO处理,自动根据方法名或类的信息进行CRUD操作。只要你定义一个接口,然后继承Repository提供的一些子接口,就能具备各种基本的CRUD功能。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D7M8jsnj-1606058387968)(/Users/allen/Documents/TyporaFiles/gulimall/resource/%25E8%25B5%2584%25E6%2596%2599%25E6%2596%2587%25E6%25A1%25A3/%25E9%2598%25B6%25E6%25AE%25B5md/assets/1575806287671.png)]
其中ElasticsearchRepository接口功能最强大。该接口的方法包括:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DV78cXpV-1606058387976)(/Users/allen/Documents/TyporaFiles/gulimall/resource/%25E8%25B5%2584%25E6%2596%2599%25E6%2596%2587%25E6%25A1%25A3/%25E9%2598%25B6%25E6%25AE%25B5md/assets/1575806405547.png)]
@Autowired
UserRepository userRepository;
@Test
void testAdd(){
this.userRepository.save(new User(1l, "zhang3", 20, "123456"));
}
修改和新增是同一个接口,区分的依据就是id,这一点跟我们在页面发起PUT请求是类似的。
@Test
void testDelete(){
this.userRepository.deleteById(1l);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l4Bx6eRd-1606058387984)(/Users/allen/Documents/TyporaFiles/gulimall/resource/%25E8%25B5%2584%25E6%2596%2599%25E6%2596%2587%25E6%25A1%25A3/%25E9%2598%25B6%25E6%25AE%25B5md/assets/1575848896764.png)]
查询一个:
@Test
void testFind(){
System.out.println(this.userRepository.findById(1l).get());
}
Spring Data 的另一个强大功能,是根据方法名称自动实现功能。
比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。
当然,方法名称要符合一定的约定:
Keyword | Sample | Elasticsearch Query String |
---|---|---|
And |
findByNameAndPrice |
{"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Or |
findByNameOrPrice |
{"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Is |
findByName |
{"bool" : {"must" : {"field" : {"name" : "?"}}}} |
Not |
findByNameNot |
{"bool" : {"must_not" : {"field" : {"name" : "?"}}}} |
Between |
findByPriceBetween |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
LessThanEqual |
findByPriceLessThan |
{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
GreaterThanEqual |
findByPriceGreaterThan |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Before |
findByPriceBefore |
{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
After |
findByPriceAfter |
{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Like |
findByNameLike |
{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
StartingWith |
findByNameStartingWith |
{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
EndingWith |
findByNameEndingWith |
{"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}} |
Contains/Containing |
findByNameContaining |
{"bool" : {"must" : {"field" : {"name" : {"query" : "**?**","analyze_wildcard" : true}}}}} |
In |
findByNameIn(Collection |
{"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}} |
NotIn |
findByNameNotIn(Collection |
{"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}} |
Near |
findByStoreNear |
Not Supported Yet ! |
True |
findByAvailableTrue |
{"bool" : {"must" : {"field" : {"available" : true}}}} |
False |
findByAvailableFalse |
{"bool" : {"must" : {"field" : {"available" : false}}}} |
OrderBy |
findByAvailableTrueOrderByNameDesc |
{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}} |
准备一组数据:
@Test
void testAddAll(){
List<User> users = new ArrayList<>();
users.add(new User(1l, "柳岩", 18, "123456"));
users.add(new User(2l, "范冰冰", 19, "123456"));
users.add(new User(3l, "李冰冰", 20, "123456"));
users.add(new User(4l, "锋哥", 21, "123456"));
users.add(new User(5l, "小鹿", 22, "123456"));
users.add(new User(6l, "韩红", 23, "123456"));
this.userRepository.saveAll(users);
}
在UserRepository中定义一个方法:
第一种写法:
public interface UserRepository extends ElasticsearchRepository<User, Long> {
/**
* 根据年龄区间查询
* @param age1
* @param age2
* @return
*/
List<User> findByAgeBetween(Integer age1, Integer age2);
}
测试:
@Test
void testFindByAgeBetween(){
System.out.println(this.userRepository.findByAgeBetween(20, 30));
}
第二种写法:
这个就牛逼了,可以实现复杂查询
@Query("{\n" +
" \"range\": {\n" +
" \"age\": {\n" +
" \"gte\": \"?0\",\n" +
" \"lte\": \"?1\"\n" +
" }\n" +
" }\n" +
" }")
List<User> findByQuery(Integer age1, Integer age2);
测试:
@Test
void testFindByQuery(){
System.out.println(this.userRepository.findByQuery(20, 30));
}
@Test
void testNative(){
// 初始化自定义查询对象
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 构建查询
queryBuilder.withQuery(QueryBuilders.matchQuery("name", "冰冰"));
// 排序
queryBuilder.withSort(SortBuilders.fieldSort("age").order(SortOrder.ASC));
// 分页
queryBuilder.withPageable(PageRequest.of(0, 2));
// 高亮
queryBuilder.withHighlightBuilder(new HighlightBuilder().field("name").preTags("").postTags(""));
// 执行查询,获取分页结果集
Page<User> userPage = this.userRepository.search(queryBuilder.build());
// 总页数
System.out.println(userPage.getTotalPages());
// 总记录数
System.out.println(userPage.getTotalElements());
// 当前页数据
System.out.println(userPage.getContent());
}
NativeSearchQueryBuilder:Spring提供的一个查询条件构建器,帮助构建json格式的请求体
Page
:默认是分页查询,因此返回的是一个分页的结果对象,包含属性:
搜索address中包含mill的所有人的年龄分布以及平均年龄,平均薪资
GET bank/_search
{
"query": {
"match": {
"address": "Mill"
}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 10
}
},
"ageAvg": {
"avg": {
"field": "age"
}
},
"balanceAvg": {
"avg": {
"field": "balance"
}
}
}
}
java实现
/**
* 复杂检索:在bank中搜索address中包含mill的所有人的年龄分布以及平均年龄,平均薪资
* @throws IOException
*/
@Test
public void searchData() throws IOException {
//1. 创建检索请求
SearchRequest searchRequest = new SearchRequest();
//1.1)指定索引
searchRequest.indices("bank");
//1.2)构造检索条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchQuery("address","Mill"));
//1.2.1)按照年龄分布进行聚合
TermsAggregationBuilder ageAgg=AggregationBuilders.terms("ageAgg").field("age").size(10);
sourceBuilder.aggregation(ageAgg);
//1.2.2)计算平均年龄
AvgAggregationBuilder ageAvg = AggregationBuilders.avg("ageAvg").field("age");
sourceBuilder.aggregation(ageAvg);
//1.2.3)计算平均薪资
AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
sourceBuilder.aggregation(balanceAvg);
System.out.println("检索条件:"+sourceBuilder);
searchRequest.source(sourceBuilder);
//2. 执行检索
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("检索结果:"+searchResponse);
//3. 将检索结果封装为Bean
SearchHits hits = searchResponse.getHits();
SearchHit[] searchHits = hits.getHits();
for (SearchHit searchHit : searchHits) {
String sourceAsString = searchHit.getSourceAsString();
Account account = JSON.parseObject(sourceAsString, Account.class);
System.out.println(account);
}
//4. 获取聚合信息
Aggregations aggregations = searchResponse.getAggregations();
Terms ageAgg1 = aggregations.get("ageAgg");
for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
String keyAsString = bucket.getKeyAsString();
System.out.println("年龄:"+keyAsString+" ==> "+bucket.getDocCount());
}
Avg ageAvg1 = aggregations.get("ageAvg");
System.out.println("平均年龄:"+ageAvg1.getValue());
Avg balanceAvg1 = aggregations.get("balanceAvg");
System.out.println("平均薪资:"+balanceAvg1.getValue());
}
可以尝试对比打印的条件和执行结果,和前面的ElasticSearch的检索语句和检索结果进行比较;
据说ElasticsearchRepository并不好用,我还没深度使用,盲猜是复杂查询的时候嵌套和平行关系有点乱哈哈,那么问题来了,如果javaAPI里面能使用原生restAPI查询就好了,借助kibana直接写完就可以放到代码中执行,美滋滋。有空继续研究一下。