1.1 部署es和kibana
1.2 springboot整合es及配置
1.2.1 引入相关依赖
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
<exclusions>
<exclusion>
<groupid>org.elasticsearch.client</groupid>
<artifactid>elasticsearch-rest-high-level-client</artifactid>
</exclusion>
<exclusion>
<groupid>org.elasticsearch.client</groupid>
<artifactid>elasticsearch-rest-client</artifactid>
</exclusion>
<exclusion>
<groupid>org.elasticsearch</groupid>
<artifactid>elasticsearch</artifactid>
</exclusion>
</exclusions>
</dependency>
<!--es相关-->
<dependency>
<groupid>org.elasticsearch.client</groupid>
<artifactid>elasticsearch-rest-high-level-client</artifactid>
<version>7.14.0</version>
</dependency>
<dependency>
<groupid>org.elasticsearch.client</groupid>
<artifactid>elasticsearch-rest-client</artifactid>
<version>7.14.0</version>
</dependency>
<dependency>
<groupid>org.elasticsearch</groupid>
<artifactid>elasticsearch</artifactid>
<version>7.14.0</version>
</dependency>
<dependency>
<groupid>org.dromara.easy-es</groupid>
<artifactid>easy-es-boot-starter</artifactid>
<version>2.0.0-beta4</version>
</dependency>
1.2.2 yml相关配置
# easy-es配置
easy-es:
# 是否启动(预先关闭)
enable: true
# es连接地址+端口 格式必须为ip:port,如果是集群则可用逗号隔开
address: 192.168.164.128:9200
# 如果无账号密码则可不配置此行
#username:
# 如果无账号密码则可不配置此行
#password:
# 默认为http 可缺省
schema: http
# 默认为true 打印banner 若您不期望打印banner,可配置为false
banner: false
# 心跳策略时间 单位:ms
keep-alive-millis: 30000
# 连接超时时间 单位:ms
connect-timeout: 5000
# 通信超时时间 单位:ms
socket-timeout: 600000
# 连接请求超时时间 单位:ms
connection-request-timeout: 5000
# 最大连接数 单位:个
max-conn-total: 100
# 最大连接路由数 单位:个
max-conn-per-route: 100
global-config:
# 索引处理模式,smoothly:平滑模式, not_smoothly:非平滑模式, manual:手动模式,,默认开启此模式
process-index-mode: manual
# 开启控制台打印通过本框架生成的dsl语句,默认为开启,测试稳定后的生产环境建议关闭,以提升少量性能
print-dsl: true
# 当前项目是否分布式项目,默认为true,在非手动托管索引模式下,若为分布式项目则会获取分布式锁,非分布式项目只需synchronized锁.
distributed: false
# 重建索引超时时间 单位小时,默认72h 可根据es中存储的数据量调整
reindextimeouthours: 72
# 异步处理索引是否阻塞主线程 默认阻塞 数据量过大时调整为非阻塞异步进行 项目启动更快
async-process-index-blocking: true
db-config:
# 是否开启下划线转驼峰 默认为false
map-underscore-to-camel-case: true
# 索引前缀,可用于区分环境 默认为空 用法和mp的tableprefix一样的作用和用法
# index-prefix: template_
# id生成策略 customize为自定义,id值由用户生成,比如取mysql中的数据id,如缺省此项配置,则id默认策略为es自动生成
id-type: customize
# 数据刷新策略,默认为不刷新,若对数据时效性要求比较高,可以调整为immediate,但性能损耗高,也可以调整为折中的wait_until
# refresh-policy: immediate
1.3 索引crud
1.3.1 索引托管自动挡
1.3.1.1 配置实体模板
@data
@indexname(shardsnum = 3,replicasnum = 2) // 可指定分片数,副本数,若缺省则默认均为1
public class document {
/**
* es中的唯一id,如果你想自定义es中的id为你提供的id,比如mysql中的id,请将注解中的type指定为customize,如此id便支持任意数据类型)
*/
@indexid(type = idtype.customize)
private long id;
/**
* 文档标题,不指定类型默认被创建为keyword_text类型,可进行精确查询
*/
private string title;
/**
* 文档内容,指定了类型及存储/查询分词器
*/
@highlight(mappingfield="highlightcontent")
@indexfield(fieldtype = fieldtype.text, analyzer = analyzer.ik_smart, searchanalyzer = analyzer.ik_max_word)
private string content;
/**
* 作者 加@tablefield注解,并指明strategy = fieldstrategy.not_empty 表示更新的时候的策略为 创建者不为空字符串时才更新
*/
@indexfield(strategy = fieldstrategy.not_empty)
private string creator;
/**
* 创建时间
*/
@indexfield(fieldtype = fieldtype.date, dateformat = "yyyy-mm-dd hh:mm:ss||yyyy-mm-dd||epoch_millis")
private string gmtcreate;
/**
* es中实际不存在的字段,但模型中加了,为了不和es映射,可以在此类型字段上加上 注解@tablefield,并指明exist=false
*/
@indexfield(exist = false)
private string notexistsfield;
/**
* 地理位置经纬度坐标 例如: "40.13933715136454,116.63441990026217"
*/
@indexfield(fieldtype = fieldtype.geo_point)
private string location;
/**
* 图形(例如圆心,矩形)
*/
@indexfield(fieldtype = fieldtype.geo_shape)
private string geolocation;
/**
* 自定义字段名称
*/
@indexfield(value = "wu-la")
private string customfield;
/**
* 高亮返回值被映射的字段
*/
private string highlightcontent;
}
1.3.1.2 配置启动模式
easy-es:
sockettimeout: 600000 # 请求通信超时时间 单位:ms 默认值600000ms 在平滑模式下,由于要迁移数据,用户可根据数据量大小调整此参数值大小,否则请求容易超时导致索引托管失败,建议您尽量给大不给小,跟那玩意一样,大点没事,太小你懂的!
global-config:
process_index_mode: smoothly #smoothly:平滑模式, not_smoothly:非平滑模式, manual:手动模式
async-process-index-blocking: true # 异步处理索引是否阻塞主线程 默认阻塞
distributed: false # 项目是否分布式环境部署,默认为true, 如果是单机运行可填false,将不加分布式锁,效率更高.
reindextimeouthours: 72 # 重建索引超时时间 单位小时,默认72h 根据迁移索引数据量大小灵活指定
1.3.2 索引手动挡
1.3.2.1 配置启动模式
easy-es:
global-config:
process_index_mode: manual # 手动挡模式
1.3.2.2 配置实体模板
/**
* 实体类信息
**/
@data
@indexname(shardsnum = 3, replicasnum = 2, keepglobalprefix = true)
public class document {
/**
* es中的唯一id,如果你想自定义es中的id为你提供的id,比如mysql中的id,请将注解中的type指定为customize或直接在全局配置文件中指定,如此id便支持任意数据类型)
*/
@indexid(type = idtype.customize)
private string id;
/**
* 文档标题,不指定类型默认被创建为keyword类型,可进行精确查询
*/
private string title;
/**
* 文档内容,指定了类型及存储/查询分词器
*/
@highlight(mappingfield = "highlightcontent")
@indexfield(fieldtype = fieldtype.text, analyzer = analyzer.ik_smart, searchanalyzer = analyzer.ik_max_word)
private string content;
// 省略其它字段...
}
1.3.2.3 创建索引
/**
* 方式1
*/
@test
public void testcreateindexbyentity() {
// 绝大多数场景推荐使用 简单至上
documentmapper.createindex();
}
/**
* 方式2
*/
@test
public void testcreateindexbyentity() {
// 适用于定时任务按日期创建索引场景
string indexname = localdate.now().format(datetimeformatter.ofpattern("yyyy-mm-dd"));
documentmapper.createindex(indexname);
}
/**
* 方式3
*/
@test
public void testcreateindex() {
// 复杂场景使用
lambdaesindexwrapper<document> wrapper = new lambdaesindexwrapper<>();
// 此处简单起见 索引名称须保持和实体类名称一致,字母小写 后面章节会教大家更如何灵活配置和使用索引
wrapper.indexname(document.class.getsimplename().tolowercase());
// 此处将文章标题映射为keyword类型(不支持分词),文档内容映射为text类型(支持分词查询)
wrapper.mapping(document::gettitle, fieldtype.keyword, 2.0f)
.mapping(document::getlocation, fieldtype.geo_point)
.mapping(document::getgeolocation, fieldtype.geo_shape)
.mapping(document::getcontent, fieldtype.text, analyzer.ik_smart, analyzer.ik_max_word);
// 0.9.8+版本,增加对符串字段名称的支持,document实体中须在对应字段上加上@tablefield(value="wu-la")用于映射此字段值
wrapper.mapping("wu-la", fieldtype.text, analyzer.ik_max_word, analyzer.ik_max_word);
// 设置分片及副本信息,可缺省
wrapper.settings(3, 2);
// 设置别名信息,可缺省
string aliasname = "daily";
wrapper.createalias(aliasname);
// 设置父子信息,若无父子文档关系则无需设置, 可缺省
wrapper.join("joinfield", "document", "comment");
// 创建索引
boolean isok = documentmapper.createindex(wrapper);
assertions.asserttrue(isok);
}
1.3.2.4 查询索引
@test
public void testexistsindex() {
// 测试是否存在指定名称的索引
string indexname = document.class.getsimplename().tolowercase();
boolean existsindex = documentmapper.existsindex(indexname);
assertions.asserttrue(existsindex);
}
@test
public void testgetindex() {
getindexresponse indexresponse = documentmapper.getindex();
// 这里打印下索引结构信息 其它分片等信息皆可从indexresponse中取
indexresponse.getmappings().foreach((k, v) -> system.out.println(v.getsourceasmap()));
}
1.3.2.5 更新索引(不推荐)
@test
public void testupdateindex() {
// 测试更新索引
lambdaesindexwrapper<document> wrapper = new lambdaesindexwrapper<>();
// 指定要更新哪个索引
string indexname = document.class.getsimplename().tolowercase();
wrapper.indexname(indexname);
wrapper.mapping(document::getcreator, fieldtype.keyword);
wrapper.mapping(document::getgmtcreate, fieldtype.date);
boolean isok = documentmapper.updateindex(wrapper);
assertions.asserttrue(isok);
}
1.3.2.6 删除索引
@test
public void testdeleteindex() {
// 指定要删除哪个索引
string indexname = document.class.getsimplename().tolowercase();
boolean isok = documentmapper.deleteindex(indexname);
assertions.asserttrue(isok);
}
1.4 数据crud
1.4.1 数据同步
1.4.2 数据crud
1.4.2.1 新增数据
// 插入一条记录,默认插入至当前mapper对应的索引
integer insert(t entity);
// 插入一条记录 可指定具体插入的索引,多个用逗号隔开
integer insert(t entity, string... indexnames);
// 批量插入多条记录
integer insertbatch(collection<t> entitylist)
// 批量插入多条记录 可指定具体插入的索引,多个用逗号隔开
integer insertbatch(collection<t> entitylist, string... indexnames);
- 如果您在insert时传入的entity有id并且该id对应数据已存在,则此次insert实际效果为更新该id对应的数据,并且更新不计入insert接口最后返回的成功总条数.
- 当insert接口如上所述,触发了数据更新逻辑,本次更新字段和全局配置的策略(如not_null/not_empty)等均不生效,若您期望策略生效,可以调用update接口而非insert接口.
- 插入后如需id值可直接从entity中取,用法和mp中一致,批量插入亦可直接从原对象中获取插入成功后的数据id,以上接口返回integer为成功条数.
1.4.2.2 删除数据
// 根据 id 删除
integer deletebyid(serializable id);
// 根据 id 删除 可指定具体的索引,多个用逗号隔开
integer deletebyid(serializable id, string... indexnames);
// 根据 entity 条件,删除记录
integer delete(lambdaesquerywrapper<t> wrapper);
// 删除(根据id 批量删除)
integer deletebatchids(collection<? extends serializable> idlist);
// 删除(根据id 批量删除)可指定具体的索引,多个用逗号隔开
integer deletebatchids(collection<? extends serializable> idlist, string... indexnames);
1.4.2.3 更新数据(不推荐)
//根据 id 更新
integer updatebyid(t entity);
//根据 id 更新 可指定具体的索引,多个用逗号隔开
integer updatebyid(t entity, string... indexnames);
// 根据id 批量更新
integer updatebatchbyids(collection<t> entitylist);
//根据 id 批量更新 可指定具体的索引,多个用逗号隔开
integer updatebatchbyids(collection<t> entitylist, string... indexnames);
// 根据动态条件 更新记录
integer update(t entity, lambdaesupdatewrapper<t> updatewrapper);
1.4.2.4 查询数据(推荐分页查询)
// 获取总数
long selectcount(lambdaesquerywrapper<t> wrapper);
// 获取总数 distinct为是否去重 若为ture则必须在wrapper中指定去重字段
long selectcount(wrapper<t> wrapper, boolean distinct);
// 根据 id 查询
t selectbyid(serializable id);
// 根据 id 查询 可指定具体的索引,多个用逗号隔开
t selectbyid(serializable id, string... indexnames);
// 查询(根据id 批量查询)
list<t> selectbatchids(collection<? extends serializable> idlist);
// 查询(根据id 批量查询)可指定具体的索引,多个用逗号隔开
list<t> selectbatchids(collection<? extends serializable> idlist, string... indexnames);
// 根据动态查询条件,查询一条记录 若存在多条记录 会报错
t selectone(lambdaesquerywrapper<t> wrapper);
// 根据动态查询条件,查询全部记录
list<t> selectlist(lambdaesquerywrapper<t> wrapper);
1.4.3 嵌套查询
/**
* 场景一: 嵌套and的使用
*/
@test
public void testnestedand() {
// 下面查询条件等价于mysql中的 select * from document where star_num in (1, 2) and (title = '老汉' or title = '推*')
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.in(document::getstarnum, 1, 2)
.and(w -> w.eq(document::gettitle, "老汉").or().eq(document::gettitle, "推*"));
list<document> documents = documentmapper.selectlist(wrapper);
}
/**
* 场景二: 拼接and的使用
*/
@test
public void testand(){
// 下面查询条件等价于mysql中的 select * from document where title = '老汉' and content like '推*'
// 拼接and比较特殊,因为使用场景最多,所以条件与条件之间默认就是拼接and,所以可以直接省略,这点和mp是一样的
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.eq(document::gettitle, "老汉")
.match(document::getcontent, "推*");
list<document> documents = documentmapper.selectlist(wrapper);
}
/**
* 场景二: 嵌套or的使用
*/
@test
public void testnestedor() {
// 下面查询条件等价于mysql中的 select * from document where star_num = 1 or (title = '老汉' and creator = '糟老头子')
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.eq(document::getstarnum, 1)
.or(i -> i.eq(document::gettitle, "老汉").eq(document::getcreator, "糟老头子"));
list<document> documents = documentmapper.selectlist(wrapper);
}
/**
* 场景三: 拼接or的使用
*/
@test
public void testor() {
// 下面查询条件等价于mysql中的 select * from document where title = '老汉' or title = '痴汉'
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.eq(document::gettitle, "老汉")
.or()
.eq(document::gettitle, "痴汉");
list<document> documents = documentmapper.selectlist(wrapper);
}
/**
* 场景四: 嵌套filter的使用 其实和场景一一样,只不过filter中的条件不计算得分,无法按得分排序,查询性能稍高
*/
@test
public void testnestedfilter() {
// 下面查询条件等价于mysql中的 select * from document where star_num in (1, 2) and (title = '老汉' or title = '推*')
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.in(document::getstarnum, 1, 2)
.filter(w -> w.eq(document::gettitle, "老汉").or().eq(document::gettitle, "推*"));
list<document> documents = documentmapper.selectlist(wrapper);
}
/**
* 场景五: 拼接filter的使用 filter中的条件不计算得分,无法按得分排序,查询性能稍高
*/
@test
public void testfilter() {
// 下面查询条件等价于mysql中的 select * from document where title = '老汉'
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.filter().eq(document::gettitle, "老汉");
list<document> documents = documentmapper.selectlist(wrapper);
}
/**
* 场景六: 嵌套mustnot的使用
*/
@test
public void testnestednot() {
// 下面查询条件等价于mysql中的 select * from document where title = '老汉' and (size != 18 and age != 18)
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.eq(document::gettitle, "老汉")
.not(i->i.eq(size,18).eq(age,18));
list<document> documents = documentmapper.selectlist(wrapper);
}
/**
* 场景六: 拼接not()的使用
*/
@test
public void testnot() {
// 下面查询条件等价于mysql中的 select * from document where title = '老汉' and size != 18
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.eq(document::gettitle, "老汉")
.not()
.eq(size,18);
list<document> documents = documentmapper.selectlist(wrapper);
}
1.4.4 链式调用
// 索引链式构造器
lambdaesindexchainwrapper<t> lambdachainindex(baseesmapper<t> baseesmapper);
// 查询链式构造器
lambdaesquerychainwrapper<t> lambdachainquery(baseesmapper<t> baseesmapper);
// 更新(含删除)链式构造器
lambdaesupdatechainwrapper<t> lambdachainupdate(baseesmapper<t> baseesmapper);
@test
public void testone() {
// 隔壁老汉写的链式调用
document document = eswrappers.lambdachainquery(documentmapper).eq(document::gettitle, "隔壁老汉").one();
}
@test
public void testselectone() {
// 隔壁老王写的半吊子链式调用
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.eq(document::gettitle, "隔壁老王")
.limit(1);
document document = documentmapper.selectone(wrapper);
}
1.5 拓展功能
1.5.1 混合查询
/**
* 正确使用姿势0(最实用,最简单,最推荐的使用姿势):ee满足的语法,直接用,不满足的可以构造原生querybuilder,然后通过wrapper.mix传入querybuilder
* @since 2.0.0-beta2 2.0.0-beta2才正式引入此方案,此方案为混合查询的最优解决方案,由于querybuilder涵盖了es中全部的查询,所以通过此方案
* 理论上可以处理任何复杂查询,并且可以和ee提供的四大嵌套类型无缝衔接,彻底简化查询,解放生产力!
*/
@test
public void testmix0(){
// 查询标题为老汉,内容匹配 推*,且最小匹配度不低于80%的数据
// 当前我们提供的开箱即用match并不支持设置最小匹配度,此时就可以自己去构造一个matchquerybuilder来实现
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
querybuilder querybuilder = querybuilders.matchquery("content", "推*").minimumshouldmatch("80%");
wrapper.eq(document::gettitle,"老汉").mix(querybuilder);
list<document> documents = documentmapper.selectlist(wrapper);
system.out.println(documents);
}
/**
* 混合查询正确使用姿势1: ee提供的功能不支持某些过细粒度的功能,所有查询条件通过原生语法构造,仅利用ee提供的数据解析功能
*/
@test
public void testmix1() {
// resthighlevelclient原生语法
searchsourcebuilder searchsourcebuilder = new searchsourcebuilder();
searchsourcebuilder.query(querybuilders.matchquery("content", "推*").minimumshouldmatch("80%"));
// 仅利用ee查询并解析数据功能
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.setsearchsourcebuilder(searchsourcebuilder);
list<document> documents = documentmapper.selectlist(wrapper);
system.out.println(documents);
}
1.5.2 分页查询
// 物理分页
espageinfo<t> pagequery(lambdaesquerywrapper<t> wrapper, integer pagenum, integer pagesize);
@test
public void testpagequery() {
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.match(document::gettitle, "老汉");
espageinfo<document> documentpageinfo = documentmapper.pagequery(wrapper,1,10);
system.out.println(documentpageinfo);
}
1.5.3 排序
// 降序排列
wrapper.orderbydesc(排序字段,支持多字段)
// 升序排列
wrapper.orderbyasc(排序字段,支持多字段)
// 根据得分排序(此功能0.9.7+版本支持;不指定sortorder时默认降序,得分高的在前,支持升序/降序)
wrapper.sortbyscore(boolean condition,sortorder sortorder)
1.5.4 分词 / 模糊匹配
@test
public void testmatch(){
// 会对输入做分词,只要所有分词中有一个词在内容中有匹配就会查询出该数据,无视分词顺序
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.match(document::getcontent,"技术");
list<document> documents = documentmapper.selectlist(wrapper);
system.out.println(documents.size());
}
@test
public void testmatchphrase() {
// 会对输入做分词,但是需要结果中也包含所有的分词,而且顺序要求一样,否则就无法查询出结果
// 例如es中数据是 技术过硬,如果搜索关键词为过硬技术就无法查询出结果
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.matchphrase(document::getcontent, "技术");
list<document> documents = documentmapper.selectlist(wrapper);
system.out.println(documents);
}
@test
public void testmatchallquery() {
// 查询所有数据,类似mysql select all.
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.matchallquery();
list<document> documents = documentmapper.selectlist(wrapper);
system.out.println(documents);
}
@test
public void testmultimatchquery() {
// 从多个指定字段中查询包含老王的数据
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.multimatchquery("老王", document::gettitle, document::getcontent, document::getcreator, document::getcustomfield);
// 其中,默认的operator为or,默认的minshouldmatch为60% 这两个参数都可以按需调整,我们api是支持的 例如:
// 其中and意味着所有搜索的token都必须被匹配,or表示只要有一个token匹配即可. minshouldmatch 80 表示只查询匹配度大于80%的数据
// wrapper.multimatchquery("老王",operator.and,80,document::getcustomfield,document::getcontent);
list<document> documents = documentmapper.selectlist(wrapper);
system.out.println(documents.size());
system.out.println(documents);
}
1.5.5 条件过滤
/**
* 场景五: 拼接filter的使用 filter中的条件不计算得分,无法按得分排序,查询性能稍高
*/
@test
public void testfilter() {
// 下面查询条件等价于mysql中的 select * from document where title = '老汉'
lambdaesquerywrapper<document> wrapper = new lambdaesquerywrapper<>();
wrapper.filter().eq(document::gettitle, "老汉");
list<document> documents = documentmapper.selectlist(wrapper);
}
发表评论