前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >肝九千字长文 | MyBatis-Plus 码之重器 lambda 表达式使用指南,开发效率瞬间提升80%

肝九千字长文 | MyBatis-Plus 码之重器 lambda 表达式使用指南,开发效率瞬间提升80%

作者头像
猿芯
发布于 2021-05-27 10:11:11
发布于 2021-05-27 10:11:11
2.6K00
代码可运行
举报
运行总次数:0
代码可运行

一、回顾

现在越来越流行基于 SpringBoot 开发 Web 应用,其中利用 Mybatis 作为数据库 CRUD 操作已成为主流。楼主以 MySQL 为例,总结了九大类使用 Mybatis 操作数据库 SQL 小技巧分享给大家。

  1. 分页查询
  2. 预置 sql 查询字段
  3. 一对多级联查询
  4. 一对一级联查询
  5. foreach 搭配 in 查询
  6. 利用if 标签拼装动态 where 条件
  7. 利用 chooseotherwise组合标签拼装查询条件
  8. 动态绑定查询参数:_parameter
  9. 利用 set 配合 if 标签,动态设置数据库字段更新值

01 分页查询

利用 limit 设置每页 offset 偏移量和每页 size 大小。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 查询字段

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<sql id="columns">
  id,title,content,original_img,is_user_edit,province_id,status,porder
</sql>

查询 select 语句引用 columns:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<select id="selectById" resultMap="RM_MsShortcutPanel">
 seelct
 <include refid="columns"/>
 from cms_self_panel
 where
 id = #{_parameter}
</select>

03 一对多级联查询

利用 mybatiscollection 标签,可以在每次查询文章主体同时通过 queryparaminstancelist 级联查询出关联表数据。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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>

queryparaminstancelistsql 语句

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<select id="queryparaminstancelist" resultMap="ParamInstanceResultMap">
 select * from `cms_article_flow_param_instance` where article_id=#{id} 
</select>

04 一对一级联查询

利用 mybatisassociation 标签,一对一查询关联表数据。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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 查询条件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
 #{item}
</foreach>

06 利用 if 标签拼装动态 where 条件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 组合标签拼装查询条件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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 参数的含义

“当 Mapperassociationcollection 指定只有一个参数时进行查询时,可以使用 _parameter,它就代表了这个参数。

另外,当使用 Mapper指定方法使用 @Param 的话,会使用指定的参数值代替。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 标签,动态设置数据库字段更新值

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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 语句的调优比较麻烦。
  • Hibernatehql 数据库移植性比 Mybatis 更好,Hibernate 的底层对 hql 进行了处理,对于数据库的兼容性更好,
  • Mybatis 直接写的原生 sql 都是与数据库相关,不同数据库 sql 不同,这时就需要多套 sql 映射文件。
  • Hibernate 在级联删除的时候效率低;数据量大, 表多的时候,基于关系操作会变得复杂。
  • MybatisHibernate 都可以使用第三方缓存,而 Hibernate 相比 Mybatis 有更好的二级缓存机制。

为什么要选择 Lambda 表达式?

Mybatis-Plus 的存在就是为了稍稍弥补 Mybatis 的不足。

在我们使用 Mybatis 时会发现,每当要写一个业务逻辑的时候都要在 DAO 层写一个方法,再对应一个 SQL,即使是简单的条件查询、即使仅仅改变了一个条件都要在 DAO层新增一个方法,针对这个问题,Mybatis-Plus 就提供了一个很好的解决方案:lambda 表达式,它可以让我们避免许多重复性的工作。

想想 Mybatis 官网提供的 CRUD 例子吧,基本上 xml 配置占据了绝大部分。而用 Lambda 表达式写的 CRUD 代码非常简洁,真正做到零配置,不需要在 xml 或用注解(@Select)写大量原生 SQL 代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

平常我们执行一个 Thread 线程:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
new Thread(new Runnable() {
  @Override
  public void run() {
      System.out.println("xxxx");
  }
}).start();

如果用 lambda 会非常简洁,一行代码搞定。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 new Thread(()-> System.out.println("xxx")).start();

所以在某些场景下使用 lambda 表达式真的能减少 java 中一些冗长的代码,增加代码的优雅性。

lambda 条件构造器基础类:包装器模式(装饰模式)之 AbstractWrapper

