【1】添加Elasticsearch-starter

pom文件添加starter如下:

<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。


maven依赖如下:

<!--导入jest依赖-->
<dependency><groupId>io.searchbox</groupId><artifactId>jest</artifactId><version>5.3.3</version>
</dependency>

Spring Data Elasticsearch主要作用如下:

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


② ElasticsearchDataAutoConfiguration注册了ElasticsearchTemplate来操作ES

@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@ConditionalOnMissingBeanpublic ElasticsearchConverter elasticsearchConverter(SimpleElasticsearchMappingContext mappingContext) {return new MappingElasticsearchConverter(mappingContext);}@Bean@ConditionalOnMissingBeanpublic SimpleElasticsearchMappingContext mappingContext() {return new SimpleElasticsearchMappingContext();}}

③ ElasticsearchRepositoriesAutoConfiguration 启用了ElasticsearchRepository

@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中的接口):

@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配置如下:

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

测试类如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootJestTest {@AutowiredJestClient jestClient;@Testpublic void index(){Article article = new Article();article.setId(1);article.setAuthor("Tom");article.setContent("hello world !");article.setTitle("今日消息");//构建一个索引功能,类型为newsIndex index = new Index.Builder(article).index("jest").type("news").build();try {jestClient.execute(index);System.out.println("数据索引成功!");} catch (IOException e) {e.printStackTrace();}}@Testpublic 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();}}
}

测试存储数据结果如下:


测试查询数据结果如下:


【3】 Elasticsearch版本调整

application.properties进行配置:

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

这里节点名取自如下图: 


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


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

官网地址: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版本:

# 拉取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

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

# 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

此时再次启动程序:


【4】ElasticsearchRepository使用

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

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

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

BookRepository 所拥有的方法如下图:


Book源码如下:

// 这里注意注解
@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;}@Overridepublic String toString() {return "Book{" +"id=" + id +", bookName='" + bookName + '\'' +", author='" + author + '\'' +'}';}
}

测试类如下:

    @AutowiredBookRepository bookRepository;@Testpublic void testRepository(){Book book = new Book();book.setAuthor("吴承恩");book.setBookName("西游记");book.setId(1);bookRepository.index(book);System.out.println("BookRepository 存入数据成功!");}

测试结果如下图:


测试获取示例如下:

     @Testpublic void testRepository2(){for (Book book : bookRepository.findByBookNameLike("游")) {System.out.println("获取的book : "+book);} ;Book book = bookRepository.findOne(1);System.out.println("根据id查询 : "+book);}

测试结果如下图:


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



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


支持@Query注解

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

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

【5】ElasticsearchTemplate使用

存入数据源码示例如下:

    @AutowiredElasticsearchTemplate elasticsearchTemplate;@Testpublic 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);}

测试结果如下:


查询数据示例如下:

    @Testpublic 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);}}

测试结果如下:

开源项目: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为测试可通过的一个版本。

<?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>

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

3.Spring Starter配置

  1. 需使用SpringBootApplication启动
  2. 需禁用ElasticsearchAutoConfiguration,ElasticsearchDataAutoConfiguration,否则会有异常
  3. HighLightJestSearchResultMapper Bean留待下面解释,主要为了解决spring data不支持elasticsearch检索highlight问题,此处为该Bean的注册
@SpringBootApplication
@EnableAutoConfiguration(exclude = {ElasticsearchAutoConfiguration.class, ElasticsearchDataAutoConfiguration.class})
public class App {public static void main(String[] args) throws Exception {SpringApplication.run(App.class, args);}@Beanpublic HighLightJestSearchResultMapper highLightJestSearchResultMapper(){return new HighLightJestSearchResultMapper();}}

3.Entity配置

a) 歌曲Entity如下:

通过对Class进行Document注解,实现与ElasticSearch中的Index和Type一一对应。
该类在最终与ES返回结果映射时,仅映射其中_source部分。即如下图部分(highlight另说,后面单独处理了):

@Document(indexName = "songs",type = "sample",shards = 1, replicas = 0, refreshInterval = "-1")
public class Song extends HighLightEntity{@Idprivate 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需要继承该类。

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配置

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) 接口

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) 实现类

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

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@AutowiredSongRepository songRepository;@AutowiredJestElasticsearchTemplate jestElasticsearchTemplate;@AutowiredHighLightJestSearchResultMapper jestSearchResultMapper;@Overridepublic 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的问题

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;}@Overridepublic <T> AggregatedPage<T> mapResults(SearchResult response, Class<T> clazz) {return mapResults(response, clazz, null);}@Overridepublic <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
重点修改部分为:

@Overridepublic <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

@Controller
@RequestMapping(value = "/search")
public class SearchController {@AutowiredSongService 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

<!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中配置如下:

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 热启动配置,便于测试

