mybatis动态sql

动态sql

一、动态sql语法

1. if 元素

元素用于条件判断,如果条件成立,则将其包含的SQL片段添加到整个SQL语句中。

示例:

xml 复制代码
<select id="findUser" parameterType="com.test.entity.User" resultType="com.test.entity.User">
    SELECT * FROM user
    WHERE 1=1
    <if test="name != null">
        AND name = #{name}
    </if>
    <if test="age != null">
        AND age = #{age}
    </if>
</select>

注意:

parameterType和resultType如果类型是对象,需要写对象的路径,如User在com.test.entity路径下,那么写resultType="com.test.entity.User"

2. choose, when, therwise元素

这组元素类似于Java中的switch-case语句,从多个条件中选择一个。

示例:

xml 复制代码
<select id="findUserByCondition" parameterType="com.test.entity.User" resultType="User">
    SELECT * FROM user
    WHERE 1=1
    <choose>
        <when test="name != null">
            AND name = #{name}
        </when>
        <when test="age != null">
            AND age = #{age}
        </when>
        <otherwise>
            AND status = 'ACTIVE'
        </otherwise>
    </choose>
</select>

3. where 元素

元素用于简化WHERE子句的处理。它会自动去除子句中多余的AND或OR,并且如果子句为空,则不会生成WHERE关键字。

示例:

xml 复制代码
<select id="findUser" parameterType="User" resultType="User">
    SELECT * FROM user
    <where>
        <if test="name != null">
            name = #{name}
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </where>
</select>

4. trim 元素

元素比和更灵活,可以自定义前缀、后缀以及要移除的多余字符串。

示例(实现WHERE功能):

xml 复制代码
<select id="findUser" parameterType="User" resultType="User">
    SELECT * FROM user
    <trim prefix="WHERE" prefixOverrides="AND |OR ">
        <if test="name != null">
            AND name = #{name}
        </if>
        <if test="age != null">
            AND age = #{age}
        </if>
    </trim>
</select>

5. set 元素

元素用于更新操作,会自动去除多余的逗号,并添加SET关键字。

示例:

xml 复制代码
<update id="updateUser" parameterType="User">
    UPDATE user
    <set>
        <if test="name != null">
            name = #{name},
        </if>
        <if test="age != null">
            age = #{age},
        </if>
    </set>
    WHERE id = #{id}
</update>

6. foreach 元素

元素用于遍历集合(如List、Set、数组等),常用于IN条件、批量插入等。

示例(IN条件):

xml 复制代码
<select id="findUsersByIds" parameterType="list" resultType="User">
    SELECT * FROM user
    WHERE id IN
    <foreach collection="list" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

示例(批量插入):

xml 复制代码
<insert id="insertUsers" parameterType="list">
    INSERT INTO user (name, age) VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.name}, #{user.age})
    </foreach>
</insert>

7. bind 元素

元素允许你在上下文中创建一个变量,并将其绑定到当前上下文,常用于模糊查询或复杂表达式。

示例(模糊查询):

xml 复制代码
<select id="findUserByName" parameterType="String" resultType="User">
    <bind name="pattern" value="'%' + name + '%'" />
    SELECT * FROM user
    WHERE name LIKE #{pattern}
</select>

8. sql 和 include 元素

用于定义可重用的SQL片段, 用于引用这些片段,提高代码复用性。

示例:

xml 复制代码
<!-- 定义可重用的列名 -->
<sql id="userColumns">id, name, age, myclass</sql>

<select id="findUser" parameterType="User" resultType="User">
    SELECT 
    <include refid="userColumns"/>
    FROM user
    WHERE id = #{id}
</select>

二、参数处理与表达式

1. OGNL 表达式语法

xml 复制代码
<!-- 基本判断 -->
<if test="name != null">...</if>

<!-- 字符串判断 -->
<if test="name != null and name != ''">...</if>

<!-- 数字比较 -->
<if test="age != null and age > 18">...</if>
<if test="age != null and age lt 18">...</if>  <!-- lt 表示 < -->

<!-- 集合判断 -->
<if test="list != null and list.size() > 0">...</if>
<if test="array != null and array.length > 0">...</if>

