Easy-Es介绍
Easy-Es
Easy-Es(简称EE)是一款基于ElasticSearch(简称Es)官方提供的RestHighLevelClient打造的ORM开发框架,在 RestHighLevelClient 的基础上,只做增强不做改变,为简化开发、提高效率而生。
引入ES相关依赖
我是新创建了一个空的Springboot工程,所以要想使用 easy-es 就必须添加他需要用到的依赖才可以。如果es和kibana的环境还没有搭建好,可以参考我之前的文章。
Linux中安装elasticsearch和kibana-CSDN博客
在pom中引入
<dependencies><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><dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-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-beta7</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
添加easy-es配置
这个地址就是自己es的访问地址,我的安装在了我的Linux虚拟机中。
easy-es:address: 192.168.200.128:9200global-config:process-index-mode:
# username: #es用户名,若无则删去此行配置
# password: #es密码,若无则删去此行配置
在启动类添加注解
在 Spring Boot 启动类中添加 @EsMapperScan 注解,扫描 有关 es 的 Mapper 文件夹。这个路径是根据自己项目中,关于es的mapper 所在位置来写的。
@EsMapperScan("com.sde.mapper.es")
@SpringBootApplication
public class EasyEsDemoApplication {public static void main(String[] args) {SpringApplication.run(EasyEsDemoApplication.class, args);}}
启动项目测试
可以看到项目正常启动了
引入MP相关依赖
就是引入mybatis-plus相关的依赖,便于我们查询数据,然后同步到es中。
在pom中引入
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.23</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.26</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>
添加mp配置
这个就是我们肯定要连接我们自己的MySQL数据库,来进行crud的操作。当然里面配置的环境地址也要替换成自己的。
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.200.128:3306/sde_item?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: roottype: com.alibaba.druid.pool.DruidDataSourcedruid:initial-size: 5min-idle: 5max-active: 20max-wait: 60000mybatis-plus:mapper-locations: classpath:mapper/*Mapper.xmlconfiguration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImpl
fastJson相关配置
jackson:serialization:write-dates-as-timestamps: false #禁用将日期序列化为时间戳date-format: yyyy-MM-dd HH:mm:ss #日期格式time-zone: GMT+8 #时区
启动项目测试
项目也是正常启动的
先测试mp是否可用
创建item表对应的实体
在 com.sde.entity.mp 包下面,创建Item实体类。
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("item")
public class Item implements Serializable {@TableId(type = IdType.AUTO)private Long id;private String name;private Integer price;private Integer stock;private String image;private String category;private String brand;private String spec;private Integer sold;private Integer commentCount;@TableField("isAD")private Boolean isAD;private Integer status;private LocalDateTime createTime;private LocalDateTime updateTime;private Long creater;private Long updater;
}
创建ItemMapper接口
在 com.sde.mapper.mp 包里面,创建一个名为 ItemMapper的接口,然后继承 BaseMapper
@Mapper
public interface ItemMapper extends BaseMapper<Item> {
}
测试mp是否可用
在测试类里面,使用 ItemMapper 进行测试。
@SpringBootTest
public class MpTest {@Autowiredprivate ItemMapper itemMapper;@Test@DisplayName(("测试根据id查询"))void testSelectById() {Item item = itemMapper.selectById(317578L);System.out.println(item);}
}
可以看到是能查到数据的,到此开始编写es相关的代码,前置已经准备好了。
Easy-Es 配置
基础配置
使用 easy-es 的必要配置,如果缺失这些配置可能会导致项目无法启动,但是账号密码,如果我们没有设置的话,可以不用添加上去。
easy-es:address : 127.0.0.1:9200 # es连接地址+端口 格式必须为ip:port,如果是集群则可用逗号隔开username: sde #如果无账号密码则可不配置此行password: 111 #如果无账号密码则可不配置此行
扩展配置
可缺省,不影响项目启动,为了提高生产环境性能,建议您按需配置
创建Es索引
Es的索引就类似于MySQL的数据表。我们先根据我们的 Item实体,创建es索引。
特别注意:如果您开启了索引托管-平滑模式(默认开启),并且您需要迁移的数据量很大,可以调大socketTimeout,否则迁移可能会超时异常 单位是毫秒,默认为1分钟,我们经过测试发现迁移1万条数据大约需要5秒左右,当然该数值需要综合考虑您的服务器硬件负载等因素,因此建议 您按需配置。
easy-es:keep-alive-millis: 30000 # 心跳策略时间 单位:msconnect-timeout: 5000 # 连接超时时间 单位:mssocket-timeout: 600000 # 通信超时时间 单位:ms request-timeout: 5000 # 请求超时时间 单位:msconnection-request-timeout: 5000 # 连接请求超时时间 单位:msmax-conn-total: 100 # 最大连接数 单位:个max-conn-per-route: 100 # 最大连接路由数 单位:个
全局配置
可缺省,不影响项目启动,若缺省则为默认值
easy-es:enable: true # 是否开启EE自动配置 默认开启,可缺省schema: http # 默认值为http 可缺省 也支持https免ssl方式 配置此值为 https 即可banner: true # 默认为true 打印banner 若您不期望打印banner,可配置为falseglobal-config:i-kun-mode: false # 是否开启小黑子模式,默认关闭, 开启后日志将更有趣,提升编码乐趣,仅供娱乐,切勿用于其它任何用途process-index-mode: manual #索引处理模式,smoothly:平滑模式, not_smoothly:非平滑模式, manual:手动模式,,默认开启此模式print-dsl: true # 开启控制台打印通过本框架生成的DSL语句,默认为开启,测试稳定后的生产环境建议关闭,以提升少量性能distributed: false # 当前项目是否分布式项目,默认为true,在非手动托管索引模式下,若为分布式项目则会获取分布式锁,非分布式项目只需synchronized锁.reindexTimeOutHours: 72 # 重建索引超时时间 单位小时,默认72H 可根据ES中存储的数据量调整async-process-index-blocking: true # 异步处理索引是否阻塞主线程 默认阻塞 数据量过大时调整为非阻塞异步进行 项目启动更快active-release-index-max-retry: 4320 # 分布式环境下,平滑模式,当前客户端激活最新索引最大重试次数,若数据量过大,重建索引数据迁移时间超过4320/60=72H,可调大此参数值,此参数值决定最大重试次数,超出此次数后仍未成功,则终止重试并记录异常日志active-release-index-fixed-delay: 60 # 分布式环境下,平滑模式,当前客户端激活最新索引最大重试次数 分布式环境下,平滑模式,当前客户端激活最新索引重试时间间隔 若您期望最终一致性的时效性更高,可调小此值,但会牺牲一些性能db-config:map-underscore-to-camel-case: false # 是否开启下划线转驼峰 默认为falseindex-prefix: daily_ # 索引前缀,可用于区分环境 默认为空 用法和MP的tablePrefix一样的作用和用法id-type: customize # id生成策略 customize为自定义,id值由用户生成,比如取MySQL中的数据id,如缺省此项配置,则id默认策略为es自动生成field-strategy: not_empty # 字段更新策略 默认为not_nullenable-track-total-hits: true # 默认开启,开启后查询所有匹配数据,若不开启,会导致无法获取数据总条数,其它功能不受影响,若查询数量突破1W条时,需要同步调整@IndexName注解中的maxResultWindow也大于1w,并重建索引后方可在后续查询中生效(不推荐,建议分页查询).refresh-policy: immediate # 数据刷新策略,默认为不刷新,若对数据时效性要求比较高,可以调整为immediate,但性能损耗高,也可以调整为折中的wait_untilbatch-update-threshold: 10000 # 批量更新接口的阈值 默认值为1万,突破此值需要同步调整enable-track-total-hits=true,@IndexName.maxResultWindow > 1w,并重建索引.smartAddKeywordSuffix: true # 是否智能为字段添加.keyword后缀 默认开启,开启后会根据当前字段的索引类型及当前查询类型自动推断本次查询是否需要拼接.keyword后缀
其他配置
logging:level:tracer: trace # 开启trace级别日志,在开发时可以开启此配置,则控制台可以打印es全部请求信息及DSL语句,为了避免重复,开启此项配置后,可以将EE的print-dsl设置为false.
Easy-Es 配置
@EsMapperScan
描述:mapper 扫描注解,功能跟mp的@MapperScan 一致
使用位置:Springboot 启动类
@EsMapperScan("com.sde.mapper.es")
@SpringBootApplication
public class Application{}
属性 | 类型 | 必须指定 | 默认值 | 描述 |
value | String[] | 是 | "" | 自定义mapper所在包全路径,2.1.0 + 版本支持多包扫描 |
注意:由于EE和MP对Mapper的扫描都是采用Springboot的doScan,而且两套系统互相独立,所以在扫描的时候没有办法互相隔离,因此如果您的项目同时有用到EE和MP,您需要将EE的Mapper和MP的Mapper放在不同的包下,否则项目将无法正常启动。
@Settings
描述:索引Settings信息注解,可设置ES索引中的Settings信息
使用位置:实体类
@Settings(shardsNum = 3, replicasNum = 2, settingsProvider = MySettingsProvider.class)
public class ItemDoc {// 其它字段省略
}
属性 | 类型 | 必须指定 | 默认值 | 描述 |
shardsNum | int | 否 | 1 | 索引分片数,默认值为1 |
shardsNum | int | 否 | 1 | 索引副本数,默认值为1 |
maxResultWindow | int | 否 | 10000 | 默认最大返回数 |
refreshInterval | String | 否 | "" | 索引的刷新间隔 es默认值为1s ms:表示毫秒 s:表示秒 m:表示分钟 |
settingsProvider | Class | 否 | settingsProvider | 自定义settings提供类 默认为DefaultSettingsProvider空实现 如需自定义,可继承此类并覆写getSettings方法 将settings信息以Map返回 |
注意:maxResultWindow:默认最大返回数,默认值为10000,超过此值推荐使用searchAfter或滚动查询等方式,性能更好,详见拓展功能章节. 当此值调整至大于1W后,需要更新索引并同步开启配置文件中的enable-track-total-hits=true方可生效
settingsProvider:自定义settings提供类,当小部分场景下,框架内置的这些参数不足以满足您对索引Settings的设置时,您可以自定义类并继承DefaultSettingsProvider,覆写其中的getSettings方法,将您自定义的settings信息以Map返回,通过此方式可以支持所有ES能够支持的Settings
@IndexName
描述:索引名注解,标识实体类对应的索引 对应MP的@TableName注解,在v0.9.40之前此注解为@TableName
使用位置:实体类
@IndexName
public class ItemDoc {// 其它字段省略
}
属性 | 类型 | 必须指定 | 默认值 | 描述 |
value | String | 否 | "" | 索引名,可简单理解为MySQL表名 |
aliasName | String | 否 | "" | 索引别名 |
keepGlobalPrefix | boolean | 否 | false | 是否保持使用全局的 tablePrefix 的值,与MP用法一致 |
refreshPolicy | Enum | 否 | NONE | 索引数据刷新策略,默认为不刷新,其取值参考RefreshPolicy枚举类,一共有3种刷新策略 |
@IndexId
描述:ES主键注解
使用位置:实体类中被作为ES主键的字段, 对应MP的@TableId注解
public class ItemDoc {@IndexIdprivate String id;
}
属性 | 类型 | 必须指定 | 默认值 | 描述 |
type | Enum | 否 | IdType.NONE | 指定主键类型 |
注意:Id 的生成类型支持以下几种
IdType.NONE: 由ES自动生成,是默认缺省时的配置,无需您额外配置 推荐
IdType.UUID: 系统生成UUID,然后插入ES (不推荐)
IdType.CUSTOMIZE: 由用户自定义,用户自己对id值进行set,如果用户指定的id在es中不存在,则在insert时就会新增一条记录,如果用户指定的id在es中已存在记录,则自动更新该id对应的记录
@IndexField
描述:ES字段注解, 对应MP的@TableField注解
使用位置:实体类中被作为ES索引字段的字段
使用示例:
public class ItemDoc {// 场景一:标记es中不存在的字段@IndexField(exist = false)private String notExistsField;// 场景二:更新时,此字段为非空字符串才会被更新@IndexField(strategy = FieldStrategy.NOT_EMPTY)private String creator;// 场景三: 指定fieldData@IndexField(fieldType = FieldType.TEXT, fieldData = true)private String filedData;// 场景四:自定义字段名@IndexField("wu-la") private String ula;// 场景五:支持日期字段在es索引中的format类型@IndexField(fieldType = FieldType.DATE, dateFormat = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")private String gmtCreate;// 场景六:支持指定字段在es索引中的分词器类型@IndexField(fieldType = FieldType.TEXT, analyzer = Analyzer.IK_SMART, searchAnalyzer = Analyzer.IK_MAX_WORD)private String content;// 场景七:支持指定字段在es的索引中忽略大小写,以便在term查询时不区分大小写,仅对keyword类型字段生效,es的规则,并非框架限制.@IndexField(fieldType = FieldType.KEYWORD, ignoreCase = true)private String caseTest;// 场景八:支持稠密向量类型 稠密向量类型,dims必须同时指定,非负 最大为2048@IndexField(fieldType = FieldType.DENSE_VECTOR, dims = 3)private double[] vector;// 场景九:支持复制字段,复制当前字段至指定字段,支持将多个字段值复制到同一字段,与原生用法一致@IndexField(copyTo = "creator") private String copiedField;
}
属性 | 类型 | 必须指定 | 默认值 | 描述 |
value | String | 否 | "" | 字段名 |
exist | boolean | 否 | true | 字段是否存在 |
fieldType | Enum | 否 | FieldType.NONE | 字段在es索引中的类型 |
fieldData | boolean | 否 | false | text类型字段是否支持聚合 |
analyzer | String | 否 | Analyzer.NONE | 索引文档时用的分词器 |
searchAnalyzer | String | 否 | Analyzer.NONE | 查询分词器 |
strategy | Enum | 否 | FieldStrategy.DEFAULT | 字段验证策略 |
dateFormat | String | 否 | "" | es索引中的日期格式,如yyyy-MM-dd |
nestedClass | Class | 否 | DefaultNestedClass.class | 嵌套类 |
parentName | String | 否 | "" | 父子文档-父名称 |
childName | String | 否 | "" | 父子文档-子名称 |
joinFieldClass | Class | 否 | JoinField.class | 父子文档-父子类型关系字段类 |
ignoreCase | boolean | 否 | false | keyword类型字段是否忽略大小写 |
ignoreAbove | int | 否 | 256 | 字符串将被索引或存储的最大长度 |
copyTo | String[] | 否 | {} | 复制字段,可将当前字段复制到多个指定字段 |
注意:
更新策略一共有3种
NOT_NULL: 非Null判断,字段值为非Null时,才会被更新
NOT_EMPTY: 非空判断,字段值为非空字符串时才会被更新
IGNORE: 忽略判断,无论字段值为什么,都会被更新
针对BigDecimal类型字段,其scalingFactor若用户未指定,则系统默认值为100
ES索引CRUD
创建ItemDoc索引
并不需要把全部的,Item实体都加进去,我们es的索引主要是为了查询,可以存入一些页面上比较常见的属性。
@Data
@IndexName
public class ItemDoc implements Serializable {@IndexId(type = IdType.CUSTOMIZE)private String id;@IndexField(fieldType = FieldType.TEXT,analyzer = Analyzer.IK_SMART,searchAnalyzer = Analyzer.IK_MAX_WORD)private String name;@IndexField(fieldType = FieldType.INTEGER)private Integer price;@IndexField(fieldType = FieldType.INTEGER)private String stock;@IndexField(fieldType = FieldType.KEYWORD)private String image;@IndexField(fieldType = FieldType.KEYWORD)private String category;@IndexField(fieldType = FieldType.KEYWORD)private String brand;@IndexField(fieldType = FieldType.KEYWORD)private String spec;@IndexField(fieldType = FieldType.KEYWORD)private Integer sold;@IndexField(fieldType = FieldType.KEYWORD)private String commentCount;@IndexField(fieldType = FieldType.BOOLEAN)private Boolean isAD;@IndexField(fieldType = FieldType.DATE,dateFormat = "yyyy-MM-dd HH:mm:ss")private LocalDateTime createTime;@IndexField(fieldType = FieldType.DATE,dateFormat = "yyyy-MM-dd HH:mm:ss")private LocalDateTime updateTime;
}
创建ItemDocMapper
其实和mp的模式差不多。创建好 ItemDocMapper 之后,继承 BaseEsMapper
/*** @Author SDE* @Classname: ItemDocMapper* @Date 2025-03-27 17:43* @Package com.sde.mapper.es*/
public interface ItemDocMapper extends BaseEsMapper<ItemDoc> {
}
测试创建索引
我编写了一个测试类,来创建es索引。
@SpringBootTest
public class EsTest {@Autowiredprivate ItemDocMapper itemDocMapper;@Test@DisplayName("创建Es索引")void testCreateEsIndex(){Boolean aBoolean = itemDocMapper.createIndex();System.out.println("创建索引是否成功:" + aBoolean);}}
看结果,索引创建成功了。
在kibana查看
我们一般会在 kibana 的控制台来查看
访问地址:你的 IP + 端口
****
使用 Easy-Es 创建索引的时候,我们会在类名的上面添加 @IndexName 来指定索引名称,如果只是加了注解没有指定名字,默认就是你的类名小写。
因为我的是 ItemDoc ,所以我的索引名是 itemdoc
# 查看索引
GET /itemdoc
在执行一次看看会出现什么?
索引已经存在了,在创建就报错了。
测试删除索引
@Test@DisplayName("删除Es索引")void testDeleteEsIndex(){//要删除那个索引String indexName = ItemDoc.class.getSimpleName().toLowerCase();System.out.println("要删除的索引:" + indexName);Boolean aBoolean = itemDocMapper.deleteIndex(indexName);System.out.println("删除索引是否成功:" + aBoolean);}
看控制台效果
测试查询索引
索引是否存在
@Test@DisplayName("测试索引是否存在")void testExistIndex(){String indexName = ItemDoc.class.getSimpleName().toLowerCase();Boolean aBoolean = itemDocMapper.existsIndex(indexName);System.out.println("索引是否存在:" + aBoolean);}
我先执行一下删除索引的操作,再来看下测试结果。可以看到这次的就是false了
当前mapper对应的索引信息
@Test@DisplayName("获取当前mapper对应的索引信息")void testMapperIndexInfo(){GetIndexResponse indexResponse = itemDocMapper.getIndex();indexResponse.getMappings().forEach((k, v) -> System.out.println(v.getSourceAsMap()));}
详细结果
{properties={image={type=keyword},
sold={type=keyword},
createTime={format=yyyy-MM-dd HH:mm:ss, type=date},
price={type=integer},
name={search_analyzer=ik_max_word,
analyzer=ik_smart, type=text},
updateTime={format=yyyy-MM-dd HH:mm:ss, type=date},
category={type=keyword},
isAD={type=boolean},
stock={type=integer},
brand={type=keyword},
spec={type=keyword},
commentCount={type=keyword}}}
测试更新索引
刷新获取索引分片数
@Test@DisplayName("测试索引更新")void testUpdateIndex(){Integer refresh = itemDocMapper.refresh();System.out.println("返回刷新成功分片数:" + refresh);}
控制台结果
更新索引结构
es索引创建好了之后,不能直接更新某个索引字段,我们测试的更新就是,新增加一个es索引字段。
我现在 ItemDoc中新增了一个属性
// 新增一个字段private Long creater;
@Test@DisplayName("测试索引更新索引结构")void UpdateIndex(){LambdaEsIndexWrapper<ItemDoc> esWrapper = new LambdaEsIndexWrapper<>();String indexName = ItemDoc.class.getSimpleName().toLowerCase();//指定更新那个索引esWrapper.indexName(indexName);esWrapper.mapping(ItemDoc::getCreater, FieldType.LONG);//更新索引Boolean aBoolean = itemDocMapper.updateIndex(esWrapper);System.out.println("更新索引是否成功:" + aBoolean);}
控制台结果
在kibana查看下
Es文档CRUD
新增单个es文档
上面已经完成了使用easy-es,快速的完成对 es索引的 crud操作。下面就开始往里面存储数据了。
@Test@DisplayName("向es索引种新增文档")void testCreateDoc(){//根据id查询一条数据库里面的数据Item item = itemMapper.selectById("317578L");//转换成itemDoc对象ItemDoc itemDoc = BeanUtil.toBean(item, ItemDoc.class);//向索引中新增一条数据Integer rows = itemDocMapper.insert(itemDoc);System.out.println("新增文档成功,受影响的行数:" + rows);}
在kibana看下
# 查看文档
GET /itemdoc/_doc/317578
批量新增es文档
可以一次新增多条数据到es索引中
@Test@DisplayName("批量新增es文档")void testBatchInsertDoc(){//查询数据库中类目是硬盘的商品数据LambdaQueryWrapper<Item> wrapper = new LambdaQueryWrapper<>();wrapper.eq(Item::getCategory, "硬盘");List<Item> itemList = itemMapper.selectList(wrapper);System.out.println("查询到类目为 硬盘的数据量:" + itemList.size() + "条");//批量转换为itemDoc对象List<ItemDoc> itemDocList = BeanUtil.copyToList(itemList, ItemDoc.class);//批量新增es文档Integer rows = itemDocMapper.insertBatch(itemDocList);System.out.println("向es索引中新增了 " + rows + " 条数据");}
查看下控制台
在kibana中查看下
删除单个es文档
可以新增es文档,当然我们也就可以删除es文档。
@Test@DisplayName("根据id删除es索引中的文档")void testDeleteDoc(){Integer rows = itemDocMapper.deleteById("317578");System.out.println(rows > 0 ? "删除成功 删除了" + rows +"条" : "删除失败");}
控制台
在kibana查询下
批量删除es文档
我们可以一个一个的删除索引中的文档,当然也可以进行删除多个的操作,就是批量删除。
@Test@DisplayName("批量删除es索引中的文档")void testBatchDeleteDoc(){//查询数据库中类目是硬盘的商品数据,只查询100条IPage<Item> page = new Page<>(1, 100);LambdaQueryWrapper<Item> wrapper = new LambdaQueryWrapper<>();wrapper.eq(Item::getCategory, "硬盘");IPage<Item> itemIPage = itemMapper.selectPage(page, wrapper);System.out.println("查询到类目为 硬盘的数据量:" + itemIPage.getSize() + "条");//提取里面的id,转换成集合。List<Long> itemIdList = itemIPage.getRecords().stream().map(Item::getId).collect(Collectors.toList());//批量删除Integer rows = itemDocMapper.deleteBatchIds(itemIdList);System.out.println("删除了 " + rows + " 条数据");}
控制台
kibana中查看,现在数据为 198条了。
查询单个es文档
@Test@DisplayName("根据id查询es索引中的文档")void testSelectDocById(){ItemDoc itemDoc = itemDocMapper.selectById("317578");System.out.println(itemDoc);}
批量查询es文档
所谓的批量查询其实就是按照条件查询,这俩我就演示一种,后面根据 LambdaEsQueryWrapper 构造条件查询的时候,在过多的说下。
@Test@DisplayName("批量查询es文档")void testBatchSelectDoc(){//查询数据库中类目是硬盘的商品数据,只查询100条IPage<Item> page = new Page<>(1, 100);LambdaQueryWrapper<Item> wrapper = new LambdaQueryWrapper<>();wrapper.eq(Item::getCategory, "硬盘");IPage<Item> itemIPage = itemMapper.selectPage(page, wrapper);System.out.println("查询到类目为 硬盘的数据量:" + itemIPage.getSize() + "条");//转换成id集合List<Long> idList = itemIPage.getRecords().stream().map(Item::getId).collect(Collectors.toList());//批量查询List<ItemDoc> itemDocList = itemDocMapper.selectBatchIds(idList);System.out.println("查询到es索引中文档的数据量:" + itemDocList.size());}
控制台
修改单个es文档
@Test@DisplayName("根据id更新es索引中的文档")void testUpdateDoc(){//查询现有的数据ItemDoc itemDoc = itemDocMapper.selectById("317578");//在现有数据基础上更改一些值itemDoc.setBrand("SDE品牌");itemDoc.setCreateTime(LocalDateTime.now());//发送更新请求Integer rows = itemDocMapper.updateById(itemDoc);System.out.println(rows > 0 ? "更新成功" : "更新失败");}
在kibana控制台查看
批量修改es文档
@Test@DisplayName("批量修改es文档")void testBatchUpdateDoc(){//查询数据库中类目是硬盘的商品数据,只查询100条IPage<Item> page = new Page<>(1, 100);LambdaQueryWrapper<Item> wrapper = new LambdaQueryWrapper<>();wrapper.eq(Item::getCategory, "硬盘");IPage<Item> itemIPage = itemMapper.selectPage(page, wrapper);System.out.println("查询到类目为 硬盘的数据量:" + itemIPage.getSize() + "条");List<Item> itemList = itemIPage.getRecords();//这里把这100条数据的name从 0 加到了 99for (int i = 0; i < itemIPage.getSize(); i++) {itemList.get(i).setName(itemList.get(i).getName()+">=" + i);}//转换成itemDoc对象List<ItemDoc> itemDocList = BeanUtil.copyToList(itemList, ItemDoc.class);//批量修改Integer rows = itemDocMapper.updateBatchByIds(itemDocList);System.out.println("修改了 " + rows + " 条数据");}
控制台效果
在kibana中查看下
数据同步
就比如我们在进行数据迁移的时候,想把MySQL数据库里面的数据,同步到es中。但是MySQL数据库中的数据也有很多,我们不能一次insert太多数据吧,就要分批次的同步数据到es中。
@Test@DisplayName("分批次同步数据")void testBatchSyncData(){Integer pageNum = 1;Integer pageSize = 2000;Integer total = 0;//构建分页条件IPage<Item> page = new Page<>(pageNum, pageSize);long startTime = System.currentTimeMillis();while (true){//分页查询IPage<Item> itemIPage = itemMapper.selectPage(page, null);//如果查询不到数据就终止循环if (CollUtil.isEmpty(itemIPage.getRecords())){break;}//转换成itemDoc对象List<ItemDoc> itemDocList = BeanUtil.copyToList(itemIPage.getRecords(), ItemDoc.class);//批量新增es文档itemDocMapper.insertBatch(itemDocList);total += itemIPage.getRecords().size();//下一页pageNum++;//更新Page对象的页码page.setCurrent(pageNum);}long endTime = System.currentTimeMillis();System.out.println("数据同步到Es索引成功!同步了 " + total + " 条数据。" + "执行耗时:" + (endTime - startTime) / 1000 + "秒" );}
控制台查看下
在kibana中查看下
查询条件构造器
LambdaEsQueryWrapper 此条件构造器主要用于查询时对条件的封装,其使用和mp类似。
allEq
顾名思义就是要符合全部的条件
方法说明
allEq(Map<R, V> params)
params:key为数据库(es索引字段)字段名,value为字段值。
@Test@DisplayName("测试查询条件的allEq")public void testAllEq() {// 查询类目(category)是牛奶 并且品牌(brand)是德亚 的商品Map<String, Object> params = new HashMap<>();params.put("category", "牛奶");params.put("brand", "欧德堡");//构建查询条件LambdaEsQueryWrapper<ItemDoc> esWrapper = new LambdaEsQueryWrapper<>();esWrapper.allEq(params);//查询数据List<ItemDoc> itemDocList = itemDocMapper.selectList(esWrapper);System.out.println("类名是:" + params.get("category") + "品牌是:" + params.get("brand") +"的商品,查询到了 " + itemDocList.size() + " 条数据");}
控制台输出
kibana控制台
# 查询类目是牛奶,品牌是 欧德堡 德商品信息
GET /itemdoc/_search
{"query": {"bool": {"must": [{"term": {"category": {"value": "牛奶"}}},{"term": {"brand": {"value": "欧德堡"}}}]}}
}
Eq
等于 =
例: eq("name","sde") ---> name = 'sde'
语法
eq(R column, Object val)
eq(boolean condition, R column, Object val)
示例
@Test@DisplayName("测试查询条件的eq")public void testEq() {//查询类目是牛奶 并且品牌是德亚 的商品//构建查询条件LambdaEsQueryWrapper<ItemDoc> esWrapper = new LambdaEsQueryWrapper<>();esWrapper.eq(ItemDoc::getCategory,"牛奶");esWrapper.eq(ItemDoc::getBrand,"德亚");//查询List<ItemDoc> itemDocList = itemDocMapper.selectList(esWrapper);System.out.println("类名是:牛奶 品牌是:德亚 的商品,查询到了 " + itemDocList.size() + " 条数据");}
控制台
kibana
# 查询类目是牛奶,品牌是 德亚 德商品信息
GET /itemdoc/_search
{"query": {"bool": {"must": [{"term": {"category": {"value": "牛奶"}}},{"term": {"brand": {"value": "德亚"}}}]}}
}
gt
大于 >
例:gt("age",18) ---> age > 18
语法
gt(R column, Object val)
gt(boolean condition, R column, Object val)
示例
@Test@DisplayName("测试查询条件的gt")public void testGt() {//查询类目是 拉杆箱 品牌是 莎米特 价格 大于 200元的 商品//构建查询条件LambdaEsQueryWrapper<ItemDoc> esWrapper = new LambdaEsQueryWrapper<>();esWrapper.eq(ItemDoc::getCategory,"拉杆箱").eq(ItemDoc::getBrand,"莎米特").gt(ItemDoc::getPrice,20000); //因为存储在es的单位是 (分),所以需要转换一下 就是 2万分//执行查询List<ItemDoc> itemDocList = itemDocMapper.selectList(esWrapper);System.out.println("类名是:拉杆箱 品牌是:莎米特 价格 大于 200元的 商品,查询到了 " + itemDocList.size() + " 条数据");}
控制台
kibana控制台
# 查询类目是拉杆箱,品牌是 莎米特价格大于 200元 德商品信息
GET /itemdoc/_search
{"query": {"bool": {"must": [{"term": {"category": {"value": "拉杆箱"}}},{"term": {"brand": {"value": "莎米特"}}},{"range": {"price": {"gt": 20000}}}]}}
}
ge
大于等于 >=
例:ge("age",18) ---> age >= 18
语法
gt(R column, Object val)
gt(boolean condition, R column, Object val)
示例
@Test@DisplayName("测试查询条件的ge")public void testGe() {//查询类目是 拉杆箱 品牌是 莎米特 价格 大于等于 140元的 商品//构建查询条件LambdaEsQueryWrapper<ItemDoc> esWrapper = new LambdaEsQueryWrapper<>();esWrapper.eq(ItemDoc::getCategory,"拉杆箱").eq(ItemDoc::getBrand,"莎米特").ge(ItemDoc::getPrice,14000);//执行查询List<ItemDoc> itemDocList = itemDocMapper.selectList(esWrapper);System.out.println("类名是:拉杆箱 品牌是:莎米特 价格 大于等于 140元的 商品,查询到了 " + itemDocList.size() + " 条数据");}
控制台
kibana
# 查询类目是拉杆箱,品牌是 莎米特价格大于 140元 德商品信息
GET /itemdoc/_search
{"query": {"bool": {"must": [{"term": {"category": {"value": "拉杆箱"}}},{"term": {"brand": {"value": "莎米特"}}},{"range": {"price": {"gte": 14000}}}]}}
}
同样也是查到了 211 条数据
lt
小于 <
例:lt("age",18) ---> age < 18
语法
lt(R column, Object val)
lt(boolean condition, R column, Object val)
示例
@Test@DisplayName("测试查询条件的lt")public void testLt() {//查询类目是手机 品牌是 Apple 价格小于 5000元的 商品//构建查询条件LambdaEsQueryWrapper<ItemDoc> esWrapper = new LambdaEsQueryWrapper<>();Map<String, Object> params = new HashMap<>();params.put("category","手机");params.put("brand","Apple");esWrapper.allEq(params).lt(ItemDoc::getPrice,500000);//执行查询List<ItemDoc> itemDocList = itemDocMapper.selectList(esWrapper);System.out.println("类目名是:"+params.get("category")+" 品牌是:"+params.get("brand")+" 价格小于 5000元的 商品,查询到了 " + itemDocList.size() + " 条数据");}
控制台
kibana
# 查询类目是手机,品牌是 Apple 价格小于 5000元 的商品信息
GET /itemdoc/_search
{"query": {"bool": {"must": [{"term": {"category": {"value": "手机"}}},{"term": {"brand": {"value": "Apple"}}},{"range": {"price": {"lt": 500000}}}]}}
}
le
小于等于 <=
例:le("age",18) ---> age <= 18
语法
le(R column, Object val)
le(boolean condition, R column, Object val)
示例
@Test@DisplayName("测试查询条件的le")public void testLe() {// 查询类目是手机 品牌是 Apple 价格小于等于 6531元的 商品LambdaEsQueryWrapper<ItemDoc> esWrapper = new LambdaEsQueryWrapper<>();Map<String, Object> params = new HashMap<>();params.put("category","手机");params.put("brand","Apple");esWrapper.allEq(params).le(ItemDoc::getPrice,653100);//执行查询List<ItemDoc> itemDocList = itemDocMapper.selectList(esWrapper);System.out.println("类目名是:"+params.get("category")+" 品牌是:"+params.get("brand")+" 价格小于等于 6531元的 商品,查询到了 " + itemDocList.size() + " 条数据");}
控制台
kibana
# 查询类目是手机,品牌是 Apple 价格小于 628900元 的商品信息
GET /itemdoc/_search
{"query": {"bool": {"must": [{"term": {"category": {"value": "手机"}}},{"term": {"brand": {"value": "Apple"}}},{"range": {"price": {"lte": 628900}}}]}}
}
between
between 值1 and 值2
例:between("age",18,28) ---> age between 18 and 28
语法
between(R column, Object val1, Object val2)
between(boolean condition, R column, Object val1, Object val2)
示例
@Test
@DisplayName("测试查询条件的between")
public void testBetween() {//查询类目是 牛奶 品牌是欧德堡 价格在 30 到 300 之间的商品//构建查询条件LambdaEsQueryWrapper<ItemDoc> esWrapper = new LambdaEsQueryWrapper<>();Map<String, Object> params = new HashMap<>();params.put("category","牛奶");params.put("brand","欧德堡");esWrapper.allEq(params).between(ItemDoc::getPrice,4000,30000);//执行查询List<ItemDoc> itemDocList = itemDocMapper.selectList(esWrapper);System.out.println("类目名是:"+params.get("category")+" 品牌是:"+params.get("brand")+" 价格在 50 到 100 之间的商品,查询到了 " + itemDocList.size() + " 条数据");
}
控制台
kibana
# 查询类目是牛奶,品牌是 欧德堡 价格在 40 到 300元 的商品信息
GET /itemdoc/_search
{"query": {"bool": {"must": [{"term": {"category": {"value": "牛奶"}}},{"term": {"brand": {"value": "欧德堡"}}},{"range": {"price": {"gte": 4000,"lte": 30000}}}]}}
}
like
like '%值%'
例:like("name","德亚") ---> name like '%德亚%'
语法
between(R column, Object val1, Object val2)
between(boolean condition, R column, Object val1, Object val2)
示例
@Test@DisplayName("测试查询条件的like")public void testLike() {//查询类目是 牛奶 品牌是 德亚 ,sku名称中包含 脱脂 的商品//构建查询条件LambdaEsQueryWrapper<ItemDoc> esWrapper = new LambdaEsQueryWrapper<>();Map<String, Object> params = new HashMap<>();params.put("category","牛奶");params.put("brand","德亚");esWrapper.allEq(params).like(ItemDoc::getName,"脱脂");//执行查询List<ItemDoc> itemDocList = itemDocMapper.selectList(esWrapper);System.out.println("类目名是:"+params.get("category")+" 品牌是:"+params.get("brand")+" sku名称中包含 脱脂 的商品,查询到了 " + itemDocList.size() + " 条数据");}
控制台
kibana
# 查询类目是牛奶,品牌是 亚德 sku名称包含 脱脂 的商品信息
GET /itemdoc/_search
{"query": {"bool": {"must": [{"term": {"category": {"value": "牛奶"}}},{"term": {"brand": {"value": "德亚"}}},{"match": {"name": "脱脂"} }]}}
}
likeLeft
like '%值'
例:likeLeft("name","减") ---> name like "%减"
语法
likeLeft(R column, Object val)
likeLeft(boolean condition, R column, Object val)
示例
@Test
@DisplayName("测试查询条件的likeLeft")
public void testLikeLeft() {//查询类目是 牛奶 品牌是 德亚 ,sku名称中以 减 开头的商品//构建查询条件LambdaEsQueryWrapper<ItemDoc> esWrapper = new LambdaEsQueryWrapper<>();Map<String, Object> params = new HashMap<>();params.put("category","牛奶");params.put("brand","德亚");esWrapper.allEq(params).likeLeft(ItemDoc::getName,"减");//执行查询List<ItemDoc> itemDocList = itemDocMapper.selectList(esWrapper);System.out.println("类目名是:"+params.get("category")+" 品牌是:"+params.get("brand")+" sku名称中以 减 开头的商品,查询到了 " + itemDocList.size() + " 条数据");
}
控制台
kibana
# 查询类目是牛奶,品牌是 亚德 sku名称以 减 开头的商品信息
GET /itemdoc/_search
{"query": {"bool": {"must": [{"term": {"category": {"value": "牛奶"}}},{"term": {"brand": {"value": "德亚"}}},{"prefix": {"name": "减"} }]}}
}
likeRight
like '值%'
例:likeRight("name","低")
语法
likeRight(R column, Object val)
likeRight(boolean condition, R column, Object val)
示例
@Test@DisplayName("测试查询条件的likeRight")public void testLikeRight() {//查询类目是 牛奶 品牌是 德亚 ,sku名称中以 低 结尾的商品//构建查询条件LambdaEsQueryWrapper<ItemDoc> esWrapper = new LambdaEsQueryWrapper<>();Map<String, Object> params = new HashMap<>();params.put("category","牛奶");params.put("brand","德亚");esWrapper.allEq(params).likeRight(ItemDoc::getName,"低");//执行查询List<ItemDoc> itemDocList = itemDocMapper.selectList(esWrapper);System.out.println("类目名是:"+params.get("category")+" 品牌是:"+params.get("brand")+" sku名称中以 低 结尾的商品,查询到了 " + itemDocList.size() + " 条数据");}
控制台
kibana
# 查询类目是牛奶,品牌是 亚德 sku名称以 低 结尾的商品信息
GET /itemdoc/_search
{"query": {"bool": {"must": [{"term": {"category": {"value": "牛奶"}}},{"term": {"brand": {"value": "德亚"}}},{"regexp": {"name": "低"} }]}}
}
isNotNull
字段 is not null 就是字段的值 不为 null
例:isNotNull("ItemDoc::getName") ---> name is not null
语法
isNotNull(R column)
isNotNull(boolean condition, R column)
示例
@Test@DisplayName("测试查询条件的is not null")public void testIsNotNull() {//查询类目是 牛奶 品牌是 德亚 商品名称 不为null的商品信息//构建查询条件LambdaEsQueryWrapper<ItemDoc> esWrapper = new LambdaEsQueryWrapper<>();Map<String, Object> params = new HashMap<>();params.put("category","牛奶");params.put("brand","德亚");esWrapper.allEq(params).isNotNull(ItemDoc::getName);//执行查询List<ItemDoc> itemDocList = itemDocMapper.selectList(esWrapper);System.out.println("类目名是:"+params.get("category")+" 品牌是:"+params.get("brand")+" 商品名称 不为null的商品信息,查询到了 " + itemDocList.size() + " 条数据");}
控制台
kibana
# 查询类目是牛奶,品牌是 亚德 商品名称 不为null的商品信息
GET /itemdoc/_search
{"query": {"bool": {"must": [{"term": {"category": {"value": "牛奶"}}},{"term": {"brand": {"value": "德亚"}}},{"exists": {"field": "name"} }]}}
}
exists
功能和 is not null 一样
语法
exists(R column)
exists(boolean condition, R column)
示例
@Test@DisplayName("测试查询条件的exists")public void testExists() {//查询类目是 牛奶 品牌是 德亚 商品名称 不为null的商品信息//构建查询条件LambdaEsQueryWrapper<ItemDoc> esWrapper = new LambdaEsQueryWrapper<>();Map<String, Object> params = new HashMap<>();params.put("category","牛奶");params.put("brand","德亚");esWrapper.allEq(params).exists("name");//执行查询List<ItemDoc> itemDocList = itemDocMapper.selectList(esWrapper);System.out.println("类目名是:"+params.get("category")+" 品牌是:"+params.get("brand")+" 商品名称 不为null的商品信息,查询到了 " + itemDocList.size() + " 条数据");}
控制台
kibana
# 查询类目是牛奶,品牌是 亚德 商品名称 不为null的商品信息
GET /itemdoc/_search
{"query": {"bool": {"must": [{"term": {"category": {"value": "牛奶"}}},{"term": {"brand": {"value": "德亚"}}},{"exists": {"field": "name"} }]}}
}
in
字段 in (value.get(0),value.get(1),...)
例:in("age",1,2,3) ---> age in(1,2,3)
语法
in(R column, Collection<?> value)
in(boolean condition, R column, Collection<?> value)
示例
@Test@DisplayName("测试查询条件的in")public void testIn() {//查询类目是 拉杆箱 品牌是 旅行之家 和 宾豪 的商品信息//构建查询条件LambdaEsQueryWrapper<ItemDoc> esWrapper = new LambdaEsQueryWrapper<>();esWrapper.eq(ItemDoc::getCategory,"拉杆箱").in(ItemDoc::getBrand,"旅行之家","宾豪");//执行查询List<ItemDoc> itemDocList = itemDocMapper.selectList(esWrapper);System.out.println("类目是 拉杆箱 品牌是 旅行之家 和 宾豪 的商品信息,查询到了 " + itemDocList.size() + " 条数据");}
控制台
kibana
# 查询类目是拉杆箱,品牌是 旅行之家 和 宾豪 的商品信息
GET /itemdoc/_search
{"query": {"bool": {"must": [{"term": {"category": "拉杆箱"}},{"terms": {"brand": ["旅行之家","宾豪"]}}]}}
}
groupBy
分组:group by 字段、...
例:groupBy(ItemDoc::getCategory,ItemDoc::getBrand) ---> group by category,brand
@Test
@DisplayName("测试查询条件的group by")
public void testGroupBy() {// 查询类目是 拉杆箱,品牌是 旅行之家 和 宾豪 的商品信息,根据品牌分组// 构建查询条件LambdaEsQueryWrapper<ItemDoc> esWrapper = new LambdaEsQueryWrapper<>();esWrapper.eq(ItemDoc::getCategory,"拉杆箱").in(ItemDoc::getBrand,"旅行之家","宾豪").groupBy(ItemDoc::getBrand);// 执行查询Long count = itemDocMapper.selectCount(esWrapper);System.out.println("根据品牌分组,查询到了 " + count + " 条数据");
}
控制台
kibana
# 查询类目是拉杆箱,品牌是 旅行之家 和 宾豪 根据品牌分组
GET /itemdoc/_search
{"query": {"bool": {"must": [{"term": {"category": "拉杆箱"}},{"terms": {"brand": ["旅行之家","宾豪"]}}]}},"aggs": {"group_by_brand": {"terms": {"field": "brand"}}},"size": 0
}