  1. 在resources/application.properties中配置如下:
spring.thymeleaf.cache=false
  1. 在pom.xml中增加:
        <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代理,以支持本机可以公网访问,便于开发

  1. 购买一台esc
  2. 在ecs上安装ngnix,并配置代理信息server 部分如下:
    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. 最后,查询效果:

===============================================================================================

Elastic Search是一个开源的,分布式,实时搜索和分析引擎。Spring Boot为Elasticsearch及Spring Data Elasticsearch提供的基于它的抽象提供了基本的配置。Spring Boot提供了一个用于聚集依赖的spring-boot-starter-data-elasticsearch 'StarterPOM'。

ElasticSearch作为搜索引擎,我们需要解决2大问题:

1,  如何将被搜索的数据在ES上创建反向索引
2,  Java代码如何与ES交互

其中第一个大问题又分为两个小问题

1.1,如何初始化已有的数据
1.2,如何同步增量数据

第二个大问题也有两种集成方式

2.1 Spring Data 9300端口集成
2.2 Restful API 9200端口集成

本篇先解决第二大问题。

第一种方式,利用RestAPI方式,也叫Jest方式:

示例代码:https://github.com/yejingtao/forblog/tree/master/demo-jest-elasticsearch

Pom.xml:

<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>yejingtao.demo.springcloud</groupId> <artifactId>demo-jest-elasticsearch</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo-jest-elasticsearch</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> </parent> <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-elasticsearch</artifactId> </dependency> <dependency> <groupId>io.searchbox</groupId> <artifactId>jest</artifactId> </dependency> <dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna</artifactId> </dependency> </dependencies>
</project>

Application.yml:

server: port: 7081 spring: elasticsearch: jest: uris: - http://192.168.226.133:9200 read-timeout: 5000

注意这里是9200端口

主程序:最简单的Spring boot启动程序:

@SpringBootApplication
public class ESApplication { public static void main(String[] args) { SpringApplication.run(ESApplication.class); }
} 

定义好ES中的实体类和对ES操作的接口:

public class Entity implements Serializable{ private static final long serialVersionUID = -763638353551774166L; public static final String INDEX_NAME = "index_entity"; public static final String TYPE = "tstype"; private Long id; private String name; public Entity() { super(); } public Entity(Long id, String name) { this.id = id; this.name = name; } 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 interface CityESService { void saveEntity(Entity entity); void saveEntity(List<Entity> entityList); List<Entity> searchEntity(String searchContent);
}

接口实现:

@Service
public class CityESServiceImpl implements CityESService{ private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class); @Autowired private JestClient jestClient; @Override public void saveEntity(Entity entity) { Index index = new Index.Builder(entity).index(Entity.INDEX_NAME).type(Entity.TYPE).build(); try { jestClient.execute(index); LOGGER.info("ES 插入完成"); } catch (IOException e) { e.printStackTrace(); LOGGER.error(e.getMessage()); } } /** * 批量保存内容到ES */ @Override public void saveEntity(List<Entity> entityList) { Bulk.Builder bulk = new Bulk.Builder(); for(Entity entity : entityList) { Index index = new Index.Builder(entity).index(Entity.INDEX_NAME).type(Entity.TYPE).build(); bulk.addAction(index); }     try { jestClient.execute(bulk.build()); LOGGER.info("ES 插入完成"); } catch (IOException e) { e.printStackTrace(); LOGGER.error(e.getMessage()); } } /** * 在ES中搜索内容 */ @Override public List<Entity> searchEntity(String searchContent){ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //searchSourceBuilder.query(QueryBuilders.queryStringQuery(searchContent)); //searchSourceBuilder.field("name"); searchSourceBuilder.query(QueryBuilders.matchQuery("name",searchContent)); Search search = new Search.Builder(searchSourceBuilder.toString()) .addIndex(Entity.INDEX_NAME).addType(Entity.TYPE).build(); try { JestResult result = jestClient.execute(search); return result.getSourceAsObjectList(Entity.class); } catch (IOException e) { LOGGER.error(e.getMessage()); e.printStackTrace(); } return null;     }
}

这里插入数据的方式给了两种,一种是单次API直接插入,一种是利用ES的bulk批量插入。

做一个controller方面我们测试:

启动后在浏览器中请求http://localhost:7081/entityController/search?name=%E4%BA%BA%E6%89%8B%E4%BA%95

得到结果:

这里只返回了9条记录,而理论上ES默认的size是10,应该不是分页的问题,而是只能检索出9条匹配记录,用Kibana连上相同的搜索确认下:

这里用的是standard分词方式,将每个中文都作为了一个term,凡是包含“人”“手”“井”的都被搜索了出来,只是评分不同,如果想支持只能中文索引需要依赖ik插件

OK,RestFul方式对ElasticSearch的检索已经搞定了,更多的扩展可以慢慢研究下QueryBuilders里的源码和批注。

第二种方式,利用Spring Data客户端方式:

事先说明此方式有个弊端,让我掉了坑里好久才爬上来,Spring Data ElasticSearch必须与ElasticSearch版本相匹配,否则在对接时ES端会报版本不匹配错误,例如我ES是5.6.1版本,Spring boot是1.5.6版本,错误如下:

为解决这个问题我查找了一些资料,Spring Data与elasticsearch版本对应关系如下:

spring data elasticsearch

elasticsearch

3.0.0.RC2

5.5.0

3.0.0.M4

5.4.0

2.0.4.RELEASE

2.4.0

2.0.0.RELEASE

2.2.0

1.4.0.M1

1.7.3

1.3.0.RELEASE

1.5.2

1.2.0.RELEASE

1.4.4

1.1.0.RELEASE

1.3.2

1.0.0.RELEASE

1.1.1

而我用的Spring Boot 1.5.6版本对应的Spring Data ElasticSearch是2.1.6版本,不支持5.X的ES,所以报错。到本博文撰写为止,Spring Boot的RELEASE版本最新的是1.5.8,对应的Spring Data ElasticSearch是2.1.8,仍不支持5.X的ES,所以如果一定要使用Java客户端方式集成ES只能放弃Spring Boot直接使用Spring Data和Spring MVC,或者降低ES的版本使之与Spring boot匹配。

示例代码:https://github.com/yejingtao/forblog/tree/master/demo-data-elasticsearch

pom.xml依赖:

<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>yejingtao.demo.springcloud</groupId> <artifactId>demo-data-elasticsearch</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo-data-elasticsearch</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.RELEASE</version> </parent> <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-elasticsearch</artifactId> </dependency> </dependencies>
</project>

不再引用Jest。

application.yml:

server: port: 7081 spring: data: elasticsearch: cluster-nodes: 192.168.226.133:9300 cluster-name: my-es repositories: enabled: true

注意这里是9300端口

Controller、主程序、Service接口同Jest项目不变,不再罗列

实体类稍作变化,指定ES中的index和type:

@Document(indexName="index_entity", type="tstype")

多一个Repository接口,无需实现类,spring data标准用法:

/** * Entity ES操作类 * @author yejingtao * */
public interface EntityRepository extends ElasticsearchRepository<Entity,Long>{ }

Service实现类与Jest的天壤之别了,从语法上可以看出更像是对数据库层的操作:

@Service
public class CityESServiceImpl implements CityESService{ private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class); int PAGE_SIZE = 15; //默认分页大小 int PAGE_NUMBER = 0; //默认当前分页 String SCORE_MODE_SUM = "sum"; //权重分求和模式 Float MIN_SCORE = 10.0F; //由于无相关性的分值默认为1, 设置权重分最小值为10 @Autowired EntityRepository entityRepository; /** * 保存内容到ES */ @Override public Long saveEntity(Entity entity) { Entity entityResult = entityRepository.save(entity); return entityResult.getId(); } /** * 在ES中搜索内容 */ @Override public List<Entity> searchEntity(int pageNumber, int pageSize, String searchContent){ if(pageSize==0) { pageSize = PAGE_SIZE; } if(pageNumber<0) { pageNumber = PAGE_NUMBER; } SearchQuery searchQuery = getEntitySearchQuery(pageNumber,pageSize,searchContent); LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n DSL = \n "  + searchQuery.getQuery().toString()); Page<Entity> cityPage = entityRepository.search(searchQuery); return cityPage.getContent(); } /** * 组装搜索Query对象 * @param pageNumber * @param pageSize * @param searchContent * @return */ private SearchQuery getEntitySearchQuery(int pageNumber, int pageSize, String searchContent) { FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery() .add(QueryBuilders.matchPhraseQuery("name", searchContent), ScoreFunctionBuilders.weightFactorFunction(1000)) //.add(QueryBuilders.matchPhraseQuery("other", searchContent), //ScoreFunctionBuilders.weightFactorFunction(1000)) .scoreMode(SCORE_MODE_SUM).setMinScore(MIN_SCORE); //设置分页,否则只能按照ES默认的分页给 Pageable pageable = new PageRequest(pageNumber, pageSize); return new NativeSearchQueryBuilder().withPageable(pageable).withQuery(functionScoreQueryBuilder).build(); } }

测试方式同Jest。

这两种方式,从设计上来讲属于两种思路,Spring Data的思路就是将ElasticSearch当自家的数据仓库来管理,直接通过Java客户端代码操作ES;Jest的思路是将ElasticSearch当为独立的服务端,自己作为客户端用兼容性最强的RestFul格式来与之交互。
个人比较倾向于Jest方式,第一兼容性好,不需要考虑版本的问题。第二,从ElasticSearch本身的设计上来分析,9200是对外服务端口,9300是内部管理和集群通信端口,请求9200获取搜索服务更符合ES的设计初衷,不会影响集群内部的通信。

转自:https://yq.aliyun.com/articles/538160

https://blog.csdn.net/j080624/article/details/80953393

https://www.jb51.net/article/127390.htm

SpringBoot 整合Jest相关推荐

