条件构造器
温馨提示
如果您之前已经有了解过MP的条件构造器,那我们建议您直接拉到文末,看一下index,enableMust2Filter,and&or这三项MP中没有的即可,其它与MP一致. Wrapper支持两种方式创建:
- 直接new,例如 new
LambdaEsQueryWrapper<>()
. - 通过EsWrappers.lambdaQuery()创建,可支撑链式编程的场景,对标MP的Wrappers
说明
- 以下出现的第一个入参boolean condition表示该条件是否加入最后生成的语句中,例如:query.like(StringUtils.isNotBlank(name), Entity::getName, name) .eq(age!=null && age >= 0, Entity::getAge, age)
- 以下代码块内的多个方法均为从上往下补全个别boolean类型的入参,默认为true
- 以下出现的泛型Param均为Wrapper的子类实例(均具有AbstractWrapper的所有方法)
- 以下方法在入参中出现的R为泛型,在普通wrapper中是String,在LambdaWrapper中是函数(例:Entity::getId,Entity为实体类,getId为字段id的getMethod)
- 以下方法入参中的R column均表示数据库字段,当R具体类型为String时则为数据库字段名(字段名是数据库关键字的自己用转义符包裹!)!而不是实体类数据字段名!!!,另当R具体类型为SFunction时项目runtime不支持eclipse自家的编译器!!!
- 以下举例均为使用普通wrapper,入参为Map和List的均以json形式表现!
- 使用中如果入参的Map或者List为空,则不会加入最后生成的sql中!
- 有任何疑问就点开源码看,看不懂函数的点击我学习新知识 (opens new window)
警告
不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输
- wrapper 很重
- 传输 wrapper 可以类比为你的 controller 用 map 接收值(开发一时爽,维护火葬场)
- 正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作
- 我们拒绝接受任何关于 RPC 传输 Wrapper 报错相关的 issue 甚至 pr
# AbstractWrapper
说明
QueryWrapper(LambdaEsQueryWrapper) 和 UpdateWrapper(LambdaEsUpdateWrapper) 的父类用于生成 语句 的 where 条件, entity 属性也用于生成 语句 的 where 条件注意: entity 生成的 where 条件与 使用各个 api 生成的 where 条件 没有任何关联行为
# QueryWrapper
说明
继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件及 LambdaEsQueryWrapper
# UpdateWrapper
说明
继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件及 LambdaEsUpdateWrapper
# allEq
allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
2
3
4
个别参数说明
params : key为数据库字段名,value为字段值 null2IsNull : 为true则在map的value为null时调用 isNull方法,为false时则忽略value为null的
- 例1: allEq({id:1,name:"老王",age:null})--->id = 1 and name = '老王' and age is null
- 例2: allEq({id:1,name:"老王",age:null}, false)--->id = 1 and name = '老王'
allEq(BiPredicate<R, V> filter, Map<R, V> params)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
2
3
4
个别参数说明
filter : 过滤函数,是否允许字段传入比对条件中 params 与 null2IsNull : 同上
- 例1: allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null})--->name = '老王' and age is null
- 例2: allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null}, false)--->name = '老王'
# eq
eq(R column, Object val)
eq(boolean condition, R column, Object val)
2
- 等于 =
- 例: eq("name", "老王")--->name = '老王'
# ne
ne(R column, Object val)
ne(boolean condition, R column, Object val)
2
- 不等于 !=
- 例: ne("name", "老王")--->name != '老王'
# gt
gt(R column, Object val)
gt(boolean condition, R column, Object val)
2
- 大于 >
- 例: gt("age", 18)--->age > 18
# ge
ge(R column, Object val)
ge(boolean condition, R column, Object val)
2
- 大于等于 >=
- 例: ge("age", 18)--->age >= 18
# lt
lt(R column, Object val)
lt(boolean condition, R column, Object val)
2
- 小于 <
- 例: lt("age", 18)--->age < 18
# le
le(R column, Object val)
le(boolean condition, R column, Object val)
2
- 小于等于 <=
- 例: le("age", 18)--->age <= 18
# between
between(R column, Object val1, Object val2)
between(boolean condition, R column, Object val1, Object val2)
2
- BETWEEN 值1 AND 值2
- 例: between("age", 18, 30)--->age between 18 and 30
# notBetween
notBetween(R column, Object val1, Object val2)
notBetween(boolean condition, R column, Object val1, Object val2)
2
- NOT BETWEEN 值1 AND 值2
- 例: notBetween("age", 18, 30)--->age not between 18 and 30
# like
like(R column, Object val)
like(boolean condition, R column, Object val)
2
- LIKE '%值%'
- 例: like("name", "王")--->name like '%王%'
# notLike
notLike(R column, Object val)
notLike(boolean condition, R column, Object val)
2
- NOT LIKE '%值%'
- 例: notLike("name", "王")--->name not like '%王%'
# likeLeft
likeLeft(R column, Object val)
likeLeft(boolean condition, R column, Object val)
2
- LIKE '%值'
- 例: likeLeft("name", "王")--->name like '%王'
# likeRight
likeRight(R column, Object val)
likeRight(boolean condition, R column, Object val)
2
- LIKE '值%'
- 例: likeRight("name", "王")--->name like '王%'
# isNull
isNull(R column)
isNull(boolean condition, R column)
2
- 字段 IS NULL
- 例: isNull(Document::getTitle)--->title is null
# isNotNull
isNotNull(R column)
isNotNull(boolean condition, R column)
2
- 字段 IS NOT NULL
- 例: isNotNull(Document::getTitle)--->title is not null
# in
in(R column, Collection<?> value)
in(boolean condition, R column, Collection<?> value)
2
- 字段 in (value.get(0), value.get(1), ...)
- 例: in("age",{1,2,3})--->age in (1,2,3)
in(R column, Object... values)
in(boolean condition, R column, Object... values)
2
- 字段 in (v0, v1, ...)
- 例: in("age", 1, 2, 3)--->age in (1,2,3)
# notIn
notIn(R column, Collection<?> value)
notIn(boolean condition, R column, Collection<?> value)
2
- 字段 not in (value.get(0), value.get(1), ...)
- 例: notIn("age",{1,2,3})--->age not in (1,2,3)
notIn(R column, Object... values)
notIn(boolean condition, R column, Object... values)
2
- 字段 not in (v0, v1, ...)
- 例: notIn("age", 1, 2, 3)--->age not in (1,2,3)
# groupBy
groupBy(R... columns)
groupBy(boolean condition, R... columns)
2
- 分组:GROUP BY 字段, ...
- 例: groupBy(Document::getId,Document::getTitle)--->group by id,title
# orderByDesc
orderByDesc(R... columns)
orderByDesc(boolean condition, R... columns)
2
- 排序:ORDER BY 字段, ... DESC
- 例: orderByDesc(Document::getId,Document::getTitle)--->order by id DESC,title DESC
# limit
limit(Integer n);
limit(Integer m, Integer n);
2
3
- limit n 最多返回多少条数据,相当于MySQL中limit n 中的n,用法一致.
- limit m,n 跳过m条数据,最多返回n条数据,相当于MySQL中的limit m,n 或 offset m limit n
- 例: limit(10)--->最多只返回10条数据
- 例: limit(2,5)--->跳过前2条数据,从第3条开始查询,总共查询5条数据
温馨提示
n参数若不指定,则其默认值是10000 如果你单次查询,不想要太多得分较低的数据,需要手动指定n去做限制. 另外此参数作用与Es中的size,from一致,只是为了兼容MySQL语法而引入,使用者可以根据自身习惯二选一,当两种都用时,只有一种会生效,后指定的会覆盖先指定的.
# from
from(Integer from)
- 从第几条数据开始查询,相当于MySQL中limit (m,n)中的m.
- 例: from(10)--->从第10条数据开始查询
# size
size(Integer size)
- 最多返回多少条数据,相当于MySQL中limit (m,n)中的n 或limit n 中的n
- 例: size(10)--->最多只返回10条数据
温馨提示
如果你单次查询,不想要太多得分较低的数据,需要手动指定size去做限制.
# set
set(String column, Object val)
set(boolean condition, String column, Object val)
2
- SQL SET 字段
- 例: set("name", "老李头")
- 例: set("name", "")--->数据库字段值变为空字符串
- 例: set("name", null)--->数据库字段值变为null
# index
index(String indexName)
index(boolean condition, String indexName)
2
温馨提示
可通过wrapper.index(String indexName)指定本次查询作用于哪个索引,如果本次查询要从多个索引上查询,那么索引名称可以用逗号隔开,例如wrapper.index("index1","indexe2"...) wrapper中指定的索引名称优先级最高,如果不指定则取实体类中配置的索引名称,如果实体类也未配置,则取实体名称小写作为当前查询的索引名 针对insert/delete/update等接口中无wrapper的情况,如果你需要指定索引名,可直接在对应接口的入参中添加索引名称,可参考下面示例:
Document document = new Document();
// 省略为document赋值的代码
String indexName = "laohan";
insert(document,indexName);
2
3
4
# enableMust2Filter
enableMust2Filter(boolean enable)
enableMust2Filter(boolean condition, boolean enable)
2
温馨提示
是否将must查询条件转换成filter查询条件,可以在wrapper中直接指定本次查询的条件是否转换,如果不指定,则从全局配置文件中获取,若配置文件中也未配置,则默认不转换. must查询条件计算得分,filter不计算得分,因此在不需要计算得分的查询场景中,开启此配置可提升少许查询性能.
# and&or (重点)
致歉
ES的参数实际上是以树形数据结构封装的,所以在ES中的AND及OR没有办法做到和MySQL中一致,为了实现和MP几乎一致的语法,作者那糟老头子头发都想没了... 好在最后,基本上做到了99%相似度的语法,仍有1%需要各位用户学习一下.
MySQL和ES语法对应关系表格如下
MySQL | ES |
---|---|
and(条件1,条件2...) | must BoolQueryBuilder(条件1,条件2) |
or(条件1,条件2...) | should BoolQueryBuilder(条件1,条件2) |
条件1.or().条件2 | should条件1,should条件2 |
AND 介绍,EE中的AND其实就是将AND括号中的多个查询条件封装进一个BoolQueryBuilder中作为整体,然后该整体与其他参数默认以Must封装,功能类似MySQL中的 AND(条件1,条件2,条件3...)
AND API
and(Consumer<Param> consumer)
and(boolean condition, Consumer<Param> consumer)
2
- AND
- 例: and(i -> i.eq(Document::getTitle, "Hello").ne(Document::getCreator, "Guy"))--->and (title ='Hello' and creator != 'Guy' )
OR 介绍,EE中的OR和MP中的OR一样,支持2种,一种是or(),作为连接符,另一种是or(条件1,条件2,条件3).
- 第一种or():用于把or()连接符前面和后面的must条件统统重置为should查询条件
- 第二种or(条件1,条件2,条件3...): 用于将括号中的多个查询条件封装进一个BoolQueryBuilder中作为整体,然后该整体与其它参数默认以Should封装,功能类似MySQL中的OR(条件1,条件2,条件3...)
- 第三种特殊情况,就是第一种or()连接符出现在and(条件1.or().条件2...)或or(条件1.or().条件2...)中,此时or()将must条件重置为should条件的范围仅限于括号内,括号外面的查询条件不受影响.
or()
or(boolean condition)
2
- 拼接 OR 注意事项: 主动调用or表示紧接着下一个方法不是用and连接!(不调用or则默认为使用and连接)
- 例: eq("Document::getId",1).or().eq(Document::getTitle,"Hello")--->id = 1 or title ='Hello'
or(Consumer<Param> consumer)
or(boolean condition, Consumer<Param> consumer)
2
OR
例: or(i -> i.eq(Document::getTitle, "Hello").ne(Document::getCreator, "Guy"))--->or (title ='Hello' and status != 'Guy' )
特殊情况
例: eq(Document::getTitle,"Hello") .and(i->i.eq(Document::getCreator,"Bob").or().eq(Document::getCreator,"Tom"))---> title="Hello" and(creator="Bob" or creator="Tom")
除此之外,有一部分使用场景是如下图这样的,所有查询字段,查询类型,匹配规则等都是不固定的,由用户自由来选,这种情况下,采用上面的语法代码会非常难写,不妨使用queryStringQuery API来解决,用它来解决,整个语法就更像MySQL了,而且灵活性和效率都很高.
前置知识学习
正式进入主题前,我们先来了解下ES的索引,因为有很多小白不懂ES索引,所以这里简单说一下ES的keyword类型和text类型,以免下面踩坑,已经了解的可直接跳过此段介绍.
ES中的keyword类型,和MySQL中的字段基本上差不多,当我们需要对查询字段进行精确匹配,左模糊,右模糊,全模糊,排序聚合等操作时,需要该字段的索引类型为keyword类型,当我们需要对字段进行分词查询时,需要该字段的类型为text类型,并且指定分词器(不指定就用ES默认分词器,效果通常不理想).当同一个字段,我们既需要把它当keyword类型使用,又需要把它当text类型使用时,此时我们的索引类型为keyword_text类型,EE中可以对字段添加注解@TableField(fieldType = FieldType.KEYWORD_TEXT),如此该字段就会被创建为keyword+text双类型如下图所示,值得注意的是,当我们把该字段当做keyword类型查询时,ES要求传入的字段名称为"字段名.keyword",当把该字段当text类型查询时,直接使用原字段名即可.
还需要注意的是,如果一个字段的索引类型被创建为仅为keyword类型(如下图所示)查询时,则不需要在其名称后面追加.keyword,直接查询就行.
啰嗦完了,正式进入主题,queryStringQuery API:
queryStringQuery(String queryString);
其中queryString字符串就是我们的查询条件,我们可以用StringBuilder把查询字段和值拼接进去,组装成最终的查询语句. 以上图为例,我演示一个场景,请忽略场景合理性,因为是我瞎xx选的:假设我的查询条件是:字段:创建者 等于老王,且创建者分词匹配"隔壁"(比如:隔壁老汉,隔壁老王),或者创建者包含大猪蹄子,对应的代码如下:
@Test
public void testQueryString() {
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
StringBuilder sb = new StringBuilder();
sb.append("(")
.append("(")
.append("creator.keyword")
.append(":")
.append("老王")
.append(")")
.append("AND")
.append("(")
.append("creator")
.append(":")
.append("隔壁")
.append(")")
.append(")")
.append("OR")
.append("(")
.append("creator.keyword")
.append(":")
.append("*大猪蹄子*")
.append(")");
// sb最终拼接为:((creator.keyword:老王)AND(creator:隔壁))OR(creator.keyword:*大猪蹄子*) ,可以说和MySQL语法非常相似了
wrapper.queryStringQuery(sb.toString());
List<Document> documents = documentMapper.selectList(wrapper);
System.out.println(documents);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
前端参数页面每传递一行查询参数,我们往sb中append对应参数就完事了,是不是很简单,没错,但是代码不优雅,可咋整? 老汉已经给你们想好出路了,我们提供了工具类,其全路径为:cn.easyes.core.toolkit.QueryUtils 我们用使用该工具类重构上面的代码,如下:
@Test
public void testQueryStringQueryMulti() {
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
String queryStr = QueryUtils.combine(Link.OR,
QueryUtils.buildQueryString(Document::getCreator, "老王", Query.EQ, Link.AND),
QueryUtils.buildQueryString(Document::getCreator, "隔壁", Query.MATCH))
+ QueryUtils.buildQueryString(Document::getCreator, "*大猪蹄子*", Query.EQ);
wrapper.queryStringQuery(queryStr);
List<Document> documents = documentMapper.selectList(wrapper);
System.out.println(documents);
}
2
3
4
5
6
7
8
9
10
11
是不是优雅了很多,其中的枚举Query和Link我也已经为你们封装好了,直接使用即可,不懂其枚举含义也可以直接点开查看,我在源码中有详细注释.