SpringData操作ElasticSearch实现搜索
整体思路
1.引入依赖
<!--注意:低版本的springboot 不支持springboot data-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
</parent>
<!--springboot web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--通过spring data 操作Es-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!--springboot 继承test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入lombook-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
2.编写yml配置
spring:
data:
elasticsearch:
cluster-nodes: 192.168.17.148:9300 #指定连接ip地址和端口号
3.创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "literature",type = "poem")
public class TPoem implements Serializable {
private static final long serialVersionUID = -90864583830296063L;
@Id
private String id;
@Field(type= FieldType.Keyword )
private String name;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String author;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String type;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String content;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String href;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String authordes;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String origin;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String imagepath;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String categoryid;
@Document : 代表一个文档记录
indexName : 用来指定索引名称
type : 用来指定索引类型
@Id : 用来将对象中id和ES中_id映射
@Field : 用来指定ES中的字段对应Mapping
type : 用来指定ES中存储类型
analyzer : 用来指定使用哪种分词器
4-1.基本查询/添加/更新/删除
编写Repository接口类 实现 ElasticsearchRepository接口
//ElasticsearchRepository接口泛型:<操作索引对应实体类的类型,id的类型>
public interface TPoemRepository extends ElasticsearchRepository<TPoem,String> {
/*
* ①接口中不写方法,使用默认提供的方法
* ②接口中根据命名规则自定义方法
* */
List<TPoem> findAllByContent(String content);//根据内容查找
List<TPoem> findAllByNameAndAndAuthor(String name,String author);//根据名字和作者查找
List<TPoem> findAllByNameOrAuthor(String name,String author);//根据名字或作者查找
/*
List<TPoem> findByXXXBetween(Double fromPrice,Double toPrice);//区间查询 xxx要是数值类型的字段
List<TPoem> findByXXXAfter(Double price);//在指定数值之前 xxx要是数值类型的字段
List<TPoem> findByXXXBefore(Double price);//在指定数值之后 xxx要是数值类型的字段
等等...
*/
}
使用
@SpringBootTest(classes = Application.class)
@RunWith(SpringRunner.class)
public class poemTests {
//注入TPoemRepository
@Autowired
private TPoemRepository poemRepository;
// 一、使用默认提供的方法
//添加索引和更新索引 id存在更新 不存在添加 id没赋值会随机生成id
@Test
public void test0(){
TPoem tPoem = new TPoem("1", "雨", "某人", "唐诗", "春雨一直下", "...", "某人", "网络", ".", ".");
poemRepository.save(tPoem);//添加单个文档
/* //添加多个文档
List<TPoem> list = new ArrayList<>();
list.add(new TPoem("2", "雨", "某人", "唐诗", "春雨一直下", "...", "某人", "网络", ".", "."));
list.add(new TPoem(null, "雪", "某人", "唐诗", "春雨一直下", "...", "某人", "网络", ".", "."));
poemRepository.saveAll(list);*/
}
//删除
@Test
public void test1(){
poemRepository.deleteById("2");//删除单个文档
poemRepository.deleteAll();//删除所有文档
}
//查询
@Test
public void test2(){
Optional<TPoem> optionalTPoem = poemRepository.findById("1");//根据id查询
System.out.println(optionalTPoem.get());
Iterable<TPoem> all = poemRepository.findAll();//查询所有
Iterable<TPoem> all1 = poemRepository.findAll(Sort.by(Sort.Order.desc("排序的字段")));//查询所有并排序
Page<TPoem> all2 = poemRepository.findAll(PageRequest.of(0, 2));//查询所有并分页
}
// 二、使用自定义方法
//查询
@Test
public void test3(){
List<TPoem> tPoems = poemRepository.findAllByContent("丞相");//根据内容查找
List<TPoem> tPoems1= poemRepository.findAllByNameAndAndAuthor("蜀相","杜甫");//根据名字和作者查找
List<TPoem> tPoems2= poemRepository.findAllByNameOrAuthor("蜀相","杜甫");//根据名字或作者查找
}
}
4-2.复杂查询
自定义复杂方法接口(这里没写方法的参数,可以自己写索引,类型,查询字段和内容等等…)
public interface PoemDao {
//删除索引
public void deleteIndex();
//条件普通查询
List<TPoem> mathQuery();
//boole查询
List<TPoem> boolQuery();
//分页和排序 查询所有
List<TPoem> sortAndPageQuery();
//多字段 高亮查询
List<TPoem> highlightQuery();
}
实现类
@Repository
public class PoemDaoImpl implements PoemDao {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
//删除索引
public void deleteIndex() {
elasticsearchTemplate.deleteIndex(TPoem.class);
}
//条件普通查询
@Override
public List<TPoem> mathQuery() {
//构建查询对象
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withIndices("literature").withTypes("poem")//指定操作索引名和类型名
.withQuery(QueryBuilders.matchQuery("name", "蜀相"))//指定查询方式
.build();
//执行查询
List<TPoem> tPoems = elasticsearchTemplate.queryForList(searchQuery, TPoem.class);
return tPoems;
}
//boole查询
@Override
public List<TPoem> boolQuery() {
//构建查询对象
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withIndices("literature").withTypes("poem")//指定操作索引名和类型名
.withQuery(QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("content", "女")))//指定查询方式
.build();
//执行查询
List<TPoem> tPoems = elasticsearchTemplate.queryForList(searchQuery, TPoem.class);
return tPoems;
}
//分页和排序 查询所有
@Override
public List<TPoem> sortAndPageQuery() {
//构建查询对象
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withIndices("literature").withTypes("poem")//指定操作索引名和类型名
.withQuery(QueryBuilders.matchAllQuery())//指定查询方式
.withPageable(PageRequest.of(0, 3))//分页
// .withSort(SortBuilders.fieldSort("xxx").order(SortOrder.DESC))//根据数值类型的字段降序排列
.build();
//执行查询
List<TPoem> tPoems = elasticsearchTemplate.queryForList(searchQuery, TPoem.class);
return tPoems;
}
//多字段 高亮查询
@Override
public List<TPoem> highlightQuery() {
//构建高亮查询对象
HighlightBuilder.Field nameField = new HighlightBuilder
.Field("*")//设置高亮的字段,*代表匹配到的所有字段都是高亮显示
.preTags("<span style='color:red'>")
.postTags("</span>").requireFieldMatch(false);
//构建查询对象
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withIndices("literature").withTypes("poem")//指定操作索引名和类型名
.withQuery(QueryBuilders.multiMatchQuery("王", "name", "author", "content"))//可同时在name和content和author查询 对应实体类中的属性名
.withHighlightFields(nameField)
.build();
//执行查询
AggregatedPage<TPoem> aggregatedPage = elasticsearchTemplate.queryForPage(searchQuery, TPoem.class,
new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
// 获取查询结果中的所有文档
SearchHit[] hits = searchResponse.getHits().getHits();
ArrayList<TPoem> poemList = new ArrayList<>();
for (SearchHit hit : hits) {
// 获取原生的结果
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
//赋值给sourceAsMap中的id→与hit中的id一致
sourceAsMap.put("id", hit.getId());
//获取高亮字段的结果
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
Set<Map.Entry<String, Object>> entries = sourceAsMap.entrySet();
for (Map.Entry<String, Object> entry : entries) {
//通过key获取高亮字段
HighlightField highlightField = highlightFields.get(entry.getKey());
//将找到的高亮字段的内容 替换到原生结果集的map中
if (highlightField != null) {
sourceAsMap.put(entry.getKey(), highlightField.fragments()[0].toString());
}
}
/*
* 自定义结果集的封装 将sourceAsMap中的结果封装成list集合返回
* */
TPoem tPoem = new TPoem();
tPoem.setId((String) sourceAsMap.get("id"));
tPoem.setName((String) sourceAsMap.get("name"));
tPoem.setAuthor((String) sourceAsMap.get("author"));
tPoem.setType((String) sourceAsMap.get("type"));
tPoem.setContent((String) sourceAsMap.get("content"));
tPoem.setHref((String) sourceAsMap.get("href"));
tPoem.setAuthordes((String) sourceAsMap.get("authordes"));
tPoem.setOrigin((String) sourceAsMap.get("origin"));
tPoem.setImagepath((String) sourceAsMap.get("imagepath"));
tPoem.setCategoryid((String) sourceAsMap.get("categoryid"));
poemList.add(tPoem);
}
//将封装好的结果集 传给 一个数据传输的载体对象
return new AggregatedPageImpl(poemList);
}
});
//获取自定义封装的集合
return aggregatedPage.getContent();
}
/* //分页和排序查询所有sortAndPageQuery()的 自定义查询结果集的封装
AggregatedPage<TPoem> aggregatedPage = elasticsearchTemplate.queryForPage(searchQuery, TPoem.class,
new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
SearchHit[] hits = searchResponse.getHits().getHits();
ArrayList<TPoem> poemList = new ArrayList<>();
for (SearchHit hit : hits) {
// 获取原生的结果
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
//自定义结果集的封装
TPoem tPoem = new TPoem();
tPoem.setId((String) sourceAsMap.get("id"));
tPoem.setName((String) sourceAsMap.get("name"));
tPoem.setAuthor((String) sourceAsMap.get("author"));
tPoem.setType((String) sourceAsMap.get("type"));
tPoem.setContent((String)sourceAsMap.get("content"));
tPoem.setHref((String) sourceAsMap.get("href"));
tPoem.setAuthordes((String) sourceAsMap.get("authordes"));
tPoem.setOrigin((String) sourceAsMap.get("origin"));
tPoem.setImagepath((String) sourceAsMap.get("imagepath"));
tPoem.setCategoryid((String) sourceAsMap.get("categoryid"));
poemList.add(tPoem);
}
//将封装好的结果集 传给 一个数据传输的载体对象
return new AggregatedPageImpl(poemList);
}
}
);
return aggregatedPage.getContent();*/
}
测试
@SpringBootTest(classes = Application.class)
@RunWith(SpringRunner.class)
public class testElasticsearchPoem {
@Autowired
private PoemDaoImpl poemDao;
@Test
public void test0() throws Exception{
poemDao.deleteIndex();
}
@Test
public void test1() throws Exception{
List<TPoem> tPoems = poemDao.mathQuery();
for (TPoem tPoem : tPoems) {
System.out.println(tPoem); }
}
@Test
public void test2() throws Exception{
List<TPoem> tPoems = poemDao.boolQuery();
for (TPoem tPoem : tPoems) {
System.out.println(tPoem); }
}
@Test
public void test3() throws Exception{
List<TPoem> tPoems = poemDao.sortAndPageQuery();
for (TPoem tPoem : tPoems) {
System.out.println(tPoem); }
}
@Test
public void test4() throws Exception{
List<TPoem> tPoems = poemDao.highlightQuery();
for (TPoem tPoem : tPoems) {
System.out.println(tPoem); }
}
}
基于反射实现通用查询
public class SearchForElasticsearch {
public static<T> List<T> queryInfo(Class<T> clazz,
ElasticsearchTemplate elasticsearchTemplate,
String indices,
String type,
String searchField,
String searchFieldValue,
String descField) {
HighlightBuilder.Field field = new HighlightBuilder.Field("*")
.preTags("<span style='color:red'>")
.postTags("</span>").requireFieldMatch(false);;
// 构建查询对象
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withIndices(indices).withTypes(type)
.withQuery(QueryBuilders.matchQuery(searchField, searchFieldValue))
.withPageable(PageRequest.of(0, 3))
.withSort(SortBuilders.fieldSort(descField).order(SortOrder.DESC))
.withHighlightFields(field)
.build();
// 执行查询
AggregatedPage<T> aggregatedPage = elasticsearchTemplate.queryForPage(searchQuery, clazz,
new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
// 获取查询结果中的所有文档
SearchHit[] hits = response.getHits().getHits();
List<T> list = new ArrayList<>();
for (SearchHit hit : hits) {
// 获取id
String id = hit.getId();
// 获取原生的结果
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
sourceAsMap.put("id", id);
// 获取高亮字段
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
Set<Map.Entry<String, Object>> entries = sourceAsMap.entrySet();
for (Map.Entry<String, Object> entry : entries) {
// 获取高亮字段
HighlightField highlightField = highlightFields.get(entry.getKey());
if (highlightField != null) {
sourceAsMap.put(entry.getKey(), highlightField.fragments()[0].toString());
}
}
System.out.println("带高亮字段的结果:" + sourceAsMap);
/**
* 封装结果集
*/
try {
// 自定义结果集的封装
// 反射创建类的对象
T t = clazz.newInstance();
// 反射获取所有的类中的字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 设置运行反射操作属性
field.setAccessible(true);
// 获取属性名
String name = field.getName();
// 获取属性类型
Class<?> type = field.getType();
// System.out.println(name+"的类型是:"+type);
// 根据当前这个字段的名字,到map中获取值,而得到的值就是要封装到当前字段的值
Object value = sourceAsMap.get(name);
// 给对象的属性反射赋值的方法 : 参数一 需要指定操作的对象 参数二 是给属性的赋值
if (type.equals(Double.class)) {
String strValue = (String) value;
Double doubleValue = Double.valueOf(strValue);
field.set(t,doubleValue);
} else if (type.equals(Integer.class)) {
String strValue = (String) value;
Integer integerValue = Integer.valueOf(strValue);
field.set(t,integerValue);
} else if (type.equals(Date.class)) {
String strValue = (String) value;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date = dateFormat.parse(strValue);
field.set(t, date);
} else {
field.set(t,value);
}
}
list.add(t);
} catch (Exception e) {
e.printStackTrace();
}
}
return new AggregatedPageImpl(list);
}
});
return aggregatedPage.getContent();
}
}
命名规则示例
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<String>names) |
{"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}} |
NotIn |
findByNameNotIn (Collection<String>names) |
{"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 |
findByAvailable TrueOrderByNameDesc |
{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}} |