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> 等标签,可以大幅提升开发效率和代码质量。

相关推荐
Eiceblue12 分钟前
Python读取PDF:文本、图片与文档属性
数据库·python·pdf
面朝大海,春不暖,花不开20 分钟前
自定义Spring Boot Starter的全面指南
java·spring boot·后端
得过且过的勇者y20 分钟前
Java安全点safepoint
java
夜晚回家1 小时前
「Java基本语法」代码格式与注释规范
java·开发语言
斯普信云原生组1 小时前
Docker构建自定义的镜像
java·spring cloud·docker
wangjinjin1801 小时前
使用 IntelliJ IDEA 安装通义灵码(TONGYI Lingma)插件,进行后端 Java Spring Boot 项目的用户用例生成及常见问题处理
java·spring boot·intellij-idea
wtg44521 小时前
使用 Rest-Assured 和 TestNG 进行购物车功能的 API 自动化测试
java
白宇横流学长2 小时前
基于SpringBoot实现的大创管理系统设计与实现【源码+文档】
java·spring boot·后端
fat house cat_2 小时前
【redis】线程IO模型
java·redis
在未来等你2 小时前
SQL进阶之旅 Day 21:临时表与内存表应用
sql·mysql·postgresql·database·temporary-table·memory-table·sql-optimization