使用 MyBatis-Plus 不用 Lambda 封装,那你的使用姿势不对
共 39454字,需浏览 79分钟
·
2021-04-17 15:18
一、回顾
现在越来越流行基于 SpringBoot
开发 Web
应用,其中利用 Mybatis
作为数据库 CRUD
操作已成为主流。楼主以 MySQL
为例,总结了九大类使用 Mybatis
操作数据库 SQL
小技巧分享给大家。
分页查询 预置 sql
查询字段一对多级联查询 一对一级联查询 foreach
搭配in
查询利用 if
标签拼装动态where
条件利用 choose
和otherwise
组合标签拼装查询条件动态绑定查询参数: _parameter
利用 set
配合if
标签,动态设置数据库字段更新值
01 分页查询
利用 limit
设置每页 offset
偏移量和每页 size
大小。
select * from sys_user u
LEFT JOIN sys_user_site s ON u.user_id = s.user_id
LEFT JOIN sys_dept d ON d.dept_id = s.dept_id
LEFT JOIN sys_emailinfo e ON u.user_id = e.userid AND e.MAIN_FLAG = 'Y'
<where>
<include refid="userCondition"/>
</where>
limit #{offset}, #{limit}
02 预置 sql 查询字段
<sql id="columns">
id,title,content,original_img,is_user_edit,province_id,status,porder
</sql>
查询 select
语句引用 columns:
<select id="selectById" resultMap="RM_MsShortcutPanel">
seelct
<include refid="columns"/>
from cms_self_panel
where
id = #{_parameter}
</select>
03 一对多级联查询
利用 mybatis
的 collection
标签,可以在每次查询文章主体同时通过 queryparaminstancelist
级联查询出关联表数据。
<resultMap id="BaseResultMap" type="com.unicom.portal.pcm.entity.ArticleEntity">
<id column="id" jdbcType="BIGINT" property="id"/>
<collection property="paramList" column="id" select="queryparaminstancelist"/>
</resultMap>
queryparaminstancelist
的 sql
语句
<select id="queryparaminstancelist" resultMap="ParamInstanceResultMap">
select * from `cms_article_flow_param_instance` where article_id=#{id}
</select>
04 一对一级联查询
利用 mybatis
的 association
标签,一对一查询关联表数据。
<resultMap id="BaseResultMap" type="com.unicom.portal.pcm.entity.ArticleEntity">
<association property="articleCount" javaType="com.unicom.portal.pcm.entity.MsArticleCount"/>
</resultMap>
查询sql语句:
MsArticlecount
实体对象的属性值可以从 上面的 select
后的 sql
字段进行匹配映射获取。
05 foreach 搭配 in 查询
利用 foreach
遍历 array
集合的参数,拼成 in
查询条件
<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
06 利用 if 标签拼装动态 where 条件
select r.*, (select d.org_name from sys_dept d where d.dept_id = r.dept_id) deptName from sys_role r
<where>
r.wid = #{wid}
<if test="roleName != null and roleName.trim() != ''">
and r.`role_name` like concat('%',#{roleName},'%')
</if>
<if test="status != null and status.trim() != ''">
and r.`status` = #{status}
</if>
</where>
07 利用 choose 和 otherwise 组合标签拼装查询条件
<choose>
<when test="sidx != null and sidx.trim() != ''">
order by r.${sidx} ${order}
</when>
<otherwise>
order by r.role_id asc
</otherwise>
</choose>
08 隐形绑定参数:_parameter
_parameter
参数的含义
“当
Mapper
、association
、collection
指定只有一个参数时进行查询时,可以使用_parameter
,它就代表了这个参数。
另外,当使用 Mapper
指定方法使用 @Param
的话,会使用指定的参数值代替。
SELECT id, grp_no grpNo, province_id provinceId, status FROM tj_group_province
<where>
...
<if test="_parameter!=null">
and grp_no = #{_parameter}
</if>
</where>
09 利用 set 配合 if 标签,动态设置数据库字段更新值
<update id="updateById">
UPDATE cms_label
<set>
<if test="labelGroupId != null">
label_group_id = #{labelGroupId},
</if>
dept_id = #{deptId},
<if test="recommend != null">
is_recommend = #{recommend},
</if>
</set>
WHERE label_id = #{labelId}
</update
提问:#{} 和 ${} 的区别是什么?
二、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
代码。
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.like(UserEntity::getUserName, "dun");
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like全包含关键字查询::" + u.getUserName()));
lambda 表达式的理论基础
Java
中的 lambda
表达式实质上是一个匿名方法,但该方法并非独立执行,而是用于实现由函数式接口定义的唯一抽象方法。
使用 lambda
表达式时,会创建实现了函数式接口的一个匿名类实例,如 Java8
中的线程 Runnable
类实现了函数接口:@FunctionalInterface
。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
平常我们执行一个 Thread
线程:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("xxxx");
}
}).start();
如果用 lambda
会非常简洁,一行代码搞定。
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
@FunctionalInterface
public interface ISqlSegment extends Serializable {
/**
* SQL 片段
*/
String getSqlSegment();
}
比较值接口 Compare<Children, R>
,如 等值eq
、不等于:ne
、大于gt
、大于等于:ge
、小于lt
、小于等于le
、between
、模糊查询:like
等等嵌套接口 Nested<Param, Children>
,如and
、or
拼接接口 Join<Children>
,如or
、exists
函数接口 Func<Children, R>
,如in
查询、groupby
分组、having
、order by
排序等
常用的 where
条件表达式 eq、like、in、ne、gt、ge、lt、le
。
@Override
public Children in(boolean condition, R column, Collection<?> coll) {
return doIt(condition, () -> columnToString(column), IN, inExpression(coll));
}
public Children notIn(boolean condition, R column, Collection<?> coll)
public Children inSql(boolean condition, R column, String inValue)
public Children notInSql(boolean condition, R column, String inValue)
public Children groupBy(boolean condition, R... columns)
public Children orderBy(boolean condition, boolean isAsc, R... columns)
public Children eq(boolean condition, R column, Object val)
public Children ne(boolean condition, R column, Object val)
public Children gt(boolean condition, R column, Object val)
public Children ge(boolean condition, R column, Object val)
public Children lt(boolean condition, R column, Object val)
public Children le(boolean condition, R column, Object val)
...
/**
* 普通查询条件
*
* @param condition 是否执行
* @param column 属性
* @param sqlKeyword SQL 关键词
* @param val 条件值
*/
protected Children addCondition(boolean condition, R column, SqlKeyword sqlKeyword, Object val) {
return doIt(condition, () -> columnToString(column), sqlKeyword, () -> formatSql("{0}", val));
}
SQL 片段函数接口
lambda
这么好用的秘诀在于 SQL 片段函数接口:ISqlSegment,我们在 doIt
方法找到 ISqlSegment
对象参数,翻开 ISqlSegment
源码,发现它真实的庐山真面目,原来是基于 Java 8
的函数接口 @FunctionalInterface
实现!
ISqlSegment
就是对 where
中的每个条件片段进行组装。
/**
* 对sql片段进行组装
*
* @param condition 是否执行
* @param sqlSegments sql片段数组
* @return children
*/
protected Children doIt(boolean condition, ISqlSegment... sqlSegments) {
if (condition) {
expression.add(sqlSegments);
}
return typedThis;
}
@FunctionalInterface
public interface ISqlSegment extends Serializable {
/**
* SQL 片段
*/
String getSqlSegment();
}
从 MergeSegments
类中,我们找到 getSqlSegment
方法,其中代码片段
sqlSegment = normal.getSqlSegment() + groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment()
这段代码表明,一条完整的 where
条件 SQL
语句,最终由 normal SQL
片段,groupBy SQL
片段,having SQL
片段,orderBy SQL
片段拼接而成。
@Getter
@SuppressWarnings("serial")
public class MergeSegments implements ISqlSegment {
private final NormalSegmentList normal = new NormalSegmentList();
private final GroupBySegmentList groupBy = new GroupBySegmentList();
private final HavingSegmentList having = new HavingSegmentList();
private final OrderBySegmentList orderBy = new OrderBySegmentList();
@Getter(AccessLevel.NONE)
private String sqlSegment = StringPool.EMPTY;
@Getter(AccessLevel.NONE)
private boolean cacheSqlSegment = true;
public void add(ISqlSegment... iSqlSegments) {
List<ISqlSegment> list = Arrays.asList(iSqlSegments);
ISqlSegment firstSqlSegment = list.get(0);
if (MatchSegment.ORDER_BY.match(firstSqlSegment)) {
orderBy.addAll(list);
} else if (MatchSegment.GROUP_BY.match(firstSqlSegment)) {
groupBy.addAll(list);
} else if (MatchSegment.HAVING.match(firstSqlSegment)) {
having.addAll(list);
} else {
normal.addAll(list);
}
cacheSqlSegment = false;
}
@Override
public String getSqlSegment() {
if (cacheSqlSegment) {
return sqlSegment;
}
cacheSqlSegment = true;
if (normal.isEmpty()) {
if (!groupBy.isEmpty() || !orderBy.isEmpty()) {
sqlSegment = groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment();
}
} else {
sqlSegment = normal.getSqlSegment() + groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment();
}
return sqlSegment;
}
}
三、Mybatis-Plus Lambda 表达式实战
01 环境准备
1. Maven 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
2. 实体(表)以及 Mapper 表映射文件
Base 实体
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@SuperBuilder(toBuilder = true)
@Data
public class BaseEntity {
@TableField(value = "created_tm", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdTm;
@TableField(value = "created_by", fill = FieldFill.INSERT)
private String createdBy;
@TableField(value = "modified_tm", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime modifiedTm;
@TableField(value = "modified_by", fill = FieldFill.INSERT_UPDATE)
private String modifiedBy;
}
用户账号实体:UserEntity
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PACKAGE)
@SuperBuilder(toBuilder = true)
@Data
@TableName("sys_user")
public class UserEntity extends BaseEntity{
private Long userId;
private String userName;
private Integer sex;
private Integer age;
private String mobile;
}
Mapper 操作类
List<UserDTO> selectUsers();
UserEntity selectByIdOnXml(long userId);
@Results(id = "userResult", value = {
@Result(property = "user_id", column = "userId", id = true),
@Result(property = "userName", column = "user_name"),
@Result(property = "sex", column = "sex"),
@Result(property = "mobile", column = "mobile"),
@Result(property = "age", column = "age")
})
@Select("select * from sys_user where user_id = #{id}")
UserEntity selectByIdOnSelectAnnotation(@Param("id") long id);
@SelectProvider(type = UserSqlProvider.class, method = "selectById")
@ResultMap("BaseResultMap")
UserEntity selectByIdOnSelectProviderAnnotation(long id);
@Select("select * from sys_user where user_id = #{id} and user_name=#{userName}")
@ResultMap("BaseResultMap")
UserEntity selectByIdOnParamAnnotation(@Param("id") long id, @Param("userName") String uerName);
Mapper 表映射文件
<mapper namespace="com.dunzung.mybatisplus.query.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.dunzung.mybatisplus.query.entity.UserEntity">
<id column="user_id" property="userId"/>
<result column="user_name" property="userName"/>
<result column="sex" property="sex"/>
<result column="age" property="age"/>
<result column="mobile" property="mobile"/>
</resultMap>
<resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap">
<association property="card" column="{userId,user_id}"
select="com.dunzung.mybatisplus.query.mapper.CardMapper.selectCardByUserId"/>
<collection property="orders" column="{userId,user_id}"
select="com.dunzung.mybatisplus.query.mapper.OrderMapper.selectOrders"/>
</resultMap>
<select id="selectUsers" resultMap="RelationResultMap">
select * from sys_user
</select>
<select id="selectByIdOnXml" resultMap="BaseResultMap">
select * from sys_user where user_id = #{userId}
</select>
</mapper>
订单实体:OrderEntity
@Data
@TableName("sys_user_card")
public class CardEntity {
private Long cardId;
private String cardCode;
private Long userId;
}
Mapper 操作类
@Mapper
public interface OrderMapper extends BaseMapper<OrderEntity> {
}
Mapper 表映射文件
<mapper namespace="com.dunzung.mybatisplus.query.mapper.OrderMapper">
<resultMap id="BaseResultMap" type="com.dunzung.mybatisplus.query.entity.OrderEntity">
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
<result column="user_id" property="userId"/>
<result column="price" property="price"/>
<result column="created_tm" property="createdTm"/>
</resultMap>
<select id="selectOrders" resultMap="BaseResultMap">
select * from biz_order where user_id = #{userId}
</select>
</mapper>
身份证实体:CardEntity
@Data
@TableName("biz_order")
public class OrderEntity {
private Long orderId;
private String orderName;
private Integer userId;
private Date createdTm;
private Integer price;
}
Mapper 操作类
@Mapper
public interface CardMapper extends BaseMapper<CardEntity> {
}
Mapper 表映射文件
<mapper namespace="com.dunzung.mybatisplus.query.mapper.CardMapper">
<resultMap id="BaseResultMap" type="com.dunzung.mybatisplus.query.entity.CardEntity">
<id column="card_id" property="cardId"/>
<result column="card_code" property="cardCode"/>
<result column="user_id" property="userId"/>
</resultMap>
<select id="selectCardByUserId" resultMap="BaseResultMap">
select * from sys_user_card where user_id = #{userId}
</select>
</mapper>
02 Lambda 基础篇
lambda 构建复杂的查询条件构造器:LambdaQueryWrapper
LambdaQueryWrapper 四种不同的 lambda 构造方法
方式一 使用 QueryWrapper
的成员方法方法lambda
构建LambdaQueryWrapper
LambdaQueryWrapper<UserEntity> lambda = new QueryWrapper<UserEntity>().lambda();
方式二 直接 new
出LambdaQueryWrapper
LambdaQueryWrapper<UserEntity> lambda = new LambdaQueryWrapper<>();
方式三 使用 Wrappers
的静态方法lambdaQuery
构建LambdaQueryWrapper
推荐
LambdaQueryWrapper<UserEntity> lambda = Wrappers.lambdaQuery();
方式四:链式查询
List<UserEntity> users = new LambdaQueryChainWrapper<UserEntity>(userMapper)
.like(User::getName, "雨").ge(User::getAge, 20).list();
笔者推荐使用 Wrappers
的静态方法 lambdaQuery
构建 LambdaQueryWrapper
条件构造器。
Debug 调试
为了 Debug
调试方便,需要在 application.yml
启动文件开启 Mybatis-Plus SQL
执行语句全栈打印:
#mybatis
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
执行效果如下:
1 等值查询:eq
@Test
public void testLambdaQueryOfEq() {
//eq查询
//相当于 select * from sys_user where user_id = 1
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getUserId, 1L);
UserEntity user = userMapper.selectOne(lqw);
System.out.println("eq查询::" + user.getUserName());
}
eq
查询等价于原生 sql
的等值查询。
select * from sys_user where user_id = 1
2 范围查询 :in
@Test
public void testLambdaQueryOfIn() {
List<Long> ids = Arrays.asList(1L, 2L);
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.in(UserEntity::getUserId, ids);
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("in查询::" + u.getUserName()));
}
in
查询等价于原生 sql
的 in
查询
select * from sys_user where user_id in (1,2)
3 通配符模糊查询:like
@Test
public void testLambdaQueryOfLikeAll() {
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.like(UserEntity::getUserName, "dun");
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like全包含关键字查询::" + u.getUserName()));
}
like
查询等价于原生 sql
的 like
全通配符模糊查询。
select * from sys_user where sex = 0 and user_name like '%dun%'
4 右通配符模糊查询:likeRight
@Test
public void testLambdaQueryOfLikeRight() {
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.likeRight(UserEntity::getUserName, "dun");
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like Right含关键字查询::" + u.getUserName()));
}
likeRight
查询相当于原生 sql
的 like
右通配符模糊查询。
select * from sys_user where sex = 0 and user_name like 'dun%'
5 左通配符模糊查询:likeLeft
@Test
public void testLambdaQueryOfLikeLeft() {
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.likeLeft(UserEntity::getUserName, "zung");
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like Left含关键字查询::" + u.getUserName()));
}
likeLeft
查询相当于原生 sql
的 like
左通配符模糊查询。
select * from sys_user where sex = 0 and user_name like '%zung'
6 条件判断查询
条件判断查询类似于 Mybatis
的 if
标签,第一个入参 boolean condition
表示该条件是否加入最后生成的 sql
中。
@Test
public void testLambdaQueryOfBoolCondition() {
UserEntity condition = UserEntity.builder()
.sex(1)
.build();
//eq 或 like 条件判断查询
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(condition.getSex() != null, UserEntity::getSex, 0L)
// 满足 bool 判断,是否进查询按字段 userName 查询
.like(condition.getUserName() != null, UserEntity::getUserName, "dun");
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like查询::" + u.getUserName()));
}
7 利用 or 和 and 构建复杂的查询条件
@Test
public void testLambdaQueryOfOr_And() {
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.eq(UserEntity::getSex, 0L)
.and(wrapper->wrapper.eq(UserEntity::getUserName,"dunzung")
.or().ge(UserEntity::getAge, 50));
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("like查询::" + u.getUserName()));
}
上面实例查询等价于原生 sql
查询:
select * from sys_user where sex = 0 and (use_name = 'dunzung' or age >=50)
8 善于利用分页利器 PageHelpler
@Test
public void testLambdaPage() {
//PageHelper分页查询
//相当于 select * from sys_user limit 0,2
int pageNumber = 0;
int pageSize = 2;
PageHelper.startPage(pageNumber + 1, pageSize);
LambdaQueryWrapper<UserEntity> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(UserEntity::getAge)
.orderByDesc(UserEntity::getMobile);
List<UserEntity> userList = userMapper.selectList(lqw);
userList.forEach(u -> System.out.println("page分页查询::" + u.getUserName()));
}
上面实例查询等价于原生 sql
分页查询:
select * from sys_user order by age desc,mobile desc limit 0,2
另外,Mybatis-Plus
自带分页组件,BaseMapper
接口提供两种分页方法来实现物理分页。
第一个返回实体对象允许 null
第二个人返回 map
对象多用于在指定放回字段时使用,避免为指定字段null
值出现
IPage<T> selectPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param("ew") Wrapper<T> queryWrapper);
注意,Mybatis-Plus
自带分页组件时,需要配置 PaginationInterceptor
分页插件。
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
9 更新条件构造器:LambdaUpdateWrapper
@Test
public void testLambdaUpdate() {
LambdaUpdateWrapper<UserEntity> luw = Wrappers.lambdaUpdate();
luw.set(UserEntity::getUserName, "dunzung01")
.set(UserEntity::getSex, 1);
luw.eq(UserEntity::getUserId, 1);
userMapper.update(null, luw);
}
03 进阶篇
1. Association
Association
标签适用于表和表之间存在一对一的关联关系,如用户和身份证存在一个人只会有一个身份证号,反过来也成立。
@Test
public void testOnAssociationTag() {
List<UserDTO> userList = userMapper.selectUsers();
userList.forEach(u -> System.out.println(u.getUserName()));
}
XML配置
<resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap">
<association property="card" column="{userId,user_id}"
select="com.dunzung.mybatisplus.query.mapper.CardMapper.selectCardByUserId"/>
</resultMap>
2. Collection
Collection
标签适用于表和表之间存在一对多的关联关系,如用户和订单存在一个人可以购买多个物品,产生多个购物订单。
@Test
public void testOnCollectionTag() {
List<UserDTO> userList = userMapper.selectUsers();
userList.forEach(u -> System.out.println(u.getUserName()));
}
XML配置
<resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap">
<collection property="orders"
column="{userId,user_id}"
select="com.dunzung.mybatisplus.query.mapper.OrderMapper.selectOrders"/>
</resultMap>
注意 Association
和 Collection
先后关系,在编写 ResultMap
时,association
在前,collection
标签在后。
<resultMap id="RelationResultMap" type="com.dunzung.mybatisplus.query.entity.UserDTO" extends="BaseResultMap">
<association property="card" column="{userId,user_id}"
select="com.dunzung.mybatisplus.query.mapper.CardMapper.selectCardByUserId"/>
<collection property="orders" column="{userId,user_id}"
select="com.dunzung.mybatisplus.query.mapper.OrderMapper.selectOrders"/>
</resultMap>
如果二者颠倒顺序会提示错误。
3. 元对象字段填充属性值:MetaObjectHandler
MetaObjectHandler
元对象字段填充器的填充原理是直接给 entity
的属性设置值,提供默认方法的策略均为:
“如果属性有值则不覆盖,如果填充值为
null
则不填充,字段必须声明TableField
注解,属性fill
选择对应策略,该声明告知Mybatis-Plus
需要预留注入SQL
字段。TableField
注解则是指定该属性在对应情况下必有值,如果无值则入库会是null
。
自定义填充处理器 MyMetaObjectHandler
在 Spring Boot
中需要声明 @Component
或 @Bean
注入,要想根据注解 FieldFill.xxx
,如:
@TableField(value = "created_tm", fill = FieldFill.INSERT)
private LocalDateTime createdTm;
@TableField(value = "modified_tm", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime modifiedTm;
和字段名以及字段类型来区分必须使用父类的 setInsertFieldValByName
或者 setUpdateFieldValByName
方法,不需要根据任何来区分可以使用父类的 setFieldValByName
方法 。
/**
* 属性值填充 Handler
*
* @author 猿芯
* @since 2021/3/30
*/
@Component
public class FillMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.setInsertFieldValByName("createdTm", LocalDateTime.now(), metaObject);
this.setInsertFieldValByName("createdBy", MvcContextHolder.getUserName(), metaObject);
this.setFieldValByName("modifiedTm", LocalDateTime.now(), metaObject);
this.setFieldValByName("modifiedBy", MvcContextHolder.getUserName(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setUpdateFieldValByName("modifiedTm", LocalDateTime.now(), metaObject);
this.setUpdateFieldValByName("modifiedBy", MvcContextHolder.getUserName(), metaObject);
}
}
一般 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
语句。
注解方式
@Select("select * from mysql_data ${ew.customSqlSegment}")
List<MysqlData> getAll(@Param(Constants.WRAPPER) Wrapper wrapper);
XML配置
List<MysqlData> getAll(Wrapper ew);
<select id="getAll" resultType="MysqlData">
SELECT * FROM mysql_data ${ew.customSqlSegment}
</select>
四、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
都是可以的。
参考
https://mp.baomidou.com/guide/wrapper.html https://www.jianshu.com/p/613a6118e2e0 https://blog.csdn.net/Solitude_w/article/details/108235236 https://blog.csdn.net/weixin_44472810/article/details/105649901 https://blog.csdn.net/weixin_44495678/article/details/106748214
2021-04-06
2021-04-02
2021-03-29