前言
小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。我们都知道在
MP
中QueryWrapper
(LambdaQueryWrapper
) 和UpdateWrapper
(LambdaUpdateWrapper
) 的父类AbstractWrapper
用于生成sql
的where
条件,但是Wrapper
这么多你只会用QueryWrapper
可远远不够的啊!各种高级骚操作必须学起来,结合案例代码轻松驾驭各种用法,顺便梳理一些常用的条件构造器及使用的注意事项,闲话少叙,直接进入正题。
前期准备
准备两张表以及对应的实体类对象
tb_province
省份表
1 | sql复制代码CREATE TABLE `tb_province` ( |
- 省份表对应的实体类
1 | java复制代码@TableName(value = "tb_province") |
tb_capital
省会表,通过省份表的邮政编码关联
1 | sql复制代码CREATE TABLE `tb_capital` ( |
- 省会表对应的实体类
1 | java复制代码@TableName(value = "tb_capital") |
代码演练
OK,一切准备就绪后开始撸代码。
普通 QueryWrapper
先来个简单的,查询省份表中的某一条记录,你可能会用 QueryWrapper
这么写:
1 | java复制代码// 查询江西省基本信息 |
执行后,控制台中可以看见如下语句:
1 | sql复制代码==> Preparing: SELECT pid,province,abbr,area,population,attraction FROM tb_province WHERE (province = ?) |
QueryWrapper
查询条件包装类使用 eq
(equal) 方法将传入的第一个参数(数据库表中的列名)和第二个参数(条件值)划上等号,然后调用 mapper
接口继承 BaseMapper
下来的 selectOne()
方法,传入 Wrapper
将查询条件加上,于是就得到了上面的 SQL
语句。
最终打印结果为:
1 | console复制代码Province [Hash = 629321967, pid=10, province=江西省, abbr=赣, area=166900, population=4666.10, attraction=庐山、鄱阳湖、滕王阁, capital=null] |
capital=null
!!因为数据库表字段不对应,使用注解排除了非表字段。
那如何在查询省份信息的时候,将其所属的省会信息塞进实体对象中,从而得到一个比较详细的省份详细信息呢?
两种做法:
Province
类中删除被注解标注的capital
属性,加上外键通过这个字段关联到Capital
实体,做一个二次查询然后再赋值。- 一个省份对应一个省会,是一对一关系,所以我们可以在对应
xml
文件中写resultMap
结果集映射,将外键值通过映射查询返回的结果映射到capital
属性上。
这里使用第二种做法:
ProvinceMapper.xml
:
1 | xml复制代码<resultMap id="BaseResultMap" type="com.xx.xxx.entity.Province"> |
CapitalMapper.xml
外键值映射查询 SQL
:
1 | xml复制代码<select id="selectAllByPostcode" parameterType="map" resultMap="BaseResultMap"> |
只要 resultMap="BaseResultMap"
,那么你查询的省份信息就包含省会相关信息了。
1 | java复制代码Province province = provinceMapper.selectByProvinceName("浙江省"); |
Wrapper
支持链式编程,如果查询条件:省份名为浙江省、简称为浙且邮政编码为 310000
,你可能会这么写:
1 | java复制代码QueryWrapper<Province> eq = new QueryWrapper<Province>() |
其实可以不用接连写三个 eq()
,直接写一个 allEq()
即可:
1 | java复制代码QueryWrapper<Province> eq = new QueryWrapper<Province>() |
第一个参数接收一个 Map
,key
为数据库字段名, value
为字段值;
第二个参数接收一个布尔值,可以不传,默认为 true
,为 true
则在 map
的 value
为 null
时调用 isNull
方法,为 false
时则不将 value
为 null
的字段作为查询条件。
链式 Lambda 操作
通过上面代码实例可以明显发觉,每次都要自己写 column_name
,一旦写错立马报错,而且写固定列名损害了代码的健壮性,比较死板。
所以使用函数式接口,实现链式查询就非常有必要了!
查询省份名带有 ”江“ 、人口超过 2000 万或省份面积在 10w~25w 平方千米的省份信息,按照人口数量降序显示。
1 | java复制代码// 在 QueryWrapper 中是获取的是 LambdaQueryWrapper |
注意:不调用 or()
则默认为使用 and
连接。
执行后的 SQL
语句:
1 | sql复制代码==> Preparing: SELECT pid,province,abbr,area,population,attraction FROM tb_province WHERE (province LIKE ? AND population > ? OR area BETWEEN ? AND ?) ORDER BY population DESC |
按照气候进行分组,并筛选出别名在三个汉字及以上的直辖市。
- 第一个入参
boolean condition
表示该条件 是否 加入生成的SQL
中,默认为true
。例如:query.like(StringUtils.isNotBlank(name), Entity::getName, name) .eq(age!=null && age >= 0, Entity::getAge, age)
1 | java复制代码List<Capital> capitals = new LambdaQueryChainWrapper<Capital>(capitalMapper) |
LambdaQueryChainWrapper
链式查询 Lambda
式,使用 CapitalMapper
接口(继承 BaseMapper
)初始化,链式的末尾使用 list()
方法调用 this.getBaseMapper().selectList(this.getWrapper());
返回一个 List
结果集。
同时,最后也可通过
one()
返回T
实体类对象oneOpt()
返回Optional<T>
Optional
包装实体类count()
返回Integer
统计结果集总条数page()
返回Page
分页对象。
除了使用 new LambdaQueryWrapper<T>()
的方式得到一个 LambdaQueryWrapper
,还可以使用 Wrappers
类调用 query()
得到一个 QueryWrapper
对象,调用 lambdaQuery()
静态方法得到一个 LambdaQueryWrapper
等等
查询简称有两个及以上和不包括含有“海”的著名景点的省份下的省会信息,如果
randomBool
随机布尔值为true
那么就再去筛选省会别名不为“林城”,否则判断一条查询SQL
语句是否存在结果,最终的查询的记录按照主键id
升序且只选取前两个记录。
这个案例需要使用到了两张表,可用子查询 inSql()
,第一个参数为表列名,另一个参数为 sqlString
即需传入 SQL
语句,拼接后的效果为:列 in (查询结果集) 。
1 | java复制代码// 生成一个随机布尔值 |
func()
方法主要作用就是要方便在出现 if...else
下调用不同方法能不断链,两种情况的不同会得到不同的 SQL
语句。
运行后,控制台打印的 SQL
语句:
1 | sql复制代码randomBool: true |
last()
无视优化规则直接拼接到SQL
的最后,但是要注意只能调用一次,如果多次调用以最后一次为准,且有 sql 注入的风险,需谨慎使用!!
CustomSqlSegment 入参
经常会遇到这样一个需求:写一个搜索接口,传入的参数是一个
VO
对象,里面包含的都是一些搜索字段,返回的搜索结果放到List<VO>
/page<VO>
中,这时如果使用mapper.selectPage()
返回并不是一个包装VO
的Page
对象,所以这时你就需要自定义mapper
层方法,编写xml
文件了。
ProvinceVO
对象:
1 | java复制代码@Data |
Mapper
层接口方法:
1 | java复制代码List<ProvinceVO> queryPageList(Page page, @Param("provinceVO") ProvinceVO provinceVO); |
mapper
接口 queryFruitList()
方法返回的是分页后的 List
集合,但是也可以是 Page
的包装对象,拿到里面的列表数据只需要调用 getRecords()
静态方法。
1 | java复制代码Page<ProvinceVO> page = new Page<ProvinceVO>(1, 10); |
如果你想使用 Wrapper
自定义 SQL
生成 where
条件,你可以使用注解的方式来书写:
1 | java复制代码@Select("select * from tb_province ${ew.customSqlSegment}") |
测试代码:
1 | java复制代码List<ProvinceVO> provinceVOList = provinceMapper.getVOListByCustomSqlSegment(new QueryWrapper<ProvinceVO>() |
注意:不支持 Wrapper
内的 entity
生成 where
语句!!也就是说不能使用函数式接口代替列名。
1 | java复制代码List<ProvinceVO> provinceVOList = provinceMapper.getVOListByCustomSqlSegment(new LambdaQueryWrapper<ProvinceVO>() |
否则,控制台会报如下错误信息:
1 | console复制代码org.apache.ibatis.builder.BuilderException: |
除了,注解的方式,还可以在 XML
文件中书写:
Constants.WRAPPER
使用了MP
中字符串常量池,其值为ew
,等价于@Param("ew")
1 | java复制代码Page<ProvinceVO> getPageListByCustomSqlSegment(Page<ProvinceVO> page, @Param(Constants.WRAPPER) Wrapper wrapper); |
XML
中书写 SQL
语句:
1 | sql复制代码<select id="getPageListByCustomSqlSegment" resultType="com.xx.xxx.vo.ProvinceVO"> |
测试代码:
1 | java复制代码Page<ProvinceVO> page = new Page<>(1, 10); |
执行后,控制台可见:
1 | console复制代码JsqlParserCountOptimize sql=select * |
注意:${ew.customSqlSegment}
前面千万不要加 where
关键字,QueryWrapper
会附带。
结尾
撰文不易,欢迎大家点赞、评论,你的关注、点赞是我坚持的不懈动力,感谢大家能够看到这里!Peace & Love。
参考
条件构造器 | MyBatis-Plus (baomidou.com)
本文转载自: 掘金