AbstractWrapper 条件构造器说明

  1. 出现的第一个入参 boolean condition 表示该条件是否加入最后生成的 sql 中,例如:query.like(StringUtils.isNotBlank(name), Entity::getName, name) .eq(age!=null && age >= 0, Entity::getAge, age)
  2. 代码块内的多个方法均为从上往下补全个别 boolean 类型的入参,默认为 true
  3. 出现的泛型 Param 均为 Wrapper 的子类实例(均具有 AbstractWrapper 的所有方法)
  4. 方法在入参中出现的 R 为泛型,在普通 wrapper 中是 String ,在 LambdaWrapper 中是函数(例:Entity::getIdEntity 为实体类,getId为字段idgetMethod)
  5. 方法入参中的 R column 均表示数据库字段,当 R 具体类型为 String 时则为数据库字段名(字段名是数据库关键字的自己用转义符包裹!)!而不是实体类数据字段名!!!,另当 R 具体类型为 SFunction 时项目 runtime 不支持 eclipse 自家的编译器!
  6. 使用普通 wrapper,入参为 MapList 的均以 json 形式表现!
  7. 使用中如果入参的 Map 或者 List为空,则不会加入最后生成的 sql 中!

警告:

不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输。

Wrapper 很重 传输 Wrapper 可以类比为你的 controllermap 接收值(开发一时爽,维护火葬场) 正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作 我们拒绝接受任何关于 RPC 传输 Wrapper 报错相关的 issue 甚至 pr

AbstractWrapper 内部结构

从上图,我们了解到 AbstractWrapper 的实际上实现了五大接口:

  • SQL 片段函数接口:ISqlSegment
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@FunctionalInterface
public interface ISqlSegment extends Serializable {
    /**
     * SQL 片段
     */
    String getSqlSegment();
}

  • 比较值接口 Compare<Children, R>,如 等值 eq、不等于:ne、大于 gt、大于等于:ge、小于 lt、小于等于 lebetween、模糊查询:like 等等
  • 嵌套接口 Nested<Param, Children> ,如 andor
  • 拼接接口 Join<Children>,如 orexists
  • 函数接口 Func<Children, R>,如 in 查询、groupby 分组、havingorder by排序等

常用的 where 条件表达式 eq、like、in、ne、gt、ge、lt、le

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 中的每个条件片段进行组装。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 对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 方法,其中代码片段

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sqlSegment = normal.getSqlSegment() + groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment()

这段代码表明,一条完整的 where 条件 SQL 语句,最终由 normal SQL 片段,groupBy SQL 片段,having SQL 片段,orderBy SQL 片段拼接而成。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

@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 依赖

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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 实体
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 操作类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 表映射文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Data
@TableName("sys_user_card")
public class CardEntity {
    private Long cardId;
    private String cardCode;
    private Long userId;
}

Mapper 操作类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Mapper
public interface OrderMapper extends BaseMapper<OrderEntity> {
}

Mapper 表映射文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Data
@TableName("biz_order")
public class OrderEntity {
    private Long orderId;
    private String orderName;
    private Integer userId;
    private Date createdTm;
    private Integer price;
}

Mapper 操作类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Mapper
public interface CardMapper extends BaseMapper<CardEntity> {
}

Mapper 表映射文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
LambdaQueryWrapper<UserEntity> lambda = new QueryWrapper<UserEntity>().lambda();
  • 方式二 直接 newLambdaQueryWrapper
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
LambdaQueryWrapper<UserEntity> lambda = new  LambdaQueryWrapper<>();
  • 方式三 使用 Wrappers 的静态方法 lambdaQuery 构建 LambdaQueryWrapper 推荐
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
LambdaQueryWrapper<UserEntity> lambda = Wrappers.lambdaQuery();
  • 方式四:链式查询
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 执行语句全栈打印:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#mybatis
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

执行效果如下:

1 等值查询:eq
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 的等值查询。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
select * from sys_user where user_id = 1
2 范围查询 :in
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 查询等价于原生 sqlin 查询

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
select * from sys_user where user_id in (1,2)  
3 通配符模糊查询:like
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 查询等价于原生 sqllike 全通配符模糊查询。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
select * from sys_user where sex = 0 and user_name like '%dun%'
4 右通配符模糊查询:likeRight
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 查询相当于原生 sqllike 右通配符模糊查询。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
select * from sys_user where sex = 0 and user_name like 'dun%'
5 左通配符模糊查询:likeLeft
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 查询相当于原生 sqllike 左通配符模糊查询。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
select * from sys_user where sex = 0 and user_name like '%zung'
6 条件判断查询

