服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|JavaScript|易语言|

服务器之家 - 编程语言 - Java教程 - SpringBoot 整合Jest实例代码讲解

SpringBoot 整合Jest实例代码讲解

2021-05-27 13:00ZhaoYingChao88 Java教程

本文通过实例代码给大家介绍了SpringBoot 整合Jest的相关知识,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下

【1】添加Elasticsearch-starter

pom文件添加starter如下:

?
1
2
3
4
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

SpringBoot默认支持两种技术和Elasticsearch进行交互:Spring Data Elasticsearch和Jest

Jest默认不生效,需要导入io.searchbox.client.JestClient。

SpringBoot 整合Jest实例代码讲解

maven依赖如下:

?
1
2
3
4
5
6
<!--导入jest依赖-->
<dependency>
 <groupId>io.searchbox</groupId>
 <artifactId>jest</artifactId>
 <version>5.3.3</version>
</dependency>

Spring Data Elasticsearch主要作用如下:

① ElasticsearchAutoConfiguration中注册了client,属性有clusterNodes和clusterName。

SpringBoot 整合Jest实例代码讲解

② ElasticsearchDataAutoConfiguration注册了ElasticsearchTemplate来操作ES 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Configuration
@ConditionalOnClass({ Client.class, ElasticsearchTemplate.class })
@AutoConfigureAfter(ElasticsearchAutoConfiguration.class)
public class ElasticsearchDataAutoConfiguration {
 @Bean
 @ConditionalOnMissingBean
 @ConditionalOnBean(Client.class)
 public ElasticsearchTemplate elasticsearchTemplate(Client client,
  ElasticsearchConverter converter) {
 try {
  return new ElasticsearchTemplate(client, converter);
 }
 catch (Exception ex) {
  throw new IllegalStateException(ex);
 }
 }
 @Bean
 @ConditionalOnMissingBean
 public ElasticsearchConverter elasticsearchConverter(
  SimpleElasticsearchMappingContext mappingContext) {
 return new MappingElasticsearchConverter(mappingContext);
 }
 @Bean
 @ConditionalOnMissingBean
 public SimpleElasticsearchMappingContext mappingContext() {
 return new SimpleElasticsearchMappingContext();
 }
}

③ ElasticsearchRepositoriesAutoConfiguration 启用了ElasticsearchRepository

?
1
2
3
4
5
6
7
@Configuration
@ConditionalOnClass({ Client.class, ElasticsearchRepository.class })
@ConditionalOnProperty(prefix = "spring.data.elasticsearch.repositories", name = "enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnMissingBean(ElasticsearchRepositoryFactoryBean.class)
@Import(ElasticsearchRepositoriesRegistrar.class)
public class ElasticsearchRepositoriesAutoConfiguration {
}

ElasticsearchRepository接口源码如下(类似于JPA中的接口):

?
1
2
3
4
5
6
7
8
9
10
@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();
}

【2】JestClient操作测试

application.properties配置如下:

?
1
2
# jest url配置
spring.elasticsearch.jest.uris=http://192.168.2.110:9200

