MyBatis动态SQL实战:增删改查全解析

在日常开发中,我们经常会遇到需要根据不同条件动态生成 SQL 的场景,比如多条件查询、选择性更新、批量操作等。MyBatis 提供的动态 SQL 标签正是解决这类问题的利器,它能让我们的 SQL 语句更加灵活、简洁,同时避免手动拼接 SQL 带来的安全隐患(如 SQL 注入)。本文将通过具体案例,详细讲解如何使用 MyBatis 动态标签实现完整的增删改查操作。

一、MyBatis 动态标签核心语法

MyBatis 动态 SQL 基于 XML 标签实现,常用标签包括:

标签作用适用场景
<if>条件判断,满足条件则拼接 SQL 片段多条件查询、选择性更新
<where>自动处理条件前缀的 AND/OR 关键字替代 WHERE 1=1 的冗余写法
<set>自动处理更新语句的逗号分隔符更新操作时排除空值字段
<choose>多条件分支判断(类似 Java 的 switch优先匹配条件的场景
<foreach>遍历集合或数组批量查询(IN)、批量插入
<trim>自定义字符串截取规则灵活处理前缀 / 后缀

这些标签可以组合使用,实现复杂的动态 SQL 逻辑。接下来我们通过实战案例学习它们的具体用法。

二、环境准备

在开始之前,我们需要准备基础环境:

  1. 数据库表结构(以用户表 user 为例):

sql

CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `age` int DEFAULT NULL COMMENT '年龄',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `status` tinyint DEFAULT 1 COMMENT '状态(1-正常,0-禁用)',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

  1. 实体类 User
public class User {
    private Integer id;
    private String username;
    private Integer age;
    private String email;
    private Integer status;
    private LocalDateTime createTime;
    
    // 省略 getter、setter 和构造方法
}

  1. Mapper 接口 UserMapper
public interface UserMapper {
    // 增删改查方法声明(后文逐步实现)
}

三、动态 SQL 实现增删改查实战

1. 动态查询(多条件筛选)

需求:实现一个用户列表查询接口,支持根据用户名模糊搜索、年龄筛选、状态筛选,条件为空时不参与过滤。

Mapper 接口方法

List<User> selectByCondition(User user);

XML 映射文件

xml

<select id="selectByCondition" resultType="User">
    SELECT id, username, age, email, status, create_time
    FROM user
    <where>
        <!-- 用户名模糊查询:不为 null 且不为空字符串时生效 -->
        <if test="username != null and username != ''">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <!-- 年龄筛选:不为 null 时生效 -->
        <if test="age != null">
            AND age = #{age}
        </if>
        <!-- 状态筛选:不为 null 时生效 -->
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
    ORDER BY create_time DESC
</select>

核心说明

  • 使用 <where> 标签替代传统的 WHERE 1=1,它会自动去除条件中多余的 AND/OR 关键字。
  • <if> 标签的 test 属性支持 OGNL 表达式,判断参数是否满足条件。
  • 模糊查询使用 CONCAT('%', #{username}, '%') 避免 SQL 注入(不推荐直接拼接字符串)。

2. 动态更新(选择性更新字段)

需求:实现用户信息更新接口,只更新传入了非空值的字段,未传入的字段保持不变。

Mapper 接口方法

int updateSelective(User user);

XML 映射文件

xml

<update id="updateSelective">
    UPDATE user
    <set>
        <!-- 用户名:不为 null 且不为空时更新 -->
        <if test="username != null and username != ''">
            username = #{username},
        </if>
        <!-- 年龄:不为 null 时更新 -->
        <if test="age != null">
            age = #{age},
        </if>
        <!-- 邮箱:不为 null 且不为空时更新 -->
        <if test="email != null and email != ''">
            email = #{email},
        </if>
        <!-- 状态:不为 null 时更新 -->
        <if test="status != null">
            status = #{status}
        </if>
    </set>
    WHERE id = #{id}
</update>

核心说明

  • <set> 标签会自动处理字段后的逗号,避免因最后一个字段带逗号导致的 SQL 语法错误。
  • 必须传入 id 作为更新条件,否则会更新全表数据(非常危险!)。

3. 动态插入(选择性插入字段)

需求:实现用户新增接口,支持只传入部分必填字段,其他可选字段使用数据库默认值。

Mapper 接口方法

int insertSelective(User user);

XML 映射文件

xml

<insert id="insertSelective" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO user
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="username != null and username != ''">
            username,
        </if>
        <if test="age != null">
            age,
        </if>
        <if test="email != null and email != ''">
            email,
        </if>
        <if test="status != null">
            status,
        </if>
        <if test="createTime != null">
            create_time,
        </if>
    </trim>
    <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
        <if test="username != null and username != ''">
            #{username},
        </if>
        <if test="age != null">
            #{age},
        </if>
        <if test="email != null and email != ''">
            #{email},
        </if>
        <if test="status != null">
            #{status},
        </if>
        <if test="createTime != null">
            #{createTime},
        </if>
    </trim>
</insert>

核心说明

  • 使用 <trim> 标签自定义拼接规则:prefix 定义前缀,suffix 定义后缀,suffixOverrides="," 去除末尾多余的逗号。
  • useGeneratedKeys="true" keyProperty="id" 用于获取数据库自增主键,并赋值到实体类的 id 属性。

4. 批量删除(根据 ID 列表)

需求:实现批量删除用户功能,支持传入多个用户 ID 进行删除。

Mapper 接口方法

int deleteBatch(@Param("ids") List<Integer> ids);

XML 映射文件

xml

<delete id="deleteBatch">
    DELETE FROM user
    WHERE id IN
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</delete>

核心说明

  • <foreach> 标签用于遍历集合:
    • collection:指定要遍历的集合参数名(使用 @Param 注解定义)。
    • item:遍历元素的别名。
    • open 和 close:定义遍历结果的前后缀(此处拼接为 (1,2,3))。
    • separator:元素之间的分隔符(此处使用逗号)。

5. 分支条件查询(Choose 标签)

需求:实现一个优先级查询接口,优先按用户名查询,若用户名不存在则按邮箱查询,若都不存在则查询状态为正常的用户。

Mapper 接口方法

List<User> selectByPriority(User user);

XML 映射文件

xml

<select id="selectByPriority" resultType="User">
    SELECT id, username, age, email, status
    FROM user
    <where>
        <choose>
            <!-- 优先级 1:用户名不为空时 -->
            <when test="username != null and username != ''">
                AND username LIKE CONCAT('%', #{username}, '%')
            </when>
            <!-- 优先级 2:邮箱不为空时 -->
            <when test="email != null and email != ''">
                AND email LIKE CONCAT('%', #{email}, '%')
            </when>
            <!-- 默认条件:状态为正常 -->
            <otherwise>
                AND status = 1
            </otherwise>
        </choose>
    </where>
</select>

核心说明

  • <choose> 标签类似 Java 的 switch 语句,只会执行第一个满足条件的 <when> 标签,若都不满足则执行 <otherwise> 标签。
  • 适用于有明确优先级的条件判断场景。

四、动态 SQL 最佳实践

  1. 避免 SQL 注入

    • 始终使用 #{} 占位符,而非 ${} 字符串拼接(${} 会直接替换变量,存在注入风险)。
    • 模糊查询推荐使用 CONCAT('%', #{param}, '%') 或数据库函数(如 MySQL 的 LIKE '%${param}%' 需谨慎使用)。
  2. 优化可读性

    • 合理缩进 XML 标签,复杂逻辑可拆分到 <sql> 标签中复用(使用 <include> 引用)。
    • 示例:

    xml

    <sql id="baseColumn">
        id, username, age, email, status, create_time
    </sql>
    
    <select id="selectById" resultType="User">
        SELECT <include refid="baseColumn"/> FROM user WHERE id = #{id}
    </select>
    
  3. 性能考量

    • 动态 SQL 会增加 MyBatis 的解析开销,过于复杂的逻辑建议拆分或通过代码层处理。
    • 批量操作(如批量插入)时,注意数据库的 SQL 长度限制,可分批次执行。
  4. 调试技巧

    • 开启 MyBatis 日志打印 SQL,方便调试动态生成的语句:

    yaml

    mybatis:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    

五、总结

MyBatis 动态 SQL 标签通过简洁的 XML 语法,让我们能够轻松实现复杂的条件查询、选择性更新、批量操作等功能,极大提升了 SQL 语句的灵活性和可维护性。本文介绍的 <if><where><set><foreach><choose> 等标签是日常开发中最常用的工具,掌握它们的使用技巧能让我们的数据库操作代码更加优雅高效。

在实际项目中,建议结合业务场景合理选择动态标签,避免过度使用导致 SQL 逻辑难以理解。同时,始终注意 SQL 注入风险和性能优化,让动态 SQL 真正成为开发效率的助力而非负担。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值