条件判断查询类似于 Mybatisif 标签,第一个入参 boolean condition 表示该条件是否加入最后生成的 sql 中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 构建复杂的查询条件
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 查询:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
select * from sys_user where sex = 0 and (use_name = 'dunzung' or age >=50)
8 善于利用分页利器 PageHelpler
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 分页查询:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
select * from sys_user order by age desc,mobile desc limit 0,2

另外,Mybatis-Plus 自带分页组件,BaseMapper 接口提供两种分页方法来实现物理分页。

  • 第一个返回实体对象允许 null
  • 第二个人返回 map 对象多用于在指定放回字段时使用,避免为指定字段 null 值出现
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 分页插件。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Bean
public PaginationInterceptor paginationInterceptor() {
    return new PaginationInterceptor();
}

9 更新条件构造器:LambdaUpdateWrapper

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 标签适用于表和表之间存在一对一的关联关系,如用户和身份证存在一个人只会有一个身份证号,反过来也成立。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Test
public void testOnAssociationTag() {
    List<UserDTO> userList = userMapper.selectUsers();
    userList.forEach(u -> System.out.println(u.getUserName()));
}

XML配置

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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 标签适用于表和表之间存在一对多的关联关系,如用户和订单存在一个人可以购买多个物品,产生多个购物订单。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Test
public void testOnCollectionTag() {
    List<UserDTO> userList = userMapper.selectUsers();
    userList.forEach(u -> System.out.println(u.getUserName()));
}

XML配置

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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>

注意 AssociationCollection 先后关系,在编写 ResultMap 时,association 在前,collection 标签在后。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 <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

自定义填充处理器 MyMetaObjectHandlerSpring Boot 中需要声明 @Component@Bean 注入,要想根据注解 FieldFill.xxx,如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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 方法 。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 属性值填充 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.INSERTFieldFill.INSERT_UPDATE 任何时候不起作用,用父类的 setFieldValByName 设置属性(创建人、创建时间、修改人、修改时间)值即可。

4. 自定义SQL

使用 Wrapper 自定义 SQL 需要 mybatis-plus 版本 >= 3.0.7param 参数名要么叫 ew,要么加上注解 @Param(Constants.WRAPPER) ,使用 ${ew.customSqlSegment} 不支持 Wrapper 内的 entity生成 where 语句。

注解方式

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Select("select * from mysql_data ${ew.customSqlSegment}")
List<MysqlData> getAll(@Param(Constants.WRAPPER) Wrapper wrapper);

XML配置

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 表达式优点:

  1. 单表操作,代码非常简洁,真正做到零配置,如不需要在 xml 或用注解(@Select)写大量原生 SQL 代码
  2. 并行计算
  3. 预测代表未来的编程趋势

lambda 表达式缺点:

  1. 单表操作,对于多表关联查询支持不好
  2. 调试困难
  3. 底层逻辑复杂

