Spring Boot整合ElasticSearch实现多版本兼容的方法详解
前言
在上一篇学习springboot中,整合了mybatis、druid和pagehelper并实现了多数据源的操作。本篇主要是介绍和使用目前最火的搜索引擎elastisearch,并和springboot进行结合使用。
elasticsearch介绍
elasticsearch是一个基于lucene的搜索服务器,其实就是对lucene进行封装,提供了 rest api 的操作接口 elasticsearch作为一个高度可拓展的开源全文搜索和分析引擎,可用于快速地对大数据进行存储,搜索和分析。
elasticsearch主要特点:分布式、高可用、异步写入、多api、面向文档 。
elasticsearch核心概念:近实时,集群,节点(保存数据),索引,分片(将索引分片),副本(分片可设置多个副本) 。它可以快速地储存、搜索和分析海量数据。
elasticsearch使用案例:*、stack overflow、github 等等。
springboot整合elasticsearch
在使用springboot整合elasticsearch 之前,我们应该了解下它们之间对应版本的关系。
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** |
这里我们使用的springboot的版本是1.5.9,elasticsearch的版本是2.3.5。
使用springboot整合elasticsearch,一般都是使用 springdata 进行封装的,然后再dao层接口继承elasticsearchrepository 类,该类实现了很多的方法,比如常用的crud方法。
springdata的使用
首先,在使用之前,先做好相关的准备。
maven的配置如下:
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> <version>1.5.9.release</version> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-elasticsearch</artifactid> <version>1.5.9.release</version> </dependency>
application.properties的配置
spring.data.elasticsearch.repositories.enabled = true spring.data.elasticsearch.cluster-nodes =127.0.0.1\:9300
注: 9300 是 java 客户端的端口。9200 是支持 restful http 的接口。
更多的配置:
spring.data.elasticsearch.cluster-name elasticsearch 集群名。(默认值: elasticsearch)
spring.data.elasticsearch.cluster-nodes 集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
spring.data.elasticsearch.propertie 用来配置客户端的额外属性。
spring.data.elasticsearch.repositories.enabled 开启 elasticsearch 仓库。(默认值:true。)
代码编写
实体类
@document(indexname = "userindex", type = "user") public class user implements serializable{ /** * */ private static final long serialversionuid = 1l; /** 编号 */ private long id; /** 姓名 */ private string name; /** 年龄 */ private integer age; /** 描述 */ private string description; /** 创建时间 */ private string createtm; // getter和setter 略 }
使用springdata的时候,它需要在实体类中设置indexname 和type ,如果和传统型数据库比较的话,就相当于库和表。
需要注意的是indexname和type都必须是小写!!!
dao层
public interface userdao extends elasticsearchrepository<user, long>{ }
dao层这里就比较简单了,只需继承elasticsearchrepository该类就行了。其中主要的方法就是 save、delete和search。其中save方法相当如insert和update,没有就新增,有就覆盖。delete方法主要就是删除数据以及索引库。至于search就是查询了,包括一些常用的查询,如分页、权重之类的。
service层
@service public class userserviceimpl implements userservice { @autowired private userdao userdao; @override public boolean insert(user user) { boolean falg=false; try{ userdao.save(user); falg=true; }catch(exception e){ e.printstacktrace(); } return falg; } @override public list<user> search(string searchcontent) { querystringquerybuilder builder = new querystringquerybuilder(searchcontent); system.out.println("查询的语句:"+builder); iterable<user> searchresult = userdao.search(builder); iterator<user> iterator = searchresult.iterator(); list<user> list=new arraylist<user>(); while (iterator.hasnext()) { list.add(iterator.next()); } return list; } @override public list<user> searchuser(integer pagenumber, integer pagesize,string searchcontent) { // 分页参数 pageable pageable = new pagerequest(pagenumber, pagesize); querystringquerybuilder builder = new querystringquerybuilder(searchcontent); searchquery searchquery = new nativesearchquerybuilder().withpageable(pageable).withquery(builder).build(); system.out.println("查询的语句:" + searchquery.getquery().tostring()); page<user> searchpageresults = userdao.search(searchquery); return searchpageresults.getcontent(); } @override public list<user> searchuserbyweight(string searchcontent) { // 根据权重进行查询 functionscorequerybuilder functionscorequerybuilder = querybuilders.functionscorequery() .add(querybuilders.boolquery().should(querybuilders.matchquery("name", searchcontent)), scorefunctionbuilders.weightfactorfunction(10)) .add(querybuilders.boolquery().should(querybuilders.matchquery("description", searchcontent)), scorefunctionbuilders.weightfactorfunction(100)).setminscore(2); system.out.println("查询的语句:" + functionscorequerybuilder.tostring()); iterable<user> searchresult = userdao.search(functionscorequerybuilder); iterator<user> iterator = searchresult.iterator(); list<user> list=new arraylist<user>(); while (iterator.hasnext()) { list.add(iterator.next()); } return list; } }
这里我就简单的写了几个方法,其中主要的方法是查询。查询包括全文搜索,分页查询和权重查询。其中需要说明的是权重查询这块,权重的分值越高,查询的结果也越靠前,如果没有对其它的数据设置分值,它们默认的分值就是1,如果不想查询这些语句,只需使用setminscore将其设为大于1即可。
代码测试
调用接口进行添加数据
新增数据:
post http://localhost:8086/api/user {"id":1,"name":"张三","age":20,"description":"张三是个java开发工程师","createtm":"2018-4-25 11:07:42"} {"id":2,"name":"李四","age":24,"description":"李四是个测试工程师","createtm":"1980-2-15 19:01:32"} {"id":3,"name":"王五","age":25,"description":"王五是个运维工程师","createtm":"2016-8-21 06:11:32"}
进行全文查询
请求
http://localhost:8086/api/user?searchcontent=工程师
返回
[{"id":2,"name":"李四","age":14,"description":"李四是个测试工程师","createtm": "1980-2-15 19:01:32"}, {"id":1,"name":"张三","age":20,"description":"张三是个java开发工程师", "createtm": "2018-4-25 11:07:42"}, {"id":3,"name":"王五","age":25,"description":"王五是个运维工程师","createtm": "2016-8-21 06:11:32"}]
进行分页查询
请求
http://localhost:8086/api/user?pagenumber=0&pagesize=2&searchcontent=工程师
返回
[{"id":2,"name":"李四","age":14,"description":"李四是个测试工程师"},{"id":1,"name":"张三","age":20,"description":"张三是个java开发工程师"}]
进行权重查询
请求
http://localhost:8086/api/user2?searchcontent=李四
返回
[{"id":2,"name":"李四","age":24,"description":"李四是个测试工程师","createtm":"1980-2-15 19:01:32"}]
权重查询打印的语句:
查询的语句:{{ "function_score" : { "functions" : [ { "filter" : { "bool" : { "should" : { "match" : { "name" : { "query" : "李四", "type" : "boolean" } } } } }, "weight" : 10.0 }, { "filter" : { "bool" : { "should" : { "match" : { "description" : { "query" : "李四", "type" : "boolean" } } } } }, "weight" : 100.0 } ], "min_score" : 2.0 } }
注:测试中,因为设置了setminscore最小权重分为2的,所以无关的数据是不会显示出来的。如果想显示的话,在代码中去掉即可。
新增完数据之后,可以在浏览器输入:http://localhost:9200/_plugin/head/
然后点击基本查询,便可以查看添加的数据。如果想用语句查询,可以将程序中控制台打印的查询语句粘贴到查询界面上进行查询!
注:这里的elasticsearch是我在windows上安装的,并安装了es插件head,具体安装步骤在文章末尾。
除了springdata之外,其实还有其它的方法操作elasticsearch的。
比如使用原生elasticsearch的api,使用transportclient类实现。
或者使用由spring封装,只需在service层,进行注入bean即可。
示例:
@autowired elasticsearchtemplate elasticsearchtemplate;
但是,上述方法中都有其局限性,也就是随着elasticsearch的版本变更,相关的java api也在做不断的调整,就是elasticsearch服务端版本进行更改之后,客户端的代码可能需要重新编写。
因此介绍一个相当好用的第三方工具jestclient,它对elasticsearch进行封装,填补了 elasticsearch httprest接口 客户端的空白,它适用于elasticsearch2.x以上的版本,无需因为elasticsearch服务端版本更改而对代码进行更改!
jestclient
首先在maven中添加如下依赖:
<dependency> <groupid>io.searchbox</groupid> <artifactid>jest</artifactid> <version>5.3.3</version> </dependency>
然后编写相关的测试代码。
代码中的注释应该很完整,所以这里就不再对代码过多的讲述了。
import java.util.arraylist; import java.util.list; import org.elasticsearch.index.query.querybuilders; import org.elasticsearch.search.builder.searchsourcebuilder; import com.pancm.pojo.user; import io.searchbox.client.jestclient; import io.searchbox.client.jestclientfactory; import io.searchbox.client.jestresult; import io.searchbox.client.config.httpclientconfig; import io.searchbox.core.bulk; import io.searchbox.core.bulkresult; import io.searchbox.core.delete; import io.searchbox.core.documentresult; import io.searchbox.core.index; import io.searchbox.core.search; import io.searchbox.indices.createindex; import io.searchbox.indices.deleteindex; import io.searchbox.indices.mapping.getmapping; import io.searchbox.indices.mapping.putmapping; public class jesttest { private static jestclient jestclient; private static string indexname = "userindex"; // private static string indexname = "userindex2"; private static string typename = "user"; private static string elasticips="http://192.169.2.98:9200"; // private static string elasticips="http://127.0.0.1:9200"; public static void main(string[] args) throws exception { jestclient = getjestclient(); insertbatch(); serach1(); serach2(); serach3(); jestclient.close(); } private static jestclient getjestclient() { jestclientfactory factory = new jestclientfactory(); factory.sethttpclientconfig(new httpclientconfig.builder(elasticips).conntimeout(60000).readtimeout(60000).multithreaded(true).build()); return factory.getobject(); } public static void insertbatch() { list<object> objs = new arraylist<object>(); objs.add(new user(1l, "张三", 20, "张三是个java开发工程师","2018-4-25 11:07:42")); objs.add(new user(2l, "李四", 24, "李四是个测试工程师","1980-2-15 19:01:32")); objs.add(new user(3l, "王五", 25, "王五是个运维工程师","2016-8-21 06:11:32")); boolean result = false; try { result = insertbatch(jestclient,indexname, typename,objs); } catch (exception e) { e.printstacktrace(); } system.out.println("批量新增:"+result); } /** * 全文搜索 */ public static void serach1() { string query ="工程师"; try { searchsourcebuilder searchsourcebuilder = new searchsourcebuilder(); searchsourcebuilder.query(querybuilders.querystringquery(query)); //分页设置 searchsourcebuilder.from(0).size(2); system.out.println("全文搜索查询语句:"+searchsourcebuilder.tostring()); system.out.println("全文搜索返回结果:"+search(jestclient,indexname, typename, searchsourcebuilder.tostring())); } catch (exception e) { e.printstacktrace(); } } /** * 精确搜索 */ public static void serach2() { try { searchsourcebuilder searchsourcebuilder = new searchsourcebuilder(); searchsourcebuilder.query(querybuilders.termquery("age", 24)); system.out.println("精确搜索查询语句:"+searchsourcebuilder.tostring()); system.out.println("精确搜索返回结果:"+search(jestclient,indexname, typename, searchsourcebuilder.tostring())); } catch (exception e) { e.printstacktrace(); } } /** * 区间搜索 */ public static void serach3() { string createtm="createtm"; string from="2016-8-21 06:11:32"; string to="2018-8-21 06:11:32"; try { searchsourcebuilder searchsourcebuilder = new searchsourcebuilder(); searchsourcebuilder.query(querybuilders.rangequery(createtm).gte(from).lte(to)); system.out.println("区间搜索语句:"+searchsourcebuilder.tostring()); system.out.println("区间搜索返回结果:"+search(jestclient,indexname, typename, searchsourcebuilder.tostring())); } catch (exception e) { e.printstacktrace(); } } /** * 创建索引 * @param indexname * @return * @throws exception */ public boolean createindex(jestclient jestclient,string indexname) throws exception { jestresult jr = jestclient.execute(new createindex.builder(indexname).build()); return jr.issucceeded(); } /** * 新增数据 * @param indexname * @param typename * @param source * @return * @throws exception */ public boolean insert(jestclient jestclient,string indexname, string typename, string source) throws exception { putmapping putmapping = new putmapping.builder(indexname, typename, source).build(); jestresult jr = jestclient.execute(putmapping); return jr.issucceeded(); } /** * 查询数据 * @param indexname * @param typename * @return * @throws exception */ public static string getindexmapping(jestclient jestclient,string indexname, string typename) throws exception { getmapping getmapping = new getmapping.builder().addindex(indexname).addtype(typename).build(); jestresult jr =jestclient.execute(getmapping); return jr.getjsonstring(); } /** * 批量新增数据 * @param indexname * @param typename * @param objs * @return * @throws exception */ public static boolean insertbatch(jestclient jestclient,string indexname, string typename, list<object> objs) throws exception { bulk.builder bulk = new bulk.builder().defaultindex(indexname).defaulttype(typename); for (object obj : objs) { index index = new index.builder(obj).build(); bulk.addaction(index); } bulkresult br = jestclient.execute(bulk.build()); return br.issucceeded(); } /** * 全文搜索 * @param indexname * @param typename * @param query * @return * @throws exception */ public static string search(jestclient jestclient,string indexname, string typename, string query) throws exception { search search = new search.builder(query) .addindex(indexname) .addtype(typename) .build(); jestresult jr = jestclient.execute(search); // system.out.println("--"+jr.getjsonstring()); // system.out.println("--"+jr.getsourceasobject(user.class)); return jr.getsourceasstring(); } /** * 删除索引 * @param indexname * @return * @throws exception */ public boolean delete(jestclient jestclient,string indexname) throws exception { jestresult jr = jestclient.execute(new deleteindex.builder(indexname).build()); return jr.issucceeded(); } /** * 删除数据 * @param indexname * @param typename * @param id * @return * @throws exception */ public boolean delete(jestclient jestclient,string indexname, string typename, string id) throws exception { documentresult dr = jestclient.execute(new delete.builder(id).index(indexname).type(typename).build()); return dr.issucceeded(); }
注:测试之前先说明下,本地windows系统安装的是elasticsearch版本是2.3.5,linux服务器上安装的elasticsearch版本是6.2。
测试结果
全文搜索
全文搜索查询语句:{ "from" : 0, "size" : 2, "query" : { "query_string" : { "query" : "工程师" } } } 全文搜索返回结果:{"id":1,"name":"张三","age":20,"description":"张三是个java开发工程师","createtm":"2018-4-25 11:07:42"},{"id":2,"name":"李四","age":24,"description":"李四是个测试工程师","createtm":"1980-2-15 19:01:32"}
匹配搜索
精确搜索查询语句:{ "query" : { "term" : { "age" : 24 } } } 精确搜索返回结果:{"id":2,"name":"李四","age":24,"description":"李四是个测试工程师","createtm":"1980-2-15 19:01:32"}
时间区间搜索
区间搜索语句:{ "query" : { "range" : { "createtm" : { "from" : "2016-8-21 06:11:32", "to" : "2018-8-21 06:11:32", "include_lower" : true, "include_upper" : true } } } } 区间搜索返回结果:{"id":1,"name":"张三","age":20,"description":"张三是个java开发工程师","createtm":"2018-4-25 11:07:42"}
新增完数据之后,我们可以上linux的 kibana中进行相关的查询,查询结果如下:
注:kibana 是属于elk中一个开源软件。kibana可以为 logstash 和 elasticsearch 提供的日志分析友好的 web 界面,可以帮助汇总、分析和搜索重要数据日志。
上述代码中测试返回的结果符合我们的预期。其中关于jestclient只是用到了很少的一部分,更多的使用可以查看jestclient的官方文档。
windows安装elasticsearch
1,文件准备
下载地址:
选择elasticsearch相关版本, 然后选择后缀名为zip文件进行下载,下载之后进行解压。
2,启动elasticsearch
进入bin目录下,运行 elasticsearch.bat
然后在浏览上输入: localhost:9200
成功显示一下界面表示成功!
3,安装es插件
web管理界面head 安装
进入bin目录下,打开cmd,进入dos界面
输入:plugin install mobz/elasticsearch-head
进行下载
成功下载之后,在浏览器输入:http://localhost:9200/_plugin/head/
若显示一下界面,则安装成功!
4,注册服务
进入bin目录下,打开cmd,进入dos界面
依次输入:
service.bat install service.bat start
成功之后,再输入
services.msc
跳转到service服务界面,可以直接查看es的运行状态!
其它
elasticsearch官网api地址:
jestclientgithub地址:
https://github.com/searchbox-io/jest (本地下载)
项目我放到github上面去了。
https://github.com/xuwujing/springboot (本地下载)
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。