MyBatis—动态 SQL

MyBatis---动态 SQL

一、动态 SQL 的核心作用

动态 SQL 主要解决以下问题:

  1. 灵活性:根据不同的输入参数生成不同的 SQL 语句(如条件查询、批量操作)。

  2. 可维护性:减少重复代码,通过标签化逻辑提高 SQL 可读性。

  3. 安全性:自动处理参数绑定,防止 SQL 注入。

二、常用动态 SQL 标签

MyBatis 提供了以下标签来实现动态 SQL:

1. <if>:条件判断
  • 根据参数值是否满足条件,决定是否包含 SQL 片段。
  • 示例:
xml 复制代码
<select id="findUser" resultType="User">
  SELECT * FROM users
  <where>
    <if test="name != null and name != ''">
      AND name = #{name}
    </if>
    <if test="age != null">
      AND age = #{age}
    </if>
  </where>
</select>
  • 说明 :如果 nameage 为空,则对应的条件不会被包含。
2. <choose>/<when>/<otherwise>:多条件选择
  • 类似于 Java 的 switch-case,按顺序判断条件。

  • 示例:

    xml 复制代码
    <select id="findUserByCondition" resultType="User">
      SELECT * FROM users
      <where>
        <choose>
          <when test="name != null">
            AND name = #{name}
          </when>
          <when test="email != null">
            AND email = #{email}
          </when>
          <otherwise>
            AND status = 'active'
          </otherwise>
        </choose>
      </where>
    </select>
  • 说明 :按顺序判断 nameemail,若都不满足则执行默认条件。

3. <where>:智能处理 WHERE 子句
  • 自动去除多余的 ANDOR,并自动添加 WHERE 关键字。

  • 示例:

    xml 复制代码
    <select id="findUser" resultType="User">
      SELECT * FROM users
      <where>
        <if test="name != null">
          AND name = #{name}
        </if>
        <if test="age != null">
          AND age = #{age}
        </if>
      </where>
    </select>
    • 结果 :若 nameage 都为空,则生成 SELECT * FROM users(无 WHERE 子句);若 name 不为空,则生成 WHERE name = ?
4. <set>:动态更新字段
  • 用于 UPDATE 语句,自动去除末尾逗号。

  • 示例:

    xml 复制代码
    <update id="updateUser">
      UPDATE users
      <set>
        <if test="name != null">
          name = #{name},
        </if>
        <if test="email != null">
          email = #{email},
        </if>
      </set>
      WHERE id = #{id}
    </update>
    • 结果 :若 nameemail 都不为空,生成 SET name = ?, email = ?;若只有一个字段不为空,自动去除末尾逗号。
5. <foreach>:遍历集合
  • 用于批量操作(如 IN 子句、批量插入)。

  • 示例:

    xml 复制代码
    <select id="findUsersByIds" resultType="User">
      SELECT * FROM users
      WHERE id IN
      <foreach item="id" collection="list" open="(" separator="," close=")">
        #{id}
      </foreach>
    </select>
    • 结果 :若传入的 list[1, 2, 3],生成 WHERE id IN (1, 2, 3)
6. <trim>:灵活拼接 SQL
  • 手动控制 SQL 片段的前缀和后缀,常用于复杂逻辑。

  • 示例:

    xml 复制代码
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
      <if test="name != null">
        AND name = #{name}
      </if>
    </trim>
    • 说明 :若 name 为空,则不生成 WHERE;若 name 不为空,生成 WHERE name = ?

三、动态 SQL 的实现原理

  1. XML 解析:MyBatis 启动时加载 Mapper XML 文件,解析动态 SQL 标签。
  2. SQL 拼接:运行时根据传入参数动态生成 SQL 片段。
  3. 参数绑定 :使用 #{} 绑定参数,防止 SQL 注入。
  4. 预编译 :最终生成的 SQL 被发送给数据库驱动,创建 PreparedStatement

四、动态 SQL 的应用场景

  1. 条件查询:根据用户输入动态过滤条件。
  2. 批量操作:批量插入、更新、删除。
  3. 多表关联:根据业务需求动态关联不同表。
  4. 复杂业务逻辑:如动态排序、分页等。