  1. SpringBoot高级-检索-SpringBoot整合Jest操作ES

    接下来就用SpringBoot来整合ElasticSearch进行测试,pom文件引入了spring-boot-starter-data-elasticsearch,其实加了data都是用spring ...

  2. 重学Elasticsearch第8章 : SpringBoot整合Jest客户端

    文章目录 JestClient介绍 引入依赖 ES的配置 (1).application.yml 配置文件 (2).java 连接配置类 JestClient操作ElasticSearch 客户端初始 ...

  3. SpringBoot整合篇-雷丰阳-专题视频课程

    SpringBoot整合篇-317人已学习 课程介绍         本视频<SpringBoot>属于下部,着重介绍SpringBoot的与各大场景的整合使用,内容包括:缓存(整合Red ...

  4. es springboot 不设置id_原创 | 一篇解决Springboot 整合 Elasticsearch

    ElasticSearch 结合业务的场景,在目前的商品体系需要构建搜索服务,主要是为了提供用户更丰富的检索场景以及高速,实时及性能稳定的搜索服务. ElasticSearch是一个基于Lucene的 ...

  5. springboot整合elasticsearch_Spring Boot学习10_整合Elasticsearch

    一.Elasticsearch概念 •以 员工文档 的形式存储为例:一个文档代表一个员工数据.存储数据到 ElasticSearch 的行为叫做 索引 ,但在索引一个文档之前,需要确定将文档存储在哪里 ...

  6. SpringBoot整合ElasticSearch实现多版本的兼容

    前言 在上一篇学习SpringBoot中,整合了Mybatis.Druid和PageHelper并实现了多数据源的操作.本篇主要是介绍和使用目前最火的搜索引擎ElastiSearch,并和Spring ...

  7. 2019.12.24笔记——SpringBoot整合Elasticsearch及其使用

    目录 Elasticsearch与springboot整合的方式 原生客户端 REST Jest Spring Data Spring Data的配置 Spring Data的使用 插入或修改数据 删 ...

  8. SpringBoot第九篇: springboot整合Redis

    这篇文章主要介绍springboot整合redis,至于没有接触过redis的同学可以看下这篇文章:5分钟带你入门Redis. 引入依赖: 在pom文件中添加redis依赖: <dependen ...

  9. springboot整合shiro使用shiro-spring-boot-web-starter

    此文章仅仅说明在springboot整合shiro时的一些坑,并不是教程 增加依赖 <!-- 集成shiro依赖 --> <dependency><groupId> ...

最新文章

  1. P2119 魔法阵(优化枚举,数学运算优化)难度⭐⭐⭐★
  2. javaweb中为mysql的curd多个值的语句
  3. python装饰器原理-python装饰器的原理和使用
  4. pycharm远程和本地同时_pycharm远程开发之django配置
  5. 如何优雅的链式取值之 MayBe 函子
  6. asp程序员2005年薪酬调查!
  7. 容器技术第一讲:容器入门篇
  8. 点计算机没有本地磁盘,快速解决WinPE系统下没有本地磁盘的方法
  9. 最少拦截系统 贪心
  10. Linux 误删除 /boot分区 的解救办法
  11. Calendar(显示日期)
  12. 洛谷 P2383 狗哥玩木棒
  13. PLECS的热模型无法导入的解决办法
  14. python自定义二元一次函数_Python二元赋值实用技巧解析
  15. Google地图3D城市模型下载最新实践
  16. Tree Booster 的参数
  17. 网络***的基础教程
  18. 小学计算机应用到英语课教案,信息技术与小学英语教学有效融合(转载)
  19. 超微服务器X11DAi-N主板简介及实际应用
  20. Linux命令之计算器bc

热门文章

  1. win7无法启用网络发现
  2. 和你一起从零开始写RISC-V处理器(3)
  3. c盘里面的html文件能删吗,c盘中用户文件里面的东西可以删吗
  4. HTML5 - 文字跑马灯标签(marquee)屏幕滚动文字
  5. 使用Proxifier+burp抓包总结
  6. 极速办公(excel)如何隔行求和
  7. DD镜像和E01镜像的主要区别
  8. fast-request-2.1.3 免费版
  9. CES 2023:高通从移动互联深入布局未来智驾
  10. 【问题解决】kindle for pc闪退