以springboot集成easyes为例,进行说明
1.easyes介绍
- Easy-Es 是一款基于 Elasticsearch 官方客户端(RestHighLevelClient)封装的 ORM 框架,其设计理念与 MyBatis-Plus 相似,旨在简化开发流程
- 示例,用简洁的语法,解决传统DSL手动拼接json的问题,以及提高代码的可读性,可维护性高,支持自动创建索引,方便索引管理等
EsWrappers.lambdaQuery(Entity.class) .eq(Entity::getTitle, "程序员") .between(Entity::getTime, start, end);
原始dsl举例:
{
"query": {
"bool": {
"must": [
{ "match": { "title": "Elasticsearch" } },
{ "range": { "price": { "gte": 10 } } }
],
"filter": [
{ "term": { "status": "active" } }
]
}
}
}
1.1语法介绍
基础 CRUD 语法
// 插入文档
Entity entity = new Entity();
entity.setTitle(" 程序员");
entity.setContent("Java 开发指南");
entityMapper.insert(entity);
// 根据ID查询
Entity result = entityMapper.selectById("1");
// 批量更新
List<Entity> entities = entityMapper.selectList(...);
entities.forEach(e -> e.setStatus(1));
entityMapper.updateBatchById(entities);
// 删除文档
entityMapper.deleteById("1");
条件构造器语法(LambdaEsQueryWrapper)
// 1. 等值查询 & 范围查询
LambdaEsQueryWrapper<Entity> wrapper = new LambdaEsQueryWrapper<>();
wrapper.eq(Entity::getTitle, "程序员")
.between(Entity::getCreateTime, "2023-01-01", "2023-12-31");
// 2. 模糊查询 & 排序
wrapper.like(Entity::getContent, "开发")
.orderByDesc(Entity::getReadCount);
// 3. 多条件组合(OR逻辑)
wrapper.and(w -> w.eq(Entity::getCategory, "技术")
.or()
.eq(Entity::getCategory, "编程"));
List<Entity> list = entityMapper.selectList(wrapper);
高级功能语法
1. 高亮查询
LambdaEsQueryWrapper<Entity> wrapper = new LambdaEsQueryWrapper<>();
wrapper.match(Entity::getContent, "Java")
.highlight(Entity::getContent, "<em>", "</em>"); // 高亮标签定义
List<Entity> results = entityMapper.selectList(wrapper);
// 结果中高亮内容会自动填充到实体类的@HighLight注解字段
2. 聚合统计
LambdaEsQueryWrapper<Entity> wrapper = new LambdaEsQueryWrapper<>();
wrapper.termsAggregation("category_agg", Entity::getCategory) // 按分类聚合
.metricAvg("read_avg", Entity::getReadCount); // 计算平均阅读量
SearchResponse response = entityMapper.search(wrapper);
// 从response中解析聚合结果
3. 地理位置查询(Geo)
// 查询距离某坐标3公里内的数据
LambdaEsQueryWrapper<Entity> wrapper = new LambdaEsQueryWrapper<>();
wrapper.geoDistance(Entity::getLocation, "40.12,116.50", 3, DistanceUnit.KILOMETERS);
// 支持多种坐标格式:
// 1. 字符串:"lat,lon"
// 2. 数组:new Double[]{116.50, 40.12}
// 3. GeoPoint对象:new GeoPoint(40.12, 116.50)
分页语法
// 1. 普通分页
Page<Entity> page = new Page<>(1, 10); // 第1页,每页10条
Page<Entity> result = entityMapper.selectPage(page, wrapper);
// 2. 优化分页(避免深分页性能问题)
Page<Entity> optimizePage = new Page<>(1, 10, true);
索引管理语法
// 自动创建索引(根据实体类注解)
@IndexName(value = "article_index", keepGlobalPrefix = true)
public class Entity {
@IndexField(type = FieldType.KEYWORD)
private String title;
// 其他字段...
}
// 手动同步索引(数据迁移)
boolean success = entityMapper.synchronizedIndex("new_index_name");
嵌套查询语法
// 嵌套对象查询(假设Entity中有User类嵌套字段)
LambdaEsQueryWrapper<Entity> wrapper = new LambdaEsQueryWrapper<>();
wrapper.nested(e -> e.eq(Entity::getUser, User::getName, "张三")
.gt(Entity::getUser, User::getAge, 25));
官方文档:
/**
* 场景一: 嵌套and的使用
*/
@Test
public void testNestedAnd() {
// 下面查询条件等价于MySQL中的 select * from document where star_num in (1, 2) and (title = 'work' or title = '干活')
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
wrapper.in(Document::getStarNum, 1, 2)
.and(w -> w.eq(Document::getTitle, "work").or().eq(Document::getTitle, "干活"));
List<Document> documents = documentMapper.selectList(wrapper);
}
/**
* 场景二: 拼接and的使用
*/
@Test
public void testAnd(){
// 下面查询条件等价于MySQL中的 select * from document where title = '工作' and content like 'work'
// 拼接and比较特殊,因为使用场景最多,所以条件与条件之间默认就是拼接and,所以可以直接省略,这点和MP是一样的
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
wrapper.eq(Document::getTitle, "干活")
.match(Document::getContent, "work");
List<Document> documents = documentMapper.selectList(wrapper);
}
/**
* 场景二: 嵌套or的使用
*/
@Test
public void testNestedOr() {
// 下面查询条件等价于MySQL中的 select * from document where star_num = 1 or (title = '工作' and creator = 'hj')
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
wrapper.eq(Document::getStarNum, 1)
.or(i -> i.eq(Document::getTitle, "工作").eq(Document::getCreator, "hj"));
List<Document> documents = documentMapper.selectList(wrapper);
}
/**
* 场景三: 拼接or的使用
*/
@Test
public void testOr() {
// 下面查询条件等价于MySQL中的 select * from document where title = '工作' or title = 'work'
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
wrapper.eq(Document::getTitle, "工作")
.or()
.eq(Document::getTitle, "work");
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 = 'work')
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
wrapper.in(Document::getStarNum, 1, 2)
.filter(w -> w.eq(Document::getTitle, "工作").or().eq(Document::getTitle, "work"));
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);
}
other
增加:
// 插入一条记录,默认插入至当前mapper对应的索引
Integer insert(T entity);
// 插入一条记录 可指定具体插入的路由
Integer insert(String routing, T entity);
// 父子类型 插入一条记录 可指定路由, 父id
Integer insert(String routing, String parentId, T entity);
// 插入数据 可指定具体插入的索引,多个用逗号隔开
Integer insert(T entity, String... indexNames);
// 插入数据,可指定路由及多索引插入
Integer insert(String routing, T entity, String... indexNames);
// 父子类型 插入数据,可指定路由,父id及多索引插入
Integer insert(String routing, String parentId, T entity, String... indexNames);
// 批量插入多条记录
Integer insertBatch(Collection<T> entityList)
// 批量插入 可指定路由
Integer insertBatch(String routing, Collection<T> entityList);
// 父子类型 批量插入 可指定路由, 父id
Integer insertBatch(String routing, String parentId, Collection<T> entityList);
// 批量插入多条记录 可指定具体插入的索引,多个用逗号隔开
Integer insertBatch(Collection<T> entityList, String... indexNames);
// 批量插入 可指定路由及多索引
Integer insertBatch(String routing, Collection<T> entityList, String... indexNames);
// 父子类型 批量插入 可指定路由,父id及多索引
Integer insertBatch(String routing, String parentId, Collection<T> entityList, String... indexNames);
删除:
// 根据 ID 删除
Integer deleteById(Serializable id);
// 根据 ID 删除 可指定路由
Integer deleteById(String routing, Serializable id);
// 根据 ID 删除 可指定具体的索引,多个用逗号隔开
Integer deleteById(Serializable id, String... indexNames);
// 根据 ID 删除 可指定路由及多索引
Integer deleteById(String routing, Serializable id, String... indexNames);
// 根据 entity 条件,删除记录
Integer delete(LambdaEsQueryWrapper<T> wrapper);
// 删除(根据ID 批量删除)
Integer deleteBatchIds(Collection<? extends Serializable> idList);
// 删除(根据ID 批量删除)可指定路由
Integer deleteBatchIds(String routing, Collection<? extends Serializable> idList);
// 删除(根据ID 批量删除)可指定具体的索引,多个用逗号隔开
Integer deleteBatchIds(Collection<? extends Serializable> idList, String... indexNames);
// 删除(根据ID 批量删除) 可指定路由及多索引
Integer deleteBatchIds(String routing, Collection<? extends Serializable> idList, String... indexNames);
修改:
//根据 ID 更新
Integer updateById(T entity);
// 根据 ID 更新 可指定路由
Integer updateById(String routing, T entity);
// 根据 ID 更新 可指定具体的索引,多个用逗号隔开
Integer updateById(T entity, String... indexNames);
// 根据 ID 更新 可指定路由和多索引
Integer updateById(String routing, T entity, String... indexNames);
// 根据ID 批量更新
Integer updateBatchByIds(Collection<T> entityList);
// 根据ID 批量更新 可指定路由
Integer updateBatchByIds(String routing, Collection<T> entityList);
//根据 ID 批量更新 可指定具体的索引,多个用逗号隔开
Integer updateBatchByIds(Collection<T> entityList, String... indexNames);
// 根据ID 批量更新 可指定路由及多索引
Integer updateBatchByIds(String routing, Collection<T> entityList, String... indexNames);
// 根据动态条件 更新记录
Integer update(T entity, LambdaEsUpdateWrapper<T> updateWrapper);
查询:
// 获取总数
Long selectCount(LambdaEsQueryWrapper<T> wrapper);
// 获取总数 distinct为是否去重 若为ture则必须在wrapper中指定去重字段
Long selectCount(Wrapper<T> wrapper, boolean distinct);
// 根据 ID 查询
T selectById(Serializable id);
// 根据 ID 查询 可指定路由
T selectById(String routing, Serializable id);
// 根据 ID 查询 可指定具体的索引,多个用逗号隔开
T selectById(Serializable id, String... indexNames);
// 根据 ID 查询 可指定路由及多索引
T selectById(String routing, Serializable id, String... indexNames);
// 查询(根据ID 批量查询)
List<T> selectBatchIds(Collection<? extends Serializable> idList);
// 查询(根据ID 批量查询) 可指定路由
List<T> selectBatchIds(String routing, Collection<? extends Serializable> idList);
// 查询(根据ID 批量查询)可指定具体的索引,多个用逗号隔开
List<T> selectBatchIds(Collection<? extends Serializable> idList, String... indexNames);
// 查询(根据ID 批量查询) 可指定路由及多索引
List<T> selectBatchIds(String routing, Collection<? extends Serializable> idList, String... indexNames);
// 根据动态查询条件,查询一条记录 若存在多条记录 会报错
T selectOne(LambdaEsQueryWrapper<T> wrapper);
// 根据动态查询条件,查询全部记录
List<T> selectList(LambdaEsQueryWrapper<T> wrapper);
2.测试项目版本
jdk 17
springBoot 3.2.4 --为避免兼容问题,尽量和es的version对应
elasticSerch 7.6.2 和ik版本一致
easyes 2.0.0
3.代码文件
- pom依赖
<dependencies>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.6.2</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.6.2</version>
</dependency>
<dependency>
<groupId>org.dromara.easy-es</groupId>
<artifactId>easy-es-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
- yml
server:
port: 8080
spring:
profiles:
active: dev
main:
allow-circular-references: true
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
#连接数据库的用户名
url: jdbc:mysql://localhost:3306/document?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
username: root
password: 123456
easy-es:
enable: true #默认为true,若为false则认为不启用本框架
address : localhost:9201 # es的连接地址,必须含端口 若为集群,则可以用逗号隔开 例如:127.0.0.1:9200,127.0.0.2:9200
logging:
level:
tracer: trace # 设置日志级别为trace,开发时可开启以打印ES全部请求信息及DSL语句
- 启动类
@SpringBootApplication
@EsMapperScan("com.example.easyes.mapper")
public class EasyesApplication {
public static void main(String[] args) {
SpringApplication.run(EasyesApplication.class, args);
}
}
- 实体类(项目用了lombok插件)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@IndexName(value = "document")
public class Document {
/**
* es对应主键id标识,自定义es中的id为我提供的id
*/ @TableId
@IndexId(type = IdType.CUSTOMIZE)
private String id;
/**
* 文档标题,分析:IK_MAX_WORD,查找:IK_SMART
*/ @IndexField(fieldType = FieldType.TEXT,analyzer = Analyzer.IK_MAX_WORD,searchAnalyzer = Analyzer.IK_SMART)
private String title;
/**
* 文档内容,分析:IK_MAX_WORD,查找:IK_SMART
*/ @IndexField(fieldType = FieldType.TEXT,analyzer = Analyzer.IK_MAX_WORD,searchAnalyzer = Analyzer.IK_SMART)
private String content;
//@DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDateTime createTime;
}
- Mapper(继承BaseEsMapper)
public interface DocumentMapper extends BaseEsMapper<Document> {
}
4.CRUD
![[TestUserEeController.java]]
- 创建索引
@GetMapping("/createIndex")
public Boolean createIndex() {
return documentMapper.createIndex();
}
- 插入数据
@PostMapping("/insertBatch")
public Integer insertBatch(@RequestParam Integer count) {
return insertDocuments(count);
}
/**
* 批量插入 1000 条数据到 Elasticsearch
*/public int insertDocuments(int count) {
List<Document> documents = generateDocuments(count);
documentMapper.insertBatch(documents);
System.out.println("成功插入 1000 条数据到 Elasticsearch");
return count;
}
/**
* 生成指定数量的 Document 数据
*
* @param count 数据数量
* @return 数据列表
*/
public List<Document> generateDocuments(int count) {
List<Document> documents = new ArrayList<>();
Random random = new Random();
// 当前时间为基准
LocalDateTime baseTime = LocalDateTime.now();
for (int i = 1; i <= count; i++) {
// 创建时间在基准时间基础上随机增加若干秒
LocalDateTime createTime = baseTime.plusSeconds(random.nextInt(86400)); // 随机一天内的秒数
Document document = Document.builder()
.id(String.valueOf(i)) // 模拟自增 ID .title("Document Title " + i)
.content("This is the content of document " + i)
.createTime(createTime)
.build();
documents.add(document);
}
return documents;
}
- 修改
@PutMapping("/updateById/{id}")
public Integer updateById(@PathVariable Integer id,@RequestParam String title,@RequestParam String content) {
return EsWrappers.lambdaChainUpdate(documentMapper)
.eq(Document::getId, id)
.set(Document::getTitle, title)
.set(Document::getContent, content)
.update();
}
- 查询(id模糊查询,链式,时间)
/**
* description: 根据title模糊查询(普通写法)
* @return: java.util.List<com.bluefoxyu.easyes.sample.domain.Document>
*/
@GetMapping("/searchByTitle1")
public List<Document> searchByTitle1() {
// 3.查询出所有标题为修改的文档列表
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
wrapper.like(Document::getTitle, "修改");
return documentMapper.selectList(wrapper);
}
/**
* description: 根据title模糊查询(链式写法)
* @return: java.util.List<com.bluefoxyu.easyes.sample.domain.Document>
*/
@GetMapping("/searchByTitle2")
public List<Document> searchByTitle2() {
// 3.查询出所有标题为修改的文档列表
return EsWrappers.lambdaChainQuery(documentMapper)
.like(Document::getTitle, "修改")
.list();
}
/**
* <p>
* description: 根据 createTime 查询文档列表(链式写法)
* </p>
*
* @param startTime 开始时间
* @param endTime 结束时间
* @return: java.util.List<com.bluefoxyu.easyes.sample.domain.Document>
*/
@GetMapping("/searchByCreateTime")
public List<Document> searchByCreateTime(@RequestParam LocalDateTime startTime, @RequestParam LocalDateTime endTime) {
// 需要进行格式匹配
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 查询出 createTime 在 startTime 和 endTime 之间的文档列表
return EsWrappers.lambdaChainQuery(documentMapper)
.ge(Document::getCreateTime, startTime.format(formatter)) // 大于或等于开始时间
.le(Document::getCreateTime, endTime.format(formatter)) // 小于或等于结束时间
.list();
}
删除
/**
* 全部删除
*/
@DeleteMapping("/deleteAll")
public Integer deleteAll() {
// 创建删除条件:match_all 表示匹配所有文档
LambdaEsQueryWrapper<Document> deleteWrapper = new LambdaEsQueryWrapper<>();
deleteWrapper.matchAllQuery(); // 匹配所有文档
return documentMapper.delete(deleteWrapper);
}
5.其他相关链接
- 混合查询:
https://www.easy-es.cn/pages/5743eb/
- 字段过滤:
https://www.easy-es.cn/pages/bbee1a/
- 嵌套查询(must,should,filter,must_not )
https://www.easy-es.cn/pages/17ea0a/#es%E5%9B%9B%E5%A4%A7%E5%B5%8C%E5%A5%97%E6%9F%A5%E8%AF%A2
- 官方文档(执行sql,条件构造器)
https://www.easy-es.cn/pages/cb6b26/#_3-%E6%96%B9%E6%B3%95%E5%B7%AE%E5%BC%82
- docker安装es,ik分词器轻移步:
https://blog.csdn.net/m0_74173363/article/details/141718077
评论一下吧
取消回复