五、总结

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-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构荟萃 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
HTTP状态码大全
HTTP状态码,即HTTP协议状态码,是我们访问网站时会遇到的,服务器端返回的Http响应码,不同的数字分别代表着不同的响应状态。我们在做SEO或做网页开发过程中需要了解5类比较重要的HTTP状态码,可以根据请求响应代码检查服务器及程序是否正常,判断网页处于什么工作状态。我们就需要了解不同的状态码分别是什么含义。
摘繁华
2022/09/16
2.5K0
HTTP1.1协议状态码
此类状态码仅由 状态行 和可选响应头组成的临时响应, 并以空行终止。此类状态码没有必需的标题。由于HTTP / 1.0没有定义任何1xx状态代码,因此服务器必须禁止向HTTP / 1.0客户端发送1xx响应。
星尘的一个朋友
2020/11/25
2.7K0
HTTP状态码列表
1xx消息——请求已被服务器接收,继续处理 2xx成功——请求已成功被服务器接收、理解、并接受 3xx重定向——需要后续操作才能完成这一请求 4xx请求错误——请求含有词法错误或者无法被执行 5xx服务器错误——服务器在处理某个正确请求时发生错误 100199:表示成功接收请求,要求客户端继续提交下一次请求才能完成整个处理过程。 200299:表示成功接收请求并已完成整个处理过程。常用200 300399:为完成请求,客户需进一步细化请求。例如:请求的资源已经移动一个新地址、常用302(意味着你请求我,我让你去找别人),307和304(我不给你这个资源,自己拿缓存) 400499:客户端的请求有错误,常用404(意味着你请求的资源在web服务器中没有)403(服务器拒绝访问,权限不够) 500~599:服务器端出现错误,常用500
Li_XiaoJin
2022/06/10
8450
看“猫”片, 学HTTP状态码
我们用浏览器访问网页时,浏览器会向网页所在服务器发出请求。服务器会返回一个包含HTTP状态码的信息头(server header)用以响应浏览器的请求。
zhaoolee
2019/02/26
2.2K0
⚡3分钟⚡熟悉面试常问状态码,面试官都听呆了
· 100 - Continue 初始的请求已经接受,客户应当继续发送请求的其余部分。(HTTP 1.1新) · 101 - Switching Protocols 服务器将遵从客户的请求转换到另外一种协议(HTTP 1.1新)
江一铭
2022/06/16
1.9K0
⚡3分钟⚡熟悉面试常问状态码,面试官都听呆了
HTTP状态码有哪些?「前端每日一题v22.11.9」
HTTP状态码表示超文本传输协议响应状态的3位数字代码。三位数字的第一个数字表示五种状态之一
FE情报局
2022/12/05
5510
HTTP状态码有哪些?「前端每日一题v22.11.9」
HTTP状态码及其含义
        开发Web项目的时候,因为环境搭建和配置的原因,经常会出现部署失败的情况;搭建这个博客的时候,也因为php-admin缺少某些库的原因,导致出现502的稀有的http状态。平时不是很留意这些状态码信息,也不是很了解出现这种状态的内在原因,每次出现都要查找。为了详细梳理和备忘,整理一份http状态码及其含义的解析。
王金龙
2018/09/05
1.8K0
HTTP状态码合集
请求的初始部分已收到,但尚未被服务器拒绝。在请求已完全收到并执行后,服务器打算发送最终响应。
BinGo_Blog
2022/11/01
1.3K0
HTTP响应状态码含义
1xx:信息 100 Continue 服务器仅接收到部分请求,但是一旦服务器并没有拒绝该请求,客户端应该继续发送其余的请求。
团团生活志
2022/08/16
1.3K0
C# HTTP系列5 HttpWebResponse.StatusCode属性
HttpWebResponse.StatusCode 属性获取响应的状态。对应 HttpStatusCode 枚举值之一。
张传宁IT讲堂
2019/09/17
2.3K0
聊一聊HTTP协议常见的状态码及含义
在测试的过程中会遇到多种协议,比如ARP地址解析协议,FTP文件传输协议,HTTP超文本传输协议,IP互联网协议,SMTP简单邮件传输协议,TCP传输控制协议,UDP用户数据报协议等等,今天聊一聊HTTP协议常见的状态码及含义。 1xx、2xx、3xx、4xx、5xx。每个类别下的具体状态码需要详细说明。比如1xx是信息性状态码,像100 Continue和101 Switching Protocols。2xx是成功,比如200 OK,201 Created,204 No Content。3xx是重定向,包括301、302、304等。4xx是客户端错误,比如404 Not Found,403 Forbidden。5xx是服务器错误,如500 Internal Server Error。
漫谈测试
2025/03/22
1500
聊一聊HTTP协议常见的状态码及含义
Ajax Status请求状态
  这篇文章主要介绍了各类Http请求状态(status)及其含义。   需要的朋友可以过来参考下,希望对大家有所帮助。Web服务器响应浏览器或其他客户程序的请求时,其应答一般由以下几个部分组成:一个状态行,几个应答头,一个空行,内容文档。下面是一个最简单的应答 : 状态行包含HTTP版本、状态代码、与状态代码对应的简短说明信息。   在大多数情况下,除了Content-Type之外的所有应答头都是可选的。但Content-Type是必需的,它描述的是后面文档的MIME类型。虽然大多数应答都包含一个文档,但也有一些不包含,例如对HEAD请求的应答永远不会附带文档。有许多状态代码实际上用来标识一次失败的请求,这些应答也不包含文档(或只包含一个简短的错误信息说明)。 当用户试图通过 HTTP 访问一台正在运行 Internet 信息服务 (IIS) 的服务器上的内容时,IIS 返回一个表示该请求的状态的数字代码。状态代码可以指明具体请求是否已成功,还可以揭示请求失败的确切原因。