五、最佳实践

  1. 避免复杂嵌套:过多嵌套会降低可读性,建议拆分逻辑。
  2. 合理使用 <where><set>:简化 SQL 片段。
  3. 测试动态 SQL:通过日志查看生成的 SQL,确保逻辑正确。
  4. 参数校验:在业务层校验参数,避免无效条件。

六、示例:综合使用动态 SQL(实战)

比如我们已经写完了controller层,entity层,mapper层,service层等Impl。

动态SQL实现:修改mapper层:(注释掉SQL注解)

java 复制代码
@Mapper
public interface UsersMapper {

    @Select("insert into users(account,password,uname,gender,age,phone,email,address,avatar,regtime,uflag) values(#{account},#{password},#{uname},#{gender},#{age},#{phone},#{email},#{address},#{avatar},#{regtime},#{uflag})")
    void addUser(Users users);

    @Select("select * from users where account=#{account}")
    Users getUserByAccount(String account);

//    @Select("select * from users where uflag = #{uflag} order by regtime desc")
    List<Users> queryUsersByUflag(Users users);

    //根据id查询用户
    @Select("select * from users where account = #{id}")
    Users queryUsersById(String id);

    //审核通过
//    @Update("update users set uflag = #{uflag} where account = #{account}")
    void updateUser(Users users);

    //删除家长
    @Delete("delete from users where account = #{id}")
    void delUsers(String id);
}

这些SQL注解我们写进resources/mapper/UsersMapper.xml这里。

