一、回顾
现在越来越流行基于 SpringBoot 开发 Web 应用,其中利用 Mybatis 作为数据库 CRUD 操作已成为主流。楼主以 MySQL 为例,总结了九大类使用 Mybatis 操作数据库 SQL 小技巧分享给大家。
- 分页查询
- 预置 sql 查询字段
- 一对多级联查询
- 一对一级联查询
- foreach 搭配 in 查询
- 利用if 标签拼装动态 where 条件
- 利用 choose 和 otherwise组合标签拼装查询条件
- 动态绑定查询参数:_parameter
- 利用 set 配合 if 标签,动态设置数据库字段更新值
01 分页查询
利用 limit 设置每页 offset 偏移量和每页 size 大小。
1 | sql复制代码select * from sys_user u |
02 预置 sql 查询字段
1 | bash复制代码<sql id="columns"> |
查询 select 语句引用 columns:
1 | bash复制代码<select id="selectById" resultMap="RM_MsShortcutPanel"> |
03 一对多级联查询
利用 mybatis 的 collection 标签,可以在每次查询文章主体同时通过 queryparaminstancelist 级联查询出关联表数据。
1 | ini复制代码<resultMap id="BaseResultMap" type="com.unicom.portal.pcm.entity.ArticleEntity"> |
queryparaminstancelist 的 sql 语句
1 | csharp复制代码<select id="queryparaminstancelist" resultMap="ParamInstanceResultMap"> |
04 一对一级联查询
利用 mybatis 的 association 标签,一对一查询关联表数据。
1 | ini复制代码<resultMap id="BaseResultMap" type="com.unicom.portal.pcm.entity.ArticleEntity"> |
查询sql语句:
MsArticlecount 实体对象的属性值可以从 上面的 select 后的 sql 字段进行匹配映射获取。
05 foreach 搭配 in 查询
利用 foreach 遍历 array 集合的参数,拼成 in 查询条件
1 | perl复制代码<foreach collection="array" index="index" item="item" open="(" separator="," close=")"> |
06 利用 if 标签拼装动态 where 条件
1 | csharp复制代码select r.*, (select d.org_name from sys_dept d where d.dept_id = r.dept_id) deptName from sys_role r |
07 利用 choose 和 otherwise 组合标签拼装查询条件
1 | vbnet复制代码<choose> |
08 隐形绑定参数:_parameter
_parameter 参数的含义
“
当 Mapper、association、collection 指定只有一个参数时进行查询时,可以使用 _parameter,它就代表了这个参数。
另外,当使用 Mapper指定方法使用 @Param 的话,会使用指定的参数值代替。
1 | bash复制代码SELECT id, grp_no grpNo, province_id provinceId, status FROM tj_group_province |
09 利用 set 配合 if 标签,动态设置数据库字段更新值
1 | bash复制代码<update id="updateById"> |
二、Mybatis-Plus Lambda 表达式理论篇
背景
如果 Mybatis-Plus 是扳手,那 Mybatis Generator 就是生产扳手的工厂。
MyBatis 是一种操作数据库的 ORM 框架,提供一种 Mapper 类,支持让你用 java 代码进行增删改查的数据库操作,省去了每次都要手写 sql 语句的麻烦。但是有一个前提,你得先在 xml 中写好 sql 语句,也是很麻烦的。
题外话:Mybatis 和 Hibernate 的比较
- Mybatis 是一个半 ORM 框架;Hibernate 是一个全 ORM 框架。Mybatis 需要自己编写 sql 。
- Mybatis 直接编写原生 sql,灵活度高,可以严格控制 sql 执行性能;Hibernate的自动生成 hql,因为更好的封装型,开发效率提高的同时,sql 语句的调优比较麻烦。
- Hibernate的 hql 数据库移植性比 Mybatis 更好,Hibernate 的底层对 hql 进行了处理,对于数据库的兼容性更好,
- Mybatis 直接写的原生 sql 都是与数据库相关,不同数据库 sql 不同,这时就需要多套 sql 映射文件。
- Hibernate 在级联删除的时候效率低;数据量大, 表多的时候,基于关系操作会变得复杂。
- Mybatis 和 Hibernate 都可以使用第三方缓存,而 Hibernate 相比 Mybatis 有更好的二级缓存机制。
为什么要选择 Lambda 表达式?
Mybatis-Plus 的存在就是为了稍稍弥补 Mybatis 的不足。
在我们使用 Mybatis 时会发现,每当要写一个业务逻辑的时候都要在 DAO 层写一个方法,再对应一个 SQL,即使是简单的条件查询、即使仅仅改变了一个条件都要在 DAO层新增一个方法,针对这个问题,Mybatis-Plus 就提供了一个很好的解决方案:lambda 表达式,它可以让我们避免许多重复性的工作。
想想 Mybatis 官网提供的 CRUD 例子吧,基本上 xml 配置占据了绝大部分。而用 Lambda 表达式写的 CRUD 代码非常简洁,真正做到零配置,不需要在 xml 或用注解(@Select)写大量原生 SQL 代码。
1 | ini复制代码LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery(); |
lambda 表达式的理论基础
Java中的 lambda 表达式实质上是一个匿名方法,但该方法并非独立执行,而是用于实现由函数式接口定义的唯一抽象方法。
使用 lambda 表达式时,会创建实现了函数式接口的一个匿名类实例,如 Java8 中的线程 Runnable 类实现了函数接口:@FunctionalInterface。
1 | csharp复制代码@FunctionalInterface |
平常我们执行一个 Thread 线程:
1 | csharp复制代码new Thread(new Runnable() { |
如果用 lambda 会非常简洁,一行代码搞定。
1 | scss复制代码 new Thread(()-> System.out.println("xxx")).start(); |
所以在某些场景下使用 lambda 表达式真的能减少 java 中一些冗长的代码,增加代码的优雅性。
lambda 条件构造器基础类:包装器模式(装饰模式)之 AbstractWrapper AbstractWrapper 条件构造器说明
- 出现的第一个入参 boolean condition 表示该条件是否加入最后生成的 sql 中,例如: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 中!
警告:
不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输。
“
Wrapper 很重 传输 Wrapper 可以类比为你的 controller 用 map 接收值(开发一时爽,维护火葬场) 正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作 我们拒绝接受任何关于 RPC 传输 Wrapper 报错相关的 issue 甚至 pr。
AbstractWrapper 内部结构
从上图,我们了解到 AbstractWrapper 的实际上实现了五大接口:
- SQL 片段函数接口:ISqlSegment
1 | csharp复制代码@FunctionalInterface |
- 比较值接口 Compare<Children, R>,如 等值 eq、不等于:ne、大于 gt、大于等于:ge、小于 lt、小于等于 le、between、模糊查询:like 等等
- 嵌套接口 Nested<Param, Children> ,如 and、or
- 拼接接口 Join,如 or 、exists
- 函数接口 Func<Children, R>,如 in 查询、groupby 分组、having、order by排序等
常用的 where 条件表达式 eq、like、in、ne、gt、ge、lt、le。
1 | scss复制代码@Override |
SQL 片段函数接口
lambda 这么好用的秘诀在于 SQL 片段函数接口:ISqlSegment,我们在 doIt 方法找到 ISqlSegment 对象参数,翻开 ISqlSegment 源码,发现它真实的庐山真面目,原来是基于 Java 8 的函数接口 @FunctionalInterface 实现!
ISqlSegment 就是对 where 中的每个条件片段进行组装。
1 | typescript复制代码/** |
从 MergeSegments 类中,我们找到 getSqlSegment 方法,其中代码片段
1 | scss复制代码sqlSegment = normal.getSqlSegment() + groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment() |
这段代码表明,一条完整的 where 条件 SQL 语句,最终由 normal SQL 片段,groupBy SQL 片段,having SQL 片段,orderBy SQL 片段拼接而成。
1 | java复制代码@Getter |
三、Mybatis-Plus Lambda 表达式实战
01 环境准备
1. Maven 依赖
1 | xml复制代码<dependency> |
2. 实体(表)以及 Mapper 表映射文件
- Base 实体
1 | less复制代码@NoArgsConstructor |
- 用户账号实体:UserEntity
1 | less复制代码@EqualsAndHashCode(callSuper = true) |
Mapper 操作类
1 | less复制代码List<UserDTO> selectUsers(); |
Mapper 表映射文件
1 | ini复制代码<mapper namespace="com.dunzung.mybatisplus.query.mapper.UserMapper"> |
- 订单实体:OrderEntity
1 | kotlin复制代码@Data |
Mapper 操作类
1 | java复制代码@Mapper |
Mapper 表映射文件
1 | ini复制代码<mapper namespace="com.dunzung.mybatisplus.query.mapper.OrderMapper"> |
- 身份证实体:CardEntity
1 | kotlin复制代码@Data |
Mapper 操作类
1 | java复制代码@Mapper |
Mapper 表映射文件
1 | ini复制代码<mapper namespace="com.dunzung.mybatisplus.query.mapper.CardMapper"> |
02 Lambda 基础篇
lambda 构建复杂的查询条件构造器:LambdaQueryWrapper
LambdaQueryWrapper 四种不同的 lambda 构造方法
- 方式一 使用 QueryWrapper 的成员方法方法 lambda 构建 LambdaQueryWrapper
1 | ini复制代码LambdaQueryWrapper<UserEntity> lambda = new QueryWrapper<UserEntity>().lambda(); |
- 方式二 直接 new 出 LambdaQueryWrapper
1 | ini复制代码LambdaQueryWrapper<UserEntity> lambda = new LambdaQueryWrapper<>(); |
- 方式三 使用 Wrappers 的静态方法 lambdaQuery 构建 LambdaQueryWrapper 推荐
1 | ini复制代码LambdaQueryWrapper<UserEntity> lambda = Wrappers.lambdaQuery(); |
- 方式四:链式查询
1 | sql复制代码List<UserEntity> users = new LambdaQueryChainWrapper<UserEntity>(userMapper) |
笔者推荐使用 Wrappers 的静态方法 lambdaQuery 构建 LambdaQueryWrapper 条件构造器。
Debug 调试
为了 Debug 调试方便,需要在 application.yml 启动文件开启 Mybatis-Plus SQL 执行语句全栈打印:
1 | yaml复制代码#mybatis |
执行效果如下:
1 等值查询:eq
1 | perl复制代码@Test |
eq 查询等价于原生 sql 的等值查询。
1 | csharp复制代码select * from sys_user where user_id = 1 |
2 范围查询 :in
1 | ini复制代码@Test |
in 查询等价于原生 sql 的 in 查询
1 | csharp复制代码select * from sys_user where user_id in (1,2) |
3 通配符模糊查询:like
1 | ini复制代码@Test |
like 查询等价于原生 sql 的 like 全通配符模糊查询。
1 | sql复制代码select * from sys_user where sex = 0 and user_name like '%dun%' |
4 右通配符模糊查询:likeRight
1 | ini复制代码@Test |
likeRight 查询相当于原生 sql 的 like 右通配符模糊查询。
1 | sql复制代码select * from sys_user where sex = 0 and user_name like 'dun%' |
5 左通配符模糊查询:likeLeft
1 | ini复制代码@Test |
likeLeft 查询相当于原生 sql 的 like 左通配符模糊查询。
1 | sql复制代码select * from sys_user where sex = 0 and user_name like '%zung' |
6 条件判断查询
条件判断查询类似于 Mybatis 的 if 标签,第一个入参 boolean condition 表示该条件是否加入最后生成的 sql 中。
1 | scss复制代码@Test |
7 利用 or 和 and 构建复杂的查询条件
1 | less复制代码@Test |
上面实例查询等价于原生 sql 查询:
1 | csharp复制代码select * from sys_user where sex = 0 and (use_name = 'dunzung' or age >=50) |
8 善于利用分页利器 PageHelpler
1 | ini复制代码@Test |
上面实例查询等价于原生 sql 分页查询:
1 | sql复制代码select * from sys_user order by age desc,mobile desc limit 0,2 |
另外,Mybatis-Plus 自带分页组件,BaseMapper 接口提供两种分页方法来实现物理分页。
- 第一个返回实体对象允许 null
- 第二个人返回 map 对象多用于在指定放回字段时使用,避免为指定字段 null 值出现
1 | less复制代码IPage<T> selectPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper); |
注意,Mybatis-Plus 自带分页组件时,需要配置 PaginationInterceptor 分页插件。
1 | typescript复制代码@Bean |
9 更新条件构造器:LambdaUpdateWrapper
1 | csharp复制代码@Test |
03 进阶篇
1. Association
Association 标签适用于表和表之间存在一对一的关联关系,如用户和身份证存在一个人只会有一个身份证号,反过来也成立。
1 | csharp复制代码@Test |
XML配置
1 | ini复制代码<resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap"> |
2. Collection
Collection 标签适用于表和表之间存在一对多的关联关系,如用户和订单存在一个人可以购买多个物品,产生多个购物订单。
1 | csharp复制代码@Test |
XML配置
1 | ini复制代码<resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap"> |
注意 Association 和 Collection 先后关系,在编写 ResultMap 时,association 在前,collection 标签在后。
1 | ini复制代码 <resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap"> |
如果二者颠倒顺序会提示错误。
3. 元对象字段填充属性值:MetaObjectHandler
MetaObjectHandler元对象字段填充器的填充原理是直接给 entity 的属性设置值,提供默认方法的策略均为:
“
如果属性有值则不覆盖,如果填充值为 null 则不填充,字段必须声明 TableField 注解,属性 fill 选择对应策略,该声明告知 Mybatis-Plus 需要预留注入 SQL字段。 TableField 注解则是指定该属性在对应情况下必有值,如果无值则入库会是 null。
自定义填充处理器 MyMetaObjectHandler 在 Spring Boot 中需要声明 @Component 或 @Bean 注入,要想根据注解 FieldFill.xxx,如:
1 | less复制代码@TableField(value = "created_tm", fill = FieldFill.INSERT) |
和字段名以及字段类型来区分必须使用父类的 setInsertFieldValByName 或者 setUpdateFieldValByName 方法,不需要根据任何来区分可以使用父类的 setFieldValByName 方法 。
1 | kotlin复制代码/** |
一般 FieldFill.INSERT 用父类的 setInsertFieldValByName 方法更新创建属性(创建人、创建时间)值;FieldFill.INSERT_UPDATE 用父类的 setUpdateFieldValByName 方法更新修改属性(修改人、修改时间)值;如果想让诸如 FieldFill.INSERT 或 FieldFill.INSERT_UPDATE 任何时候不起作用,用父类的 setFieldValByName 设置属性(创建人、创建时间、修改人、修改时间)值即可。
4. 自定义SQL
使用 Wrapper 自定义 SQL 需要 mybatis-plus 版本 >= 3.0.7 ,param 参数名要么叫 ew,要么加上注解 @Param(Constants.WRAPPER) ,使用 ${ew.customSqlSegment} 不支持 Wrapper 内的 entity生成 where 语句。
注解方式
1 | less复制代码@Select("select * from mysql_data ${ew.customSqlSegment}") |
XML配置
1 | sql复制代码List<MysqlData> getAll(Wrapper ew); |
四、Mybatis-Plus lambda 表达式的优势与劣势
通过上面丰富的举例详解以及剖析 lambda 底层实现原理,想必大家会问:” lambda 表达式似乎只支持单表操作?”
据我对 Mybatis-Plus 官网的了解,目前确实是这样。依笔者实际运用经验来看,其实程序员大部分开发的功能基本上都是针对单表操作的,Lambda 表达式的优势在于帮助开发者减少在 XML 编写大量重复的 CRUD 代码,这点是非常重要的 nice 的。很显然,Lambda 表达式对于提高程序员的开发效率是不言而喻的,我想这点也是我作为程序员非常喜欢 Mybatis-Plus 的一个重要原因。
但是,如果涉及对于多表之间的关联查询,lambda 表达式就显得力不从心了,因为 Mybatis-Plus 并没有提供类似于 join 查询的条件构造器。
lambda 表达式优点:
- 单表操作,代码非常简洁,真正做到零配置,如不需要在 xml 或用注解(@Select)写大量原生 SQL 代码
- 并行计算
- 预测代表未来的编程趋势
lambda 表达式缺点:
- 单表操作,对于多表关联查询支持不好
- 调试困难
- 底层逻辑复杂
五、总结
Mybatis-Plus 推出的 lambda 表达式致力于构建复杂的 where 查询构造器式并不是银弹,它可以解决你实际项目中 80% 的开发效率问题,但是针对一些复杂的大 SQL 查询条件支持地并不好,例如一些复杂的 SQL 报表统计查询。
所以,笔者推荐单表操作用 lambda 表达式,查询推荐用 LambdaQueryWrapper,更新用 LambdaUpdateWrapper;多表操作还是老老实实写一些原生 SQL ,至于原生 SQL 写在哪里? Mapper 文件或者基于注解,如 @Select 都是可以的。
参考
- mp.baomidou.com/guide/wrapp…
- www.jianshu.com/p/613a6118e…
- blog.csdn.net/Solitude_w/…
- blog.csdn.net/weixin_4447…
- blog.csdn.net/weixin_4449…
作者:猿芯
来源:www.toutiao.com/i6951307172…
本文转载自: 掘金