目录
一、使用版本介绍
二、搭建项目和ES环境
1、Elasticsearch客户端搭建
2、搭建SpringBoot服务及相关依赖
3、Elasticsearch的分词搜索实战
4、搜索方法源码分析
5、分词搜索高亮实现
话不多说,直接开干。
springboot :1.5.2.RELEASE
spring-boot-starter-data-elasticsearch :1.5.2.RELEASE
Elasticsearch :2.3.5
JDK :1.7
以上解决参考下面的对应关系:
Spring Boot Version (x) | Spring Data Elasticsearch Version (y) | Elasticsearch Version (z) |
---|---|---|
x <= 1.3.5 | y <= 1.3.4 | z <= 1.7.2* |
x >= 1.4.x | 2.0.0 <=y < 5.0.0** | 2.0.0 <= z < 5.0.0** |
ES JDK
0.90 1.6
----------------
1.3 1.7
... 1.7
2.4 1.7
----------------
5.0 1.8
... 1.8
---------------------
在 Elasticsearch 官网 https://www.elastic.co/downloads/past-releases 下载对应版本的客户端。此处下载windows版本。
解压后目录结构如下:
进入bin目录, 运行启动 elasticsearch.bat
启动完成后,在浏览器输入 http://localhost:9200/
接下来安装 ES 的WEB端 展示
通过 cmd 的 dos 命令 进入ES安装的bin目录,运行
plugin install mobz/elasticsearch-head
(注:低版本可使用bin目录的 plugin脚本命令,高版本可能不同,如 6.X版本以上是 elasticsearch-plugin)
运行期间可能会提示错误,但也能正常访问,这里就不管跳过了。访问 http://localhost:9200/_plugin/head/
此处引用之前一篇 SpringBoot 整合 Mybatis 的基础上改造
引入相关依赖
org.springframework.boot
spring-boot-starter-data-elasticsearch
application.properties相关内容
#elasticsearch
#开启 Elasticsearch 仓库(默认值:true)
spring.data.elasticsearch.local=true
#仓库中存储数据
spring.data.elasticsearch.repositories.enabled=true
#节点名字,默认elasticsearch
spring.data.elasticsearch.cluster-name=elasticsearch-cluster
#节点地址,多个节点用逗号隔开
#默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
#存储索引的位置
#spring.data.elasticsearch.properties.path.home=/data/project/target/elastic
#elasticsearch日志存储目录
#spring.data.elasticsearch.properties.path.logs=/data/project/target/elastic
#elasticsearch数据存储目录
#spring.data.elasticsearch.properties.path.data=/data/project/target/elastic
#连接超时的时间
spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s
但要注意的是,如何把服务和客户端关联起来
在配置中我们配置了 节点名称 elasticsearch-cluster 和 服务IP 127.0.0.1
需要在 ES的安装目录下的 config 进行相应的配置 ,进入目录 E:\elasticSearch\elasticsearch-2.3.5\config
在 elasticsearch.yml 中搜索修改两个配置
cluster.name: elasticsearch-cluster
network.host: 127.0.0.1
这里是本地搭建,IP和端口就不多说,节点名称如果不正确匹配,项目可以启动成功,但运行链接时会报错,ES的web端界面也会找不到服务。
org.elasticsearch.client.transport.NoNodeAvailableException: None of the configured nodes are available: [{#transport#-1}
其实这样就算springboot和elasticsearch整合完成了。
接下来是如何去使用所整合的服务,大招开启:
实体类:
@Document(indexName = "adminanswer" , type = "answer")
public class Answer {
@Id
private String id;
@Field(type = FieldType.String)
private String title;
private Date createTime;
@Field(type = FieldType.String)
private String content;
public Answer(){
}
public Answer(String id, String title, String content, Date createTime) {
super();
this.id = id;
this.title = title;
this.createTime = createTime;
this.content = content;
}
//以下的 get - set 就省略
}
indexName; //索引库的名称,个人建议以项目的名称命名
type //default ""; //类型,个人建议以实体的名称命名
更多参数可参考:spring data elasticsearch的 @Documnet 和 @Field 注解
重要!重要!重要!以下是 MyBatis和ES的冲突区别,当时被这个整懵了
先来个引用ES服务的 ElasticsearchRepository 接口
public interface AnswerElasticsearchMapper extends ElasticsearchRepository{
}
这样我们就可以使用ES的服务,但是,链接的是ES的服务,数据库我们用的是 MyBatis
所以还要新建一个 mapper接口
@Mapper
public interface AnswerMapper {
int insert(Answer record);
int insertSelective(Answer record);
//使用 mybatis - generator 工具生成,多余方法就省略
}
因为ElasticsearchRepository 和 Mybatis 使用 @Mapper 接口在使用的时候有冲突,所以是没法放在同一个类里面的,要分开写
将数据库数据和ES库同步,网上推荐的方法是使用 logstash-input-jdbc ,这里就不采用了,需要的自行查阅,我们使用简单的方式来实现。
定义实现类接口
public interface AnswerService {
/** * 添加问答信息 * @param adminUser */
public void addAnswer(Answer answer);
/** * 根据标题查找问答信息 * @param title* @return */
public List findAnswerByTitle(String title);
/** * 更新日期* * @param date * @return */
public long updateAllAnswerForTime();
}
实现类:
@Service
public class AnswerServiceImpl implements AnswerService {
@Autowired
private AnswerMapper answerMapper;
@Autowired
private AnswerElasticsearchMapper answerElasticsearchMapper;
@Override
public void addAnswer(Answer answer) {
//注:这里没做ES和mysql数据同步操作,所以调用ES的save方法,只是在ES库中添加
//为了完成两边数据同步,所以同时引用插入,实际开发不推荐这样写
answerMapper.insertSelective(answer);
answerElasticsearchMapper.save(answer);
}
//对title和content进行分词查询
@Override
public List findAnswerByTitle(String title) {
// 构建查询内容
QueryStringQueryBuilder queryBuilder = new QueryStringQueryBuilder(title);
// 查询的字段
queryBuilder.field("title").field("content");
Iterable searchResult = answerElasticsearchMapper.search(queryBuilder);
Iterator iterator = searchResult.iterator();
List list = new ArrayList();
while (iterator.hasNext()) {
list.add(iterator.next());
}
return list;
}
@Override
public long updateAllAnswerForTime() {
List answerList = answerMapper.findAnswerAll();
for (Answer answer : answerList) {
answer.setCreateTime(new Date());
answerElasticsearchMapper.save(answer);
}
return 1;
}
}
写下控制器
@RestController
@RequestMapping(value = "/answer")
public class AnswerController extends BaseController {
@Autowired
private AnswerServiceImpl answerServiceImpl;
/**添加问答信息 */
@RequestMapping(value = "/addAnswer", method = RequestMethod.POST)
public Message addAnswer(String title, String content) {
Answer answer1 = new Answer(UUID.randomUUID().toString(), "测试标题一", "减肥速度快了福建省快递费到付件水电费", new Date());
answerServiceImpl.addAnswer(answer1);
return new Message(SystemCodeAndMsg.SUCCESS);
}
/**查找问答信息*/
@RequestMapping(value = "/findAnswerByTitle", method = RequestMethod.GET)
public Message findAnswerByTitle(String title) {
List answerList = answerServiceImpl.findAnswerByTitle(title);
return new Message(SystemCodeAndMsg.SUCCESS,answerList);
}
/** 修改问答时间*/
@RequestMapping(value = "/updateAnswerTime", method = RequestMethod.POST)
public Message updateAnswerTime() {
answerServiceImpl.updateAllAnswerForTime();
return new Message(SystemCodeAndMsg.SUCCESS);
}
}
启动项目,用postman 访问 http://172.16.60.187:8081/answer/findAnswerByTitle?title=公二
你没看错哦,这样完成了对title和content分词查询,简单吧!!!
但我们要的是分词高亮显示,上面的需求又满足不了我的需求,ElasticsearchRepository提供的方法都是封装好的,没看到可以调用的,但网上都有别的实现方式,那我们就看下 search() 方法的源码实现。(注:不想看源码分析过程的,可以直接往后面跳,直接看方法实现)
AbstractElasticsearchRepository 抽象实现 ElasticsearchRepository
接着看 ElasticsearchTemplate 实现类
可以实现类中看到如果有高亮字段,则添加高亮字段(但只是添加,没有渲染任何额外属性)
我们再看下输入的 mapper.mapResults 实现
mapResults的实现
既然知道了在哪里可以更改实现我们想要的效果,那就开始动手↓↓↓↓↓↓↓↓↓↓↓↓↓↓
因为ElasticsearchRepository提供的实现接口是封装好的,那我们就把接口的实现单独抽出来
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
重写下之前的 findAnswerByTitle 实现接口,主要是重写 SearchResultMapper的mapResults方法,修改如下
@Override
public List findAnswerByTitle(String title) {
// 定义高亮字段
Field titleField = new HighlightBuilder.Field("title").preTags("").postTags("");
Field contentField = new HighlightBuilder.Field("content").preTags("").postTags("");
// 构建查询内容
QueryStringQueryBuilder queryBuilder = new QueryStringQueryBuilder(title);
// 查询匹配的字段
queryBuilder.field("title").field("content");
SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(queryBuilder)
.withHighlightFields(titleField, contentField).build();
long count = elasticsearchTemplate.count(searchQuery, Answer.class);
System.out.println("系统查询个数:--》" + count);
if (count == 0) {
return new ArrayList<>();
}
//需要的话可以实现分页效果,注意,页面是从 0 开始
searchQuery.setPageable(new PageRequest(0, (int) count));
AggregatedPage queryForPage = elasticsearchTemplate.queryForPage(searchQuery, Answer.class,
new SearchResultMapper() {
@Override
public AggregatedPage mapResults(SearchResponse response, Class clazz,
Pageable pageable) {
List list = new ArrayList();
for (SearchHit searchHit : response.getHits()) {
if (response.getHits().getHits().length <= 0) {
return null;
}
Answer answer = JSONObject.parseObject(searchHit.getSourceAsString(), Answer.class);
Map highlightFields = searchHit.getHighlightFields();
//匹配到的title字段里面的信息
HighlightField titleHighlight = highlightFields.get("title");
if (titleHighlight != null) {
Text[] fragments = titleHighlight.fragments();
String fragmentString = fragments[0].string();
answer.setTitle(fragmentString);
}
//匹配到的content字段里面的信息
HighlightField contentHighlight = highlightFields.get("content");
if (contentHighlight != null) {
Text[] fragments = contentHighlight.fragments();
String fragmentString = fragments[0].string();
answer.setContent(fragmentString);
}
list.add(answer);
}
if (list.size() > 0) {
return new AggregatedPageImpl((List) list);
}
return null;
}
});
List list = queryForPage.getContent();
return list;
}
别看内容很长,已经做好分隔,一段一段的看,你就知道其实没什么内容,基本都是从源码刚刚讲解的位置搬出来的。
修改好后,我们再来试试效果。访问: http://172.16.60.187:8081/answer/findAnswerByTitle?title=公二
分词高亮显示,完成!!!
网上一直查阅资料,都各有依据,上面源码部分里面也有大多内容没理清楚,也是个人四处搜寻碰壁查找理解的,欢迎对这方面有研究的伙伴们留下有帮助的文章链接,一起探讨。
参看借鉴文章:
Elasticsearch&JDK版本要求
springboot elasticsearch 集成注意事项
同步mysql数据到ElasticSearch的最佳实践
SpringBoot集成Elasticsearch 进阶,实现中文、拼音分词,繁简体转换高级搜索