Marco爱吃红烧肉
2021/07/23
1.9K0
网站状态码大全
404,503,301,网站状态码是最常见的,但是作为一个站长应该掌握更多网站状态码,以便应对网站出现的各种各样的错误,能更快修复错误。以下是杨小杰blog为大家整理的一些网站状态码。 100  Continue 初始的请求已经接受,客户应当继续发送请求的其余部分。(HTTP 1.1新) 101  Switching Protocols 服务器将遵从客户的请求转换到另外一种协议(HTTP 1.1新) 200  OK 一切正常,对GET和POST请求的应答文档跟在后面。
Youngxj
2018/06/07
1.5K0
RFC2616-HTTP1.1-Status Code(状态码规定部分—译文)
part of Hypertext Transfer Protocol -- HTTP/1.1
zaking
2018/08/13
9960
SIP协议学习笔记
友情提示:初次接触SIP(Session Initiation Protocol)协议的同学,强烈建议先将文末参考文章中的链接,先看至少二遍!
菩提树下的杨过
2020/12/07
2.3K0
SIP协议学习笔记
HTTP状态码大全
写在前面 我们在开发Web服务的时候,经常会遇到404,500等错误。对于初学者来说遇到错误,不知如何下手,今天我们来看看每个状态都表示什么意思? HTTP状态码(HTTP Status Code)是
互扯程序
2018/03/26
1.5K0
HTTP状态码大全
状态码在后端开发中常常遇到的场景
场景:在服务器还在处理请求时,客户端已经关闭了连接。 Nginx:当Nginx作为Web服务器正在处理请求,但客户端在响应发送前断开了连接,Nginx可能会记录499状态码。这种情况可能由网络问题、客户端程序错误或客户端机器故障引起。
GeekLiHua
2025/01/21
1330
喵星人教你 HTTP 状态码
HTTP 状态码(英语:HTTP Status Code)是用以表示 HTTP 响应状态的 3 位数字代码。比如:
HelloGitHub
2021/05/14
6900
http状态码
chimchim
2023/10/17
2690
关于常见状态码,你了解多少?
大家好,我是坚果,今天在逛github的时候发现一个文档HTTP 接口设计指北,顺便了解了一下,觉得不错,就把关于状态码的知识整理了一下。
徐建国
2022/03/30
8900
相关推荐
HTTP状态码大全
更多 >
LV.0
这个人很懒,什么都没有留下~
目录
  • 一、回顾
  • 01 分页查询
  • 02 预置 sql 查询字段
  • 03 一对多级联查询
  • 04 一对一级联查询
  • 05 foreach 搭配 in 查询
  • 06 利用 if 标签拼装动态 where 条件
  • 07 利用 choose 和 otherwise 组合标签拼装查询条件
  • 08 隐形绑定参数:_parameter
  • 09 利用 set 配合 if 标签,动态设置数据库字段更新值
  • 提问:#{} 和 ${} 的区别是什么?
  • 二、Mybatis-Plus Lambda 表达式理论篇
    • 背景
      • 题外话:Mybatis 和 Hibernate 的比较
      • 为什么要选择 Lambda 表达式?
    • lambda 表达式的理论基础
    • lambda 条件构造器基础类:包装器模式(装饰模式)之 AbstractWrapper
      • AbstractWrapper 条件构造器说明
      • AbstractWrapper 内部结构
      • SQL 片段函数接口
  • 三、Mybatis-Plus Lambda 表达式实战
    • 01 环境准备
      • 1. Maven 依赖
      • 2. 实体(表)以及 Mapper 表映射文件
    • 02 Lambda 基础篇
      • lambda 构建复杂的查询条件构造器:LambdaQueryWrapper
      • 9 更新条件构造器:LambdaUpdateWrapper
    • 03 进阶篇
      • 1. Association
      • 2. Collection
      • 3. 元对象字段填充属性值:MetaObjectHandler
      • 4. 自定义SQL
  • 四、Mybatis-Plus lambda 表达式的优势与劣势
  • 五、总结
    • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档