MyBatis动态SQL与MyBatis-Plus高效开发指南

文章目录

  • [1. 动态SQL](#1. 动态SQL)
    • [1.1 <if>标签](#1.1 标签)
    • [1.2 <trim>标签](#1.2 标签)
    • [1.3 <where>标签](#1.3 标签)
    • [1.4 <set>标签](#1.4 标签)
    • [1.5 <foreach>标签](#1.5 标签)
    • [1.6 <include>标签](#1.6 标签)
  • [2. MyBatis-Plus](#2. MyBatis-Plus)
  • [3. MyBatis-Plus复杂操作](#3. MyBatis-Plus复杂操作)
    • [3.1 常见注解](#3.1 常见注解)
      • [3.1.1 @TableName](#3.1.1 @TableName)
      • [3.1.2 @TableField](#3.1.2 @TableField)
      • [3.1.3 @TableId](#3.1.3 @TableId)
    • [3.2 打印日志](#3.2 打印日志)
    • [3.3 条件构造器](#3.3 条件构造器)
      • [3.3.1 QueryWrapper](#3.3.1 QueryWrapper)
    • [3.3.2 UpdateWrapper](#3.3.2 UpdateWrapper)
      • [3.3.3 LambdaQueryWrapper](#3.3.3 LambdaQueryWrapper)
      • [3.3.4 LambdaUpdateWrapper](#3.3.4 LambdaUpdateWrapper)

1. 动态SQL

动态 SQL 是Mybatis的强大特性之一,能够完成不同条件下不同的 sql 拼接

1.1 <if>标签

可以用if标签来实现非必填字段

比如性别 gender 为非必填字段

接口定义

java 复制代码
Integer insertUserByCondition(Integer gender);

Xml实现

xml 复制代码
 <insert id="insertUserByCondition">
        INSERT into userinfo (
        username, password, age,
        <if test="gender != null">
            gender,
        </if>
        phone)
        VALUES (
        #{username}, #{password}, #{age},
        <if test="gender != null">
            #{gender},
        </if>
        #{phone})
    </insert>

或者使用注解方式, 但并不推荐, 感兴趣可自行了解

test标签中的gender字段, 是传入对象的属性, 要跟Java对象的属性 对应, 而不是数据库的字段

不可以不判断直接把字段设置为null, 这种情况下, 如果gender字段有默认值, 就会设置为默认值

1.2 <trim>标签

之前的插入用户功能,只是有一个 gender 字段可能是选填项,如果有多个字段,一般考虑使用标签结合标签,对多个字段都采取动态生成的方式.

  1. prefix:表示整个语句块,以prefix的值作为前缀
  2. suffix:表示整个语句块,以suffix的值作为后缀
  3. prefixOverrides:表示整个语句块要去除掉的前缀
  4. suffixOverrides:表示整个语句块要去除掉的后缀
xml 复制代码
<insert id="insertUserByCondition">
    INSERT INTO userinfo
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="username !=null">
            username,
        </if>
        <if test="password !=null">
            `password`, 
        </if>
        <if test="age != null">
            age,
        </if>
        <if test="gender != null">
            gender,
        </if>
        <if test="phone != null">
            phone,
        </if>
    </trim>
    VALUES
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="username !=null">
            #{username},
        </if>
        <if test="password !=null">
            #{password},
        </if>
        <if test="age != null">
            #{age},
        </if>
        <if test="gender != null">
            #{gender},
        </if>
        <if test="phone != null">
            #{phone}
        </if>
    </trim>
    </insert>

以上的sql,

基于 prefix 配置,开始部分加上 (

基于 suffix 配置,结束部分加上 )

多个 组织的语句都以 , 结尾,在最后拼接好的字符串还会以 , 结尾,会基于suffixOverrides 配置去掉最后一个, 注意suffixOverrides 只能去掉末尾的逗号, 如果末尾不是, suffixOverrides不会做处理

1.3 <where>标签

需求: 传入的用户对象,根据属性做where条件查询,用户对象中属性不为 null 的,都为查询条件.

如:

username 为 "a",则查询条件为 where username="a"

每个条件前面都写 AND,where标签会自动去掉第一个 AND,其他的保留

java 复制代码
List<UserInfo> queryByCondition(UserInfo userInfo);
xml 复制代码
<select id="queryByCondition" resultType="com.example.test.demos.dao.UserInfo">
        select id, username, age, gender, phone, delete_flag, create_time,
               update_time
        from userinfo
        <where>
            <if test="age != null">
                and age = #{age}
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="deleteFlag != null">
                and delete_flag = #{deleteFlag}
            </if>
        </where>
    </select>

注意语句中数据库字段和Java对象字段的映射关系

1.4 <set>标签

需求: 根据传入的用户对象属性来更新用户数据,可以使用标签来指定动态内容

接口定义: 根据传入的用户 id 属性,修改其他不为 null 的属性

java 复制代码
Integer updateUserByCondition(UserInfo userInfo);
xml 复制代码
<update id="updateUserByCondition">
        <set>
            <if test="username != null">
                username = #{username}
            </if>
            <if test="age != null">
                age = #{age},
            </if>
            <if test="deleteFlag != null">
                delete_flag = #{deleteFlag},
            </if>
        </set>
        where id = #{id}
    </update>

<set> :动态的在SQL语句中插入set关键字,并会删掉额外的逗号. (用于update语句中) 以上标签也可以使用 <trim prefix="set" suffixOverrides=","> 替换

用if标签也能实现, 但是需要处理边界情况和多出来逗号, 很麻烦

1.5 <foreach>标签

对集合进行遍历时可以使用该标签。标签有如下属性:

  1. collection:绑定方法参数中的集合,如 List,Set,Map或数组对象
  2. item:遍历时的每一个对象
  3. open:语句块开头的字符串
  4. close:语句块结束的字符串
  5. separator:每次遍历之间间隔的字符串

接口定义: 根据多个userid, 删除用户数据

java 复制代码
void deleteByIds(@Param("ids") List<Integer> ids);

XML实现

xml 复制代码
  <delete id="deleteByIds">
        delete from userinfo
        where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>

形参如果是多个集合, 需要用@Param, 否则会报错, 用了@Param后, collection的值要和@param中写的一致

1.6 <include>标签

在xml映射文件中配置的SQL,有时可能会存在很多重复的片段,此时就会存在很多冗余的代码

我们可以对重复的代码片段进行抽取,将其通过 <sql> 标签封装到一个SQL片段,然后再通过<include> 标签进行引用

<sql> :定义可重用的SQL片段

<include> :通过属性refid,指定包含的SQL片段

通过<sql>定义重复SQL片段

xml 复制代码
    <sql id="allColumn">
        id, username, age, gender, phone, delete_flag, create_time, update_time
    </sql>

通过 <include> 标签在原来抽取的地方进行引用

xml 复制代码
    <select id="queryAllUser" resultMap="BaseMap">
        select
        <include refid="allColumn"></include>
        from userinfo
    </select>

2. MyBatis-Plus

MyBatis-Plus(简称 MP) 是一个 MyBatis 的增强工具, 在 MyBatis 的基础上只做增强不做改变, 为简化开发. 提高效率而生。

MybatisPlus提供了一个基础的 BaseMapper 接口,已经实现了单表的CRUD, 我们自定义的Mapper只需要继承这个BaseMapper, 就无需自己实现单表CRUD

java 复制代码
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {}

UserInfoMapper 在继承 BaseMapper 时, 指定了一个泛型, 这个UserInfo就是与数据库表相对应的实体类

默认情况下:

  1. 表名: 实体类的驼峰表示法转换成蛇形表示法(下划线分割), 作为表名. 比如UserInfo -> user_info
  2. 字段: 根据实体类的属性名, 转换为蛇形表示法作为字段名. 比如deleteFlag -> delete_flag
  3. 主键: 默认为id

3. MyBatis-Plus复杂操作

3.1 常见注解

3.1.1 @TableName

我们可以通过 @TableName 来标识实体类对应的表

java 复制代码
@Data
@TableName("user_info")
public class UserInfo {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private Integer gender;
    private String phone;
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
}

3.1.2 @TableField

我们可以通过 @TableField 来标识 对应的字段名

java 复制代码
@Data
@TableName("user_info")
public class UserInfo {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private Integer gender;
    private String phone;
    @TableField("delete_flag")
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;
    
}

3.1.3 @TableId

我们可以通过 @TableId 来 指定对应的主键

java 复制代码
@Data
@TableName("user_info")
public class UserInfo {
    @TableId("id")
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private Integer gender;
    private String phone;
    @TableField("delete_flag")
    private Integer deleteFlag;
    private Date createTime;
    private Date updateTime;

}

如果属性名和字段名不一致, 需要在 @TableId 指明对应的字段名;

属性名和字段一致的情况下, 直接加 @TableId 注解就可以

3.2 打印日志

Mybatis-Plus配置日志如下:

yaml 复制代码
mybatis-plus:
  configuration: # 配置打印 MyBatis日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.3 条件构造器

入门程序里的使用, 都是简单的CRUD, 在实际的应用场景中, 我们还需要使用更复杂的操作, MyBatisPlus 也给我们提供了相应的支持, MyBatis-Plus 提供了一套强大的条件构造器(Wrapper) , 用于构建复杂的数据库查询条件. Wrapper 类 允许开发者以链式调用的方式构造查询条件, 无需编写繁琐的 SQL 语句, 从而提高开发效率并减少 SQL注入 的风险

以下是主要的 Wrapper 类及其功能:

  1. AbstractWrapper:这是一个抽象基类, 提供了所有 Wrapper 类共有的方法和属性.
  2. QueryWrapper:用于构造查询条件, 在AbstractWrapper的基础上拓展了一个select方法, 允许指定查询字段
  3. UpdateWrapper: 用于构造更新条件, 可以在更新数据时指定条件
  4. LambdaQueryWrapper :基于 Lambda 表达式的查询条件构造器, 它通过 Lambda 表达式来引用实体类的属性,从而避免了硬编码字段名
  5. LambdaUpdateWrapper: 基于 Lambda 表达式的更新条件构造器, 它允许你使用 Lambda 表达式来指定更新字段和条件,同样避免了硬编码字段名的问题

LambdaQueryWrapper和LambdaUpdateWrapper是能完全替代QueryWrapper和UpdateWrapper的, 而且也更推荐使 lambdaWrapper

3.3.1 QueryWrapper

QueryWrapper并不只用于查询语句, 无论是修改, 删除, 查询, 都可以使用QueryWrapper来构建查询条件这里只简单介绍查询和更新

sql 复制代码
SELECT id,username,password,age FROM user_info WHERE age = 18 AND username "%min%"
java 复制代码
    void testQueryWrapper() {
        QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<UserInfo>()
                .select("id", "username", "password", "age")
                .eq("age", 18)
                .like("username", "min");
        List<UserInfo> userInfos = userInfoMapper.selectList(userInfoQueryWrapper);
    }

QueryWrapper并不只用于查询语句, 无论是修改, 删除, 查询, 都可以使用QueryWrapper来构建查询条件这里只简单介绍查询和更新

sql 复制代码
UPDATE user_info SET delete_flag=? WHERE age < 20
java 复制代码
    void testUpdateByQueryWrapper(){
        QueryWrapper<UserInfo> userInfoQueryWrapper = new QueryWrapper<UserInfo>()
                .lt("age", 20);
        UserInfo userInfo = new UserInfo();
        userInfo.setDeleteFlag(1);
        userInfoMapper.update(userInfo, userInfoQueryWrapper);
    }

• lt : "less than" 的缩写,表示小于.

• le : "less than or equal to"的缩写, 表示小于等于

• ge : "greater than or equal to" 的缩写, 表示大于等于.

• gt : "greater than" 的缩写, 表示大于.

• eq : "equals" 的缩写, 表示等于.

• ne : "not equals" 的缩写, 表示不等于.

3.3.2 UpdateWrapper

对于更新, 我们也可以直接使用 UpdateWrapper, 在不创建实体对象的情况下, 直接设置更新字段和条件

sql 复制代码
UPDATE user_info SET delete_flag=0, age=5 WHERE id IN (1,2,3)
java 复制代码
void testUpdateByUpdateWrapper(){
        UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<UserInfo>()
                .set("delete_flag",0)
                .set("age", 5)
                .in("id", List.of(1,2,3));
        userInfoMapper.update(updateWrapper);
    }

QueryWrapper 和 UpdateWrapper存在一个问题, 就是需要写死字段名, 如果字段名发生变更, 可能会因为测试不到位导致出错;

MyBatis-Plus 给我们提供了一种基于Lambda表达式的条件构造器, 它通过 Lambda 表达式来引用实体类的属性,从而避免了硬编码字段名, 也提高了代码的可读性和可维护性

3.3.3 LambdaQueryWrapper

java 复制代码
void testLambdaQueryWrapper() {
        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<UserInfo>();
        queryWrapper.lambda()
                .select(UserInfo::getUsername, UserInfo::getPassword, UserInfo::getAge)
                .eq(UserInfo::getId, 1);
        userInfoMapper.selectList(queryWrapper);
    }

3.3.4 LambdaUpdateWrapper

java 复制代码
    void testLambdaUpdateWrapper() {
        UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<UserInfo>();
        updateWrapper.lambda()
                .set(UserInfo::getDeleteFlag, 0)
                .set(UserInfo::getAge, 5)
                .in(UserInfo::getId, List.of(1,2,3));
        userInfoMapper.update(updateWrapper);
    }
相关推荐
冰暮流星1 小时前
mysql之分组查询
数据库·mysql
倒流时光三十年1 小时前
PostgreSQL Semi-Join(半连接)通俗讲解
数据库·postgresql
NiceCloud喜云2 小时前
腾讯云国际版云数据库选型:MySQL、Redis 怎么按业务架构来判断
数据库·mysql·腾讯云
ew452182 小时前
MySQL JOIN 关联语法、差异、标准用法、经典坑点汇总
数据库·mysql
Rain的Java大神之路3 小时前
线上接口负载满了如何解决
java·数据库·redis·后端·缓存·面试·架构
Nturmoils3 小时前
给运营导个 CSV,在 ksql 里用 \copy 就够了
数据库
ciqingloveless3 小时前
Oracle打开数据库加密
数据库·oracle
m0_462605224 小时前
大模型week02
数据库
DBA_G4 小时前
GBase 8a数据库集群多维度资源管控策略矩阵解析
数据库·oracle