<!-- 调用方法 -->
<if test="name.contains('张')">...</if>
<if test="name.startsWith('王')">...</if>

2. 特殊参数名

xml 复制代码
<!-- _parameter 表示整个参数 -->
<if test="_parameter != null">...</if>

<!-- _databaseId 表示当前数据库厂商 -->
<if test="_databaseId == 'mysql'">
    SELECT * FROM user LIMIT #{limit}
</if>
<if test="_databaseId == 'oracle'">
    SELECT * FROM user WHERE ROWNUM &lt;= #{limit}
</if>

三、高级用法与技巧

注意:

过多语法未做示例,大家有好的示例,可以卸载评论区,博主进行补充,以便大家更好理解

1. 动态表名和列名

xml 复制代码
<select id="dynamicQuery" resultType="map">
    SELECT 
    <foreach collection="columns" item="column" separator=",">
        ${column}
    </foreach>
    FROM ${tableName}
    <where>
        <if test="conditions != null">
            <foreach collection="conditions" item="cond" separator=" AND ">
                ${cond.column} = #{cond.value}
            </foreach>
        </if>
    </where>
</select>

2. 分页查询优化

xml 复制代码
<select id="findByPage" resultType="User">
    SELECT * FROM user 
    <where>
        <if test="name != null">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
    </where>
    ORDER BY 
    <choose>
        <when test="orderBy == 'name'">name</when>
        <when test="orderBy == 'age'">age</when>
        <otherwise>id</otherwise>
    </choose>
    <choose>
        <when test="orderDirection == 'desc'">DESC</when>
        <otherwise>ASC</otherwise>
    </choose>
    LIMIT #{offset}, #{pageSize}
</select>

3. 批量更新

xml 复制代码
<update id="batchUpdate">
    <foreach collection="users" item="user" separator=";">
        UPDATE user 
        <set>
            <if test="user.name != null">name = #{user.name},</if>
            <if test="user.age != null">age = #{user.age},</if>
        </set>
        WHERE id = #{user.id}
    </foreach>
</update>

4. SQL 片段重用

xml 复制代码
<!-- 定义可重用的 SQL 片段 -->
<sql id="userColumns">
    id, name, age, myclass, email, phone
</sql>

<sql id="userConditions">
    <where>
        <if test="name != null">AND name = #{name}</if>
        <if test="age != null">AND age = #{age}</if>
        <if test="email != null">AND email = #{email}</if>
    </where>
</sql>

<!-- 使用 SQL 片段 -->
<select id="selectUsers" resultType="User">
    SELECT 
    <include refid="userColumns"/>
    FROM user
    <include refid="userConditions"/>
</select>

5. 复杂条件组合

xml 复制代码
<select id="complexQuery" resultType="User">
    SELECT * FROM user 
    <where>
        <!-- 第一组条件 -->
        <if test="conditionGroup == 1">
            <if test="name != null">AND name = #{name}</if>
            <if test="age != null">AND age > #{age}</if>
        </if>
        
        <!-- 第二组条件 -->
        <if test="conditionGroup == 2">
            AND status = 'ACTIVE'
            <if test="startDate != null">
                AND create_time >= #{startDate}
            </if>
        </if>
        
        <!-- 通用条件 -->
        <if test="keywords != null and keywords.size() > 0">
            AND (
            <foreach collection="keywords" item="keyword" separator=" OR ">
                name LIKE CONCAT('%', #{keyword}, '%')
                OR email LIKE CONCAT('%', #{keyword}, '%')
            </foreach>
            )
        </if>
    </where>
</select>

四、最佳实践与注意事项

1. 性能优化

xml 复制代码
<!-- 避免全表扫描 -->
<select id="optimizedQuery" resultType="User">
    SELECT * FROM user 
    <where>
        <!-- 把能使用索引的条件放在前面 -->
        <if test="id != null">
            AND id = #{id}  <!-- id有索引,优先使用 -->
        </if>
        <if test="name != null">
            <!-- 右模糊可以使用索引 -->
            AND name LIKE CONCAT(#{name}, '%')  
        </if>
    </where>
</select>

2. 避免 SQL 注入

xml 复制代码
<!-- ❌ 危险:直接拼接 -->
WHERE column = ${value}

<!-- ✅ 安全:使用预编译 -->
WHERE column = #{value}

<!-- ✅ 表名/列名等标识符使用 ${},但要确保安全 -->
ORDER BY ${orderBy}

3. 处理特殊字符

xml 复制代码
<!-- 使用 XML 转义字符 -->
<!-- 错误 -->
<if test="age != null and age < 18">  
<!-- 正确 -->
<if test="age != null and age &lt; 18">  

<!-- 或者使用 CDATA 区块 -->
<if test="age != null">
    <![CDATA[ AND age < 18 ]]>
</if>

4. 调试技巧

xml 复制代码
<!-- 添加调试信息 -->
<select id="debugQuery" resultType="User">
    <!-- 使用 bind 查看参数值 -->
    <bind name="debugName" value="'参数name值: ' + name" />
    
    SELECT * FROM user 
    <where>
        <if test="name != null">
            AND name = #{name}
        </if>
    </where>
    
    <!-- 可以通过日志查看 debugName 的值 -->
</select>

五、实际应用场景示例

场景1:高级搜索功能

xml 复制代码
<select id="advancedSearch" resultType="User">
    SELECT * FROM user 
    <where>
        <!-- 姓名搜索 -->
        <if test="name != null and name != ''">
            AND (
                name LIKE CONCAT('%', #{name}, '%')
                OR nickname LIKE CONCAT('%', #{name}, '%')
            )
        </if>
        
        <!-- 年龄范围 -->
        <if test="minAge != null and maxAge != null">
            AND age BETWEEN #{minAge} AND #{maxAge}
        </if>
        <if test="minAge != null and maxAge == null">
            AND age >= #{minAge}
        </if>
        <if test="minAge == null and maxAge != null">
            AND age &lt;= #{maxAge}
        </if>
        
        <!-- 多选条件 -->
        <if test="statusList != null and statusList.size() > 0">
            AND status IN
            <foreach collection="statusList" item="status" open="(" separator="," close=")">
                #{status}
            </foreach>
        </if>
        
        <!-- 日期范围 -->
        <if test="startDate != null">
            AND create_time >= #{startDate}
        </if>
        <if test="endDate != null">
            <![CDATA[ AND create_time <= #{endDate} ]]>
        </if>
    </where>
    
    <!-- 动态排序 -->
    <if test="orderBy != null">
        ORDER BY 
        <foreach collection="orderBy" item="order" separator=",">
            ${order.field} ${order.direction}
        </foreach>
    </if>
</select>

场景2:权限过滤

xml 复制代码
<select id="findWithPermission" resultType="User">
    SELECT u.* FROM user u
    <where>
        <!-- 基本条件 -->
        <if test="name != null">
            AND u.name LIKE CONCAT('%', #{name}, '%')
        </if>
        
        <!-- 权限过滤 -->
        <if test="currentUser.role == 'ADMIN'">
            <!-- 管理员可以看到所有用户 -->
        </if>
        <if test="currentUser.role == 'MANAGER'">
            <!-- 经理只能看到自己部门的用户 -->
            AND u.department_id = #{currentUser.departmentId}
        </if>
        <if test="currentUser.role == 'USER'">
            <!-- 普通用户只能看到自己 -->
            AND u.id = #{currentUser.id}
        </if>
        
        <!-- 数据范围权限 -->
        <if test="dataScope != null and dataScope.size() > 0">
            AND u.department_id IN
            <foreach collection="dataScope" item="deptId" open="(" separator="," close=")">
                #{deptId}
            </foreach>
        </if>
    </where>
</select>

六、常见问题与解决方案

问题1:动态 SQL 中的空值处理

xml 复制代码
<!-- 错误:空字符串和null都可能导致问题 -->
<if test="name != null">...</if>

<!-- 正确:严格检查 -->
<if test="name != null and name.trim() != ''">...</if>

问题2:Boolean 类型处理

xml 复制代码
<!-- 错误 -->
<if test="active">...</if>  <!-- 当active为false时,表达式为false -->

<!-- 正确 -->
<if test="active != null and active == true">...</if>

问题3:嵌套条件

xml 复制代码
<!-- 使用 <trim> 处理复杂嵌套 -->
<trim prefix="AND (" prefixOverrides="AND |OR " suffix=")">
    <if test="condition1">OR condition1 = #{value1}</if>
    <if test="condition2">AND condition2 = #{value2}</if>
</trim>

七、动态sql具体场景示例

1.请求参数为id,查询用户数数据

getUserById是方法名,需要跟mapper中的方法名对应

xml 复制代码
<select id="getUserById" resultType="com.test.entity.User" parameterType="Long">
    SELECT id, name, age, myclass 
    FROM user 
    WHERE id = #{id}
</select>

2.请求参数为Listids,查询用户数据

xml 复制代码
<select id="getUsersByIds" resultType="com.test.entity.User">
    SELECT id, name, age, myclass 
    FROM user 
    WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

3.请求参数为id,查询name

xml 复制代码
<select id="getNameById" resultType="String" parameterType="java.Long">
    SELECT name 
    FROM user 
    WHERE id = #{id}
</select>

4.请求参数为name,根据name模糊查询

xml 复制代码
<select id="getUsersByName" resultType="com.test.entity.User" parameterType="String">
    SELECT id, name, age, myclass 
    FROM user 
    WHERE name LIKE CONCAT('%', #{name}, '%')
</select>

5.请求参数为UserDto,字段name,age,根据name和age查询

xml 复制代码
<select id="getUsersByDto" resultType="com.test.entity.User" parameterType="UserDto">
    SELECT id, name, age, myclass 
    FROM user 
    WHERE 1=1
    <if test="name != null and name != ''">
        AND name = #{name}
    </if>
    <if test="age != null">
        AND age = #{age}
    </if>
</select>

6.请求参数为List userDtos,UserDto字段name,age,根据userDtos查询

xml 复制代码
<select id="getUsersByDtoList" resultType="com.test.entity.User">
    SELECT id, name, age, myclass 
    FROM user 
    WHERE 1=1
    <if test="list != null and list.size() > 0">
        AND (
        <foreach collection="list" item="dto" separator=" OR ">
            (
            <if test="dto.name != null and dto.name != ''">
                name = #{dto.name}
            </if>
            <if test="dto.age != null">
                <if test="dto.name != null and dto.name != ''">AND</if>
                age = #{dto.age}
            </if>
            )
        </foreach>
        )
    </if>
</select>

7.choose标签

请求参数为name,如果name为null,则条件为name is null,否则根据name查询

xml 复制代码
<select id="getUsersByNameOrNull" resultType="com.test.entity.User" parameterType="String">
    SELECT id, name, age, myclass 
    FROM user 
    WHERE 1=1
    <choose>
        <when test="name == null">
            AND name IS NULL
        </when>
        <otherwise>
            AND name = #{name}
        </otherwise>
    </choose>
</select>
相关推荐
zqmattack4 小时前
SQL优化与索引策略实战指南
java·数据库·sql
lang201509284 小时前
Jackson 1.x到2.x的演进与Spring集成
数据库·sql·spring
BD_Marathon5 小时前
自定义映射resultMap——通过collection解决一对多的映射关系(九)
mybatis
码农幻想梦5 小时前
实验四 mybatis动态sql及逆向工程
sql·性能优化·mybatis
小北方城市网6 小时前
SpringBoot 集成 MyBatis-Plus 实战(高效 CRUD 与复杂查询):简化数据库操作
java·数据库·人工智能·spring boot·后端·安全·mybatis
笃行客从不躺平7 小时前
PG SQL 行转列记录
数据库·sql
知识分享小能手7 小时前
Oracle 19c入门学习教程,从入门到精通,PL/SQL 编程详解:语法、使用方法与综合案例(6)
sql·学习·oracle
码农幻想梦8 小时前
实验三 Mybatis多表查询操作
mybatis
cab58 小时前
MyBatis如何处理数据库中的JSON字段
数据库·json·mybatis