例如:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bkty.turtorsystem.mapper.UsersMapper">

    <!--根据动态的条件查询用户-->
    <select id="queryUsersByUflag"  resultType="Users"
            parameterType="Users">
        select * from users
        <where>
            <if test="uflag != null and uflag != ''">
                uflag = #{uflag}
            </if>
            <if test="account != null and account != ''">
                and account = #{account}
            </if>
            <if test="uname != null and uname != ''">
                and uname = #{uname}
            </if>
            <if test="password != null and password != ''">
                and password = #{password}
            </if>
            <if test="email != null and email != ''">
                email = #{email}
            </if>
            <if test="phone != null and phone != ''">
                and phone like concat('%',#{phone},'%')
            </if>
            <if test="gender != null and gender != ''">
                and gender = #{gender}
            </if>
            <if test="age != null and age != ''">
                and age = #{age}
            </if>
            <if test="address != null and address != ''">
                address = #{address}
            </if>
            <if test="condition != null and condition != ''">
                ${condition}
            </if>
        </where>
        order by regtime desc
    </select>

    <update id="updateUser" parameterType="Users">
            update users
            <set>
                <if test="account != null and account != ''">
                    account = #{account},
                </if>
                <if test="password != null and password != ''">
                    password = #{password},
                </if>
                <if test="uname != null and uname != ''">
                    uname = #{uname},
                </if>
                <if test="gender != null and gender != ''">
                    gender = #{gender},
                </if>
                <if test="age != null and age != ''">
                    age = #{age},
                </if>
                <if test="phone != null and phone != ''">
                    phone = #{phone},
                </if>
                <if test="email != null and email != ''">
                    email = #{email},
                </if>
                <if test="address != null and address != ''">
                    address = #{address},
                </if>
                <if test="avatar != null and avatar != ''">
                    avatar = #{avatar},
                </if>
                <if test="regtime != null and regtime != ''">
                    regtime = #{regtime},
                </if>
                <if test="uflag != null and uflag != ''">
                    uflag =#{uflag},
                </if>
            </set>
            where account = #{account}
    </update>
</mapper>
java 复制代码
<mapper namespace="com.bkty.turtorsystem.mapper.UsersMapper">

这个namespace=指向我的mapper,相当于注解SQL,把注解改成了xml。

1. 查询语句:queryUsersByUflag

xml 复制代码
<select id="queryUsersByUflag" resultType="Users" parameterType="Users">
    select * from users
    <where>
        <if test="uflag != null and uflag != ''">
            uflag = #{uflag}
        </if>
        <if test="account != null and account != ''">
            and account = #{account}
        </if>
        <!-- 其他字段的 <if> 条件 -->
        <if test="condition != null and condition != ''">
            ${condition}
        </if>
    </where>
    order by regtime desc
</select>
功能说明
  • 作用 :根据传入的 Users 对象中的字段动态生成 SQL 查询条件,查询 users 表中的记录。
  • 动态条件:
    • 使用 <where> 标签包裹所有条件,MyBatis 会自动处理 ANDOR 的冗余问题(例如,如果第一个条件不成立,WHERE 关键字不会被输出)。
    • 每个 <if> 标签检查字段是否非空,若非空则添加对应的查询条件。
    • 特殊字段 condition 使用 ${condition} 直接拼接 SQL(需注意 SQL 注入风险)。
关键点
  1. 字段条件:
    • 所有字段(如 uflag, account, uname 等)都通过 <if> 动态判断是否添加到查询条件中。
    • 注意:第一个条件(uflag)没有加 AND,但 <where> 标签会自动处理这种情况,避免语法错误。
  2. condition 字段:
    • 使用 ${condition} 直接拼接原始 SQL 片段(例如 1=1status='active')。
    • 风险${} 不会进行参数绑定,存在 SQL 注入风险,需确保传入值的安全性。
  3. 排序:
    • 固定按 regtime 降序排列。
示例

假设传入的 Users 对象包含 uflag="1"account="test123",生成的 SQL 为:

sql 复制代码
SELECT * FROM users 
WHERE uflag = '1' AND account = 'test123' 
ORDER BY regtime DESC;

2. 更新语句:updateUser

xml 复制代码
<update id="updateUser" parameterType="Users">
    update users
    <set>
        <if test="account != null and account != ''">
            account = #{account},
        </if>
        <!-- 其他字段的 <if> 条件 -->
        <if test="uflag != null and uflag != ''">
            uflag =#{uflag},
        </if>
    </set>
    where account = #{account}
</update>
功能说明
  • 作用 :根据传入的 Users 对象中的字段动态更新 users 表中的记录。
  • 动态更新字段:
    • 使用 <set> 标签包裹所有字段更新逻辑,MyBatis 会自动去除末尾多余的逗号。
    • 每个 <if> 标签判断字段是否非空,若非空则更新对应字段。
  • 更新条件:
    • 使用 account = #{account} 作为更新条件(需确保 account 是唯一标识字段)。
关键点
  1. 字段更新:
    • 所有字段(如 account, password, uname 等)都通过 <if> 动态判断是否更新。
    • 注意:每个字段条件后都有逗号 ,,但 <set> 会自动去除最后一个字段的逗号。
  2. 更新条件:
    • 使用 account = #{account} 作为更新条件,需确保 account 是唯一值(否则可能更新多条记录)。
  3. 潜在问题:
    • 如果 account 不唯一,可能会导致意外更新多条记录。
    • 更推荐使用主键(如 id)作为更新条件。
示例

假设传入的 Users 对象包含 account="test123"uname="NewName",生成的 SQL 为:

sql 复制代码
UPDATE users 
SET account = 'test123', uname = 'NewName' 
WHERE account = 'test123';

七、总结

MyBatis 的动态 SQL 通过标签化逻辑,解决了传统 SQL 硬编码的问题,使代码更简洁、安全且灵活。合理使用 <if><where><foreach> 等标签,可以大幅提升开发效率和代码质量。

相关推荐
喵手11 分钟前
如何利用Java的Stream API提高代码的简洁度和效率?
java·后端·java ee
-Xie-12 分钟前
Maven(二)
java·开发语言·maven
IT利刃出鞘25 分钟前
Java线程的6种状态和JVM状态打印
java·开发语言·jvm
薛晓刚29 分钟前
当MySQL的int不够用了
数据库
SelectDB技术团队1 小时前
Apache Doris 在菜鸟的大规模湖仓业务场景落地实践
数据库·数据仓库·数据分析·apache doris·菜鸟技术
星空下的曙光1 小时前
mysql 命令语法操作篇 数据库约束有哪些 怎么使用
数据库·mysql
小楓12011 小时前
MySQL數據庫開發教學(一) 基本架構
数据库·后端·mysql
天天摸鱼的java工程师1 小时前
Java 解析 JSON 文件:八年老开发的实战总结(从业务到代码)
java·后端·面试
白仑色1 小时前
Spring Boot 全局异常处理
java·spring boot·后端·全局异常处理·统一返回格式
染落林间色1 小时前
达梦数据库-实时主备集群部署详解(附图文)手工搭建一主一备数据守护集群DW
数据库·sql