测试类如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootJestTest {
 @Autowired
 JestClient jestClient;
 @Test
 public void index(){
  Article article = new Article();
  article.setId(1);
  article.setAuthor("Tom");
  article.setContent("hello world !");
  article.setTitle("今日消息");
  //构建一个索引功能,类型为news
  Index index = new Index.Builder(article).index("jest").type("news").build();
  try {
   jestClient.execute(index);
   System.out.println("数据索引成功!");
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
 @Test
 public void search(){
  //查询表达式
  String json = "{\n" +
    " \"query\" : {\n" +
    "  \"match\" : {\n" +
    "   \"content\" : \"hello\"\n" +
    "  }\n" +
    " }\n" +
    "}";
  //构建搜索功能
  Search search = new Search.Builder(json).addIndex("jest").addType("news").build();
  try {
   SearchResult result = jestClient.execute(search);
   System.out.println(result.getJsonString());
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}

测试存储数据结果如下:

SpringBoot 整合Jest实例代码讲解

测试查询数据结果如下:

SpringBoot 整合Jest实例代码讲解

【3】 Elasticsearch版本调整

application.properties进行配置:

?
1
2
3
# Spring data elasticsearch配置
spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=192.168.2.110:9300

这里节点名取自如下图:

 
SpringBoot 整合Jest实例代码讲解

启动主程序,可能报错如下(ES版本不合适):

SpringBoot 整合Jest实例代码讲解

查看Spring Data官网,其中spring data elasticsearch与elasticsearch适配表如下:

SpringBoot 整合Jest实例代码讲解

官网地址:https://github.com/spring-projects/spring-data-elasticsearch

我们在上篇博文中安装的ES版本为5.6.10,项目中SpringBoot版本为1.5.12,spring-boot-starter-data-elasticsearch为2.1.11,elasticsearch版本为2.4.6。

两种解决办法:① 升级SpringBoot版本;② 安装2.4.6版本的elasticsearch。

 

这里修改暴露的端口,重新使用docker安装2.4.6版本:

?
1
2
3
4
# 拉取2.4.6 镜像
docker pull registry.docker-cn.com/library/elasticsearch:2.4.6
# 启动容器
docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d -p 9201:9200 -p 9301:9300 --name ES02 bc337c8e4f

SpringBoot 整合Jest实例代码讲解

application.properties配置文件同步修改:

?
1
2
3
4
5
# jest url配置
spring.elasticsearch.jest.uris=http://192.168.2.110:9201
# Spring data elasticsearch配置
spring.data.elasticsearch.cluster-name=elasticsearch
spring.data.elasticsearch.cluster-nodes=192.168.2.110:9301

此时再次启动程序:

SpringBoot 整合Jest实例代码讲解

【4】ElasticsearchRepository使用

类似于JPA,编写自定义Repository接口,继承自ElasticsearchRepository:

?
1
2
3
public interface BookRepository extends ElasticsearchRepository<Book,Integer> {
 public List<Book> findByBookNameLike(String bookName);
}

这里第一个参数为对象类型,第二个参数为对象的主键类型。

BookRepository 所拥有的方法如下图:

SpringBoot 整合Jest实例代码讲解

Book源码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 这里注意注解
@Document(indexName = "elastic",type = "book")
public class Book {
 private Integer id;
 private String bookName;
 private String author;
 public Integer getId() {
  return id;
 }
 public void setId(Integer id) {
  this.id = id;
 }
 public String getBookName() {
  return bookName;
 }
 public void setBookName(String bookName) {
  this.bookName = bookName;
 }
 public String getAuthor() {
  return author;
 }
 public void setAuthor(String author) {
  this.author = author;
 }
 @Override
 public String toString() {
  return "Book{" +
    "id=" + id +
    ", bookName='" + bookName + '\'' +
    ", author='" + author + '\'' +
    '}';
 }
}

测试类如下:

?
1
2
3
4
5
6
7
8
9
10
11
@Autowired
 BookRepository bookRepository;
 @Test
 public void testRepository(){
  Book book = new Book();
  book.setAuthor("吴承恩");
  book.setBookName("西游记");
  book.setId(1);
  bookRepository.index(book);
  System.out.println("BookRepository 存入数据成功!");
 }

测试结果如下图:

SpringBoot 整合Jest实例代码讲解

测试获取示例如下:

?
1
2
3
4
5
6
7
8
@Test
public void testRepository2(){
 for (Book book : bookRepository.findByBookNameLike("游")) {
  System.out.println("获取的book : "+book);
 } ;
 Book book = bookRepository.findOne(1);
 System.out.println("根据id查询 : "+book);
}

测试结果如下图:

SpringBoot 整合Jest实例代码讲解

Elasticsearch支持方法关键字如下图所示

SpringBoot 整合Jest实例代码讲解
SpringBoot 整合Jest实例代码讲解
SpringBoot 整合Jest实例代码讲解

即,在BookRepository中使用上述关键字构造方法,即可使用,Elastic自行实现其功能!

支持@Query注解

如下所示,直接在方法上使用注解:

?
1
2
3
4
5
public interface BookRepository extends ElasticsearchRepository<Book, String> {
 @Query("{"bool" : {"must" : {"field" : {"name" : "?0"}}}}")
 Page<Book> findByName(String name,Pageable pageable);
 
}

【5】ElasticsearchTemplate使用

存入数据源码示例如下:

?
1
2
3
4
5
6
7
8
9
10
11
@Autowired
 ElasticsearchTemplate elasticsearchTemplate;
 @Test
 public void testTemplate01(){
  Book book = new Book();
  book.setAuthor("曹雪芹");
  book.setBookName("红楼梦");
  book.setId(2);
  IndexQuery indexQuery = new IndexQueryBuilder().withId(String.valueOf(book.getId())).withObject(book).build();
  elasticsearchTemplate.index(indexQuery);
 }

测试结果如下:

SpringBoot 整合Jest实例代码讲解

查询数据示例如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
@Test
 public void testTemplate02(){
  QueryStringQueryBuilder stringQueryBuilder = new QueryStringQueryBuilder("楼");
  stringQueryBuilder.field("bookName");
  SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(stringQueryBuilder).build();
  Page<Book> books = elasticsearchTemplate.queryForPage(searchQuery,Book.class);
  Iterator<Book> iterator = books.iterator();
  while(iterator.hasNext()){
   Book book = iterator.next();
   System.out.println("该次获取的book:"+book);
  }
 }

测试结果如下:

SpringBoot 整合Jest实例代码讲解

 开源项目: https://github.com/spring-projects/spring-data-elasticsearch

 https://github.com/searchbox-io/Jest/tree/master/jest

1.说明

本文主要讲解如何使用Spring Boot快速搭建Web框架,结合Spring Data 和 Jest 快速实现对阿里云ElasticSearch的全文检索功能。

主要使用组件:

Spring Boot Starter:可以帮助我们快速的搭建spring mvc 环境
Jest:一种rest访问es的客户端
elasticsearch:全文检索
spring data elasticsearch:结合spring data
thymeleaf:web前端模版框架
jquery:js框架
bootstrap:前端样式框架

2.项目Maven配置

以下为项目Maven配置,尤其需要注意各个组件的版本,以及注释部分。

各个组件的某些版本组合下回出现各种异常,以下maven为测试可通过的一个版本。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>org.lewis</groupId>
 <artifactId>esweb</artifactId>
 <version>0.1</version>
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <!--必须用2.0+,否则会有一个类
  Caused by: java.lang.NoSuchMethodError: org.elasticsearch.common.settings.Settings.settingsBuilder()Lorg/elasticsearch/common/settings/Settings$Builder;
  -->
  <version>2.0.0.M7</version>
 </parent>
 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
 </properties>
 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <!--
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  -->
  <!--不可使用version 5.3.3,会有一个类的方法找不到-->
  <dependency>
   <groupId>io.searchbox</groupId>
   <artifactId>jest</artifactId>
   <version>5.3.2</version>
  </dependency>
  <!--必须用5.0+,否则会有一个类找不到org/elasticsearch/node/NodeValidationException-->
  <dependency>
   <groupId>org.elasticsearch</groupId>
   <artifactId>elasticsearch</artifactId>
   <version>5.3.3</version>
  </dependency>
  <dependency>
   <groupId>org.springframework.data</groupId>
   <artifactId>spring-data-elasticsearch</artifactId>
   <version>3.0.0.RELEASE</version>
  </dependency>
  <dependency>
   <groupId>com.github.vanroy</groupId>
   <artifactId>spring-boot-starter-data-jest</artifactId>
   <version>3.0.0.RELEASE</version>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-devtools</artifactId>
   <optional>true</optional>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
  <!--
  不需要引用
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  </dependency>
  -->
  <!--spring boot elasticsearch 缺少的jar,需要单独引入-->
  <dependency>
   <groupId>net.java.dev.jna</groupId>
   <artifactId>jna</artifactId>
   <version>4.5.1</version>
  </dependency>
  <!--webjars 前端框架,整体管理前端js框架-->
  <dependency>
   <groupId>org.webjars</groupId>
   <artifactId>jquery</artifactId>
   <version>3.3.0</version>
  </dependency>
  <dependency>
   <groupId>org.webjars</groupId>
   <artifactId>bootstrap</artifactId>
   <version>4.0.0</version>
  </dependency>
  <!--When using Spring Boot version 1.3 or higher, it will automatically detect the webjars-locator library on the classpath and use it to automatically resolve the version of any WebJar assets for you. In order to enable this feature, you will need to add the webjars-locator library as a dependency of your application in the pom.xml file-->
  <dependency>
   <groupId>org.webjars</groupId>
   <artifactId>webjars-locator</artifactId>
   <version>0.30</version>
  </dependency>
 </dependencies>
 <build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
     <fork>true</fork>
    </configuration>
   </plugin>
  </plugins>
 </build>
</project>

创建完成后,项目目录结构如下:


SpringBoot 整合Jest实例代码讲解

3.Spring Starter配置需使用SpringBootApplication启动需禁用ElasticsearchAutoConfiguration,ElasticsearchDataAutoConfiguration,否则会有异常HighLightJestSearchResultMapper Bean留待下面解释,主要为了解决spring data不支持elasticsearch检索highlight问题,此处为该Bean的注册

?
1
2
3
4
5
6
7
8
9
10
11
@SpringBootApplication
@EnableAutoConfiguration(exclude = {ElasticsearchAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class})
public class App {
 public static void main(String[] args) throws Exception {
  SpringApplication.run(App.class, args);
 }
 @Bean
 public HighLightJestSearchResultMapper highLightJestSearchResultMapper(){
  return new HighLightJestSearchResultMapper();
 }
}

3.Entity配置

a) 歌曲Entity如下:

通过对Class进行Document注解,实现与ElasticSearch中的Index和Type一一对应。

该类在最终与ES返回结果映射时,仅映射其中_source部分。即如下图部分(highlight另说,后面单独处理了):

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Document(indexName = "songs",type = "sample",shards = 1, replicas = 0, refreshInterval = "-1")
public class Song extends HighLightEntity{
 @Id
 private Long id;
 private String name;
 private String href;
 private String lyric;
 private String singer;
 private String album;
 public Song(Long id, String name, String href, String lyric, String singer, String album, Map<String, List<String>> highlight) {
  //省略
 }
 public Song() {
 }
 //getter setter 省略...
}

b) 为了解决Spring data elasticsearch问题,此处增加一个抽象类:HighLightEntity,其他Entity需要继承该类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
package org.leiws.esweb.entity;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
public abstract class HighLightEntity implements Serializable{
 private Map<String, List<String>> highlight;
 public Map<String, List<String>> getHighlight() {
  return highlight;
 }
 public void setHighlight(Map<String, List<String>> highlight) {
  this.highlight = highlight;
 }
}

4.Repository配置

?
1
2
3
4
5
package org.leiws.esweb.repository;
import org.leiws.esweb.entity.Song;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface SongRepository extends ElasticsearchRepository<Song,Long> {
}

5.Service配置

a) 接口

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.leiws.esweb.service;
import org.leiws.esweb.entity.Song;
import org.springframework.data.domain.Page;
import java.util.List;
/**
 * The interface Song service.
 */
public interface SongService {
 /**
  * Search song list.
  *
  * @param pNum  the p num
  * @param pSize the p size
  * @param keywords the keywords
  * @return the list
  */
 public Page<Song> searchSong(Integer pNum, Integer pSize, String keywords);
}

b) 实现类

该类实现了具体如何分页,如何查询等

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package org.leiws.esweb.service.impl;
import com.github.vanroy.springdata.jest.JestElasticsearchTemplate;
import org.apache.log4j.Logger;
import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
import org.elasticsearch.index.query.MatchPhraseQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.leiws.esweb.entity.Song;
import org.leiws.esweb.repository.HighLightJestSearchResultMapper;
import org.leiws.esweb.repository.SongRepository;
import org.leiws.esweb.service.SongService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;
import static org.elasticsearch.index.query.QueryBuilders.functionScoreQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchPhraseQuery;
import java.util.List;
@Service
public class SongServiceImpl implements SongService{
 private static final Logger LOGGER = Logger.getLogger(SongServiceImpl.class);
 /* 分页参数 */
 private final static Integer PAGE_SIZE = 12;   // 每页数量
 private final static Integer DEFAULT_PAGE_NUMBER = 0; // 默认当前页码
 /* 搜索模式 */
 private final static String SCORE_MODE_SUM = "sum"; // 权重分求和模式
 private final static Float MIN_SCORE = 10.0F;  // 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
 @Autowired
 SongRepository songRepository;
 @Autowired
 JestElasticsearchTemplate jestElasticsearchTemplate;
 @Autowired
 HighLightJestSearchResultMapper jestSearchResultMapper;
 @Override
 public Page<Song> searchSong(Integer pNum, Integer pSize, String keywords) {
  // 校验分页参数
  if (pSize == null || pSize <= 0) {
   pSize = PAGE_SIZE;
  }
  if (pNum == null || pNum < DEFAULT_PAGE_NUMBER) {
   pNum = DEFAULT_PAGE_NUMBER;
  }
  LOGGER.info("\n searchCity: searchContent [" + keywords + "] \n ");
  // 构建搜索查询
  SearchQuery searchQuery = getCitySearchQuery(pNum,pSize,keywords);
  LOGGER.info("\n searchCity: searchContent [" + keywords + "] \n DSL = \n " + searchQuery.getQuery().toString());
//  Page<Song> cityPage = songRepository.search(searchQuery);
  Page<Song> cityPage = jestElasticsearchTemplate.queryForPage(searchQuery,Song.class,jestSearchResultMapper);
  return cityPage;
 }
 /**
  * 根据搜索词构造搜索查询语句
  *
  * 代码流程:
  *  - 权重分查询
  *  - 短语匹配
  *  - 设置权重分最小值
  *  - 设置分页参数
  *
  * @param pNum 当前页码
  * @param pSize 每页大小
  * @param searchContent 搜索内容
  * @return
  */
 private SearchQuery getCitySearchQuery(Integer pNum, Integer pSize,String searchContent) {
  /* elasticsearch 2.4.6 版本写法
  FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery()
    .add(QueryBuilders.boolQuery().should(QueryBuilders.matchQuery("lyric", searchContent)),
      ScoreFunctionBuilders.weightFactorFunction(1000))
    .scoreMode(SCORE_MODE_SUM).setMinScore(MIN_SCORE);
  */
  FunctionScoreQueryBuilder.FilterFunctionBuilder[] functions = {
    new FunctionScoreQueryBuilder.FilterFunctionBuilder(
      matchPhraseQuery("lyric", searchContent),
      ScoreFunctionBuilders.weightFactorFunction(1000))
  };
  FunctionScoreQueryBuilder functionScoreQueryBuilder =
    functionScoreQuery(functions).scoreMode(FiltersFunctionScoreQuery.ScoreMode.SUM).setMinScore(MIN_SCORE);
  // 分页参数
//  Pageable pageable = new PageRequest(pNum, pSize);
  Pageable pageable = PageRequest.of(pNum, pSize);
  //高亮提示
  HighlightBuilder.Field highlightField = new HighlightBuilder.Field("lyric")
    .preTags(new String[]{"<font color='red'>", "<b>", "<em>"})
    .postTags(new String[]{"</font>", "</b>", "</em>"})
    .fragmentSize(15)
    .numOfFragments(5)
    //highlightQuery必须单独设置,否则在使用FunctionScoreQuery时,highlight配置不生效,返回结果无highlight元素
    //官方解释:Highlight matches for a query other than the search query. This is especially useful if you use a rescore query because those are not taken into account by highlighting by default.
    .highlightQuery(matchPhraseQuery("lyric", searchContent));
  return new NativeSearchQueryBuilder()
    .withPageable(pageable)
 //   .withSourceFilter(new FetchSourceFilter(new String[]{"name","singer","lyric"},new String[]{}))
    .withHighlightFields(highlightField)
    .withQuery(functionScoreQueryBuilder).build();
 }
}

c) 解决Spring Data ElasticSearch不支持Highlight的问题

通过自定义实现一个如下的JestSearchResultMapper,解决无法Highlight的问题

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package org.leiws.esweb.repository;
//import 省略
public class HighLightJestSearchResultMapper extends DefaultJestResultsMapper {
 private EntityMapper entityMapper;
 private MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
 public HighLightJestSearchResultMapper() {
  this.entityMapper = new DefaultEntityMapper();
  this.mappingContext = new SimpleElasticsearchMappingContext();
 }
 public HighLightJestSearchResultMapper(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext, EntityMapper entityMapper) {
  this.entityMapper = entityMapper;
  this.mappingContext = mappingContext;
 }
 public EntityMapper getEntityMapper() {
  return entityMapper;
 }
 public void setEntityMapper(EntityMapper entityMapper) {
  this.entityMapper = entityMapper;
 }
 @Override
 public <T> AggregatedPage<T> mapResults(SearchResult response, Class<T> clazz) {
  return mapResults(response, clazz, null);
 }
 @Override
 public <T> AggregatedPage<T> mapResults(SearchResult response, Class<T> clazz, List<AbstractAggregationBuilder> aggregations) {
  LinkedList<T> results = new LinkedList<>();
  for (SearchResult.Hit<JsonObject, Void> hit : response.getHits(JsonObject.class)) {
   if (hit != null) {
    T result = mapSource(hit.source, clazz);
    HighLightEntity highLightEntity = (HighLightEntity) result;
    highLightEntity.setHighlight(hit.highlight);
    results.add((T) highLightEntity);
   }
  }
  String scrollId = null;
  if (response instanceof ExtendedSearchResult) {
   scrollId = ((ExtendedSearchResult) response).getScrollId();
  }
  return new AggregatedPageImpl<>(results, response.getTotal(), response.getAggregations(), scrollId);
 }
 private <T> T mapSource(JsonObject source, Class<T> clazz) {
  String sourceString = source.toString();
  T result = null;
  if (!StringUtils.isEmpty(sourceString)) {
   result = mapEntity(sourceString, clazz);
   setPersistentEntityId(result, source.get(JestResult.ES_METADATA_ID).getAsString(), clazz);
  } else {
   //TODO(Fields results) : Map Fields results
   //result = mapEntity(hit.getFields().values(), clazz);
  }
  return result;
 }
 private <T> T mapEntity(String source, Class<T> clazz) {
  if (isBlank(source)) {
   return null;
  }
  try {
   return entityMapper.mapToObject(source, clazz);
  } catch (IOException e) {
   throw new ElasticsearchException("failed to map source [ " + source + "] to class " + clazz.getSimpleName(), e);
  }
 }
 private <T> void setPersistentEntityId(Object entity, String id, Class<T> clazz) {
  ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getRequiredPersistentEntity(clazz);
  ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
  // Only deal with text because ES generated Ids are strings !
  if (idProperty != null) {
   if (idProperty.getType().isAssignableFrom(String.class)) {
    persistentEntity.getPropertyAccessor(entity).setProperty(idProperty, id);
   }
  }
 }
}

上面类的大部分代码来源于:DefaultJestResultsMapper

重点修改部分为:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
 public <T> AggregatedPage<T> mapResults(SearchResult response, Class<T> clazz, List<AbstractAggregationBuilder> aggregations) {
  LinkedList<T> results = new LinkedList<>();
  for (SearchResult.Hit<JsonObject, Void> hit : response.getHits(JsonObject.class)) {
   if (hit != null) {
    T result = mapSource(hit.source, clazz);
    HighLightEntity highLightEntity = (HighLightEntity) result;
    highLightEntity.setHighlight(hit.highlight);
    results.add((T) highLightEntity);
   }
  }
  String scrollId = null;
  if (response instanceof ExtendedSearchResult) {
   scrollId = ((ExtendedSearchResult) response).getScrollId();
  }
  return new AggregatedPageImpl<>(results, response.getTotal(), response.getAggregations(), scrollId);
 }

6.Controller

相对简单,如普通的Spring Controller

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Controller
@RequestMapping(value = "/search")
public class SearchController {
 @Autowired
 SongService songService;
 /**
  * Song list string.
  *
  * @param map the map
  * @return the string
  */
 @RequestMapping(method = RequestMethod.GET)
 public String songList(@RequestParam(value = "pNum") Integer pNum,
       @RequestParam(value = "pSize", required = false) Integer pSize,
       @RequestParam(value = "keywords") String keywords,ModelMap map){
  map.addAttribute("pageSong",songService.searchSong(pNum,pSize,keywords));
  return "songList";
 }
}

7.前端页面thymeleaf模版

存放目录为:resources/templates/songList.html

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
 <meta charset="UTF-8"/>
 <title>Title</title>
 <link rel='stylesheet' href='/webjars/bootstrap/css/bootstrap.min.css'>
 <script src="/webjars/jquery/jquery.min.js"></script>
 <script src="/webjars/bootstrap/js/bootstrap.min.js"></script>
</head>
<body>
<form action="/search" class="px-5 py-3" >
 <div class="input-group">
  <input name="keywords" type="text" class="form-control" placeholder="歌词搜索,请输入歌词内容" aria-label="歌词搜索,请输入歌词内容" aria-describedby="basic-addon2">
  <div class="input-group-append">
   <button class="btn btn-outline-secondary" type="button">搜索</button>
  </div>
  <input type="hidden" name="pNum" value="0"/>
 </div>
</form>
<div class="alert alert-light" role="alert">
 为您找到0个结果:
</div>
<ul class="list-group">
 <li th:each="song : ${pageSong.content}" class="list-group-item">
  <div class="row">
   <a th:href="${song.href}">
   <h4 scope="row" th:text="${song.name}" ></h4>
   </a>
   &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
   <h6 scope="row" th:text="${song.singer}" class="align-bottom" ></h6>
  </div>
  <!--
   <td><a th:href="@{/users/update/{userId}(userId=${user.id})}" th:text="${user.name}"></a></td>
  -->
  <div class="row">
   <span th:each="highlight : ${song.highlight}">
    <span th:each="word : ${highlight.value}">
     <span th:utext="${word}"></span>...
    </span>
   </span>
  </div>
 </li>
</ul>
<nav aria-label="..." class="">
 <ul class="pagination pagination-sm justify-content-center py-5">
  <li class="page-item ">
   <a class="page-link" href="#">
    <span aria-hidden="true">&laquo;</span>
    <span class="sr-only">Previous</span>
   </a>
  </li>
  <li class="page-item"><a class="page-link" href="#">1</a></li>
  <li class="page-item"><a class="page-link" href="#">2</a></li>
  <li class="page-item"><a class="page-link" href="#">3</a></li>
  <li class="page-item">
   <a class="page-link" href="#">
   <span aria-hidden="true">&raquo;</span>
   <span class="sr-only">Next</span>
   </a>
  </li>
 </ul>
</nav>
</body>
</html>

8.阿里云ElasticSearch连接配置

在resources/application.properties中配置如下:

?
1
2
3
4
5
6
spring.data.jest.uri=http://1xx.xxx.xxx.xxx:8080
spring.data.jest.username=username
spring.data.jest.password=password
spring.data.jest.maxTotalConnection=50
spring.data.jest.defaultMaxTotalConnectionPerRoute=50
spring.data.jest.readTimeout=5000

9.其他

a) thymeleaf 热启动配置,便于测试在resources/application.properties中配置如下:

?
1
spring.thymeleaf.cache=false

在pom.xml中增加:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-devtools</artifactId>
   <optional>true</optional>
  </dependency>
<build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
     <fork>true</fork>
    </configuration>
   </plugin>
  </plugins>
 </build>

3.每次还是需要重新compile后,修改的thymeleaf模版代码才会自动生效,因为spring boot启动是以target目录为准的

b) 阿里云elasticsearch在esc上配置ngnix代理,以支持本机可以公网访问,便于开发购买一台esc在ecs上安装ngnix,并配置代理信息server 部分如下:

?
1
2
3
4
5
6
7
8
9
10
11
server {
  listen  8080;
  #listen  [::]:80 default_server;
  server_name {本机内网ip} {本机外网ip};
  #root   /usr/share/nginx/html;
  # Load configuration files for the default server block.
  #include /etc/nginx/default.d/*.conf;
  location / {
      proxy_pass http://{elasticsearch 内网 ip}:9200;
  }
 }

10. 最后,查询效果:

SpringBoot 整合Jest实例代码讲解

总结

以上所述是小编给大家介绍的SpringBoot 整合Jest实例代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!

原文链接:https://blog.csdn.net/ZYC88888/article/details/81873038

延伸 · 阅读

精彩推荐