动态sql
- 一、动态sql语法
-
- [1. if 元素](#1. if 元素)
- [2. choose, when, therwise元素](#2. choose, when, therwise元素)
- [3. where 元素](#3. where 元素)
- [4. trim 元素](#4. trim 元素)
- [5. set 元素](#5. set 元素)
- [6. foreach 元素](#6. foreach 元素)
- [7. bind 元素](#7. bind 元素)
- [8. sql 和 include 元素](#8. sql 和 include 元素)
- 二、参数处理与表达式
-
- [1. OGNL 表达式语法](#1. OGNL 表达式语法)
- [2. 特殊参数名](#2. 特殊参数名)
- 三、高级用法与技巧
-
- [1. 动态表名和列名](#1. 动态表名和列名)
- [2. 分页查询优化](#2. 分页查询优化)
- [3. 批量更新](#3. 批量更新)
- [4. SQL 片段重用](#4. SQL 片段重用)
- [5. 复杂条件组合](#5. 复杂条件组合)
- 四、最佳实践与注意事项
-
- [1. 性能优化](#1. 性能优化)
- [2. 避免 SQL 注入](#2. 避免 SQL 注入)
- [3. 处理特殊字符](#3. 处理特殊字符)
- [4. 调试技巧](#4. 调试技巧)
- 五、实际应用场景示例
- 六、常见问题与解决方案
-
- [问题1:动态 SQL 中的空值处理](#问题1:动态 SQL 中的空值处理)
- [问题2:Boolean 类型处理](#问题2:Boolean 类型处理)
- 问题3:嵌套条件
- 七、动态sql具体场景示例
-
- 1.请求参数为id,查询用户数数据
- 2.请求参数为List<long>ids,查询用户数据
- 3.请求参数为id,查询name
- 4.请求参数为name,根据name模糊查询
- 5.请求参数为UserDto,字段name,age,根据name和age查询
- [6.请求参数为List<UserDto> userDtos,UserDto字段name,age,根据userDtos查询](#6.请求参数为List<UserDto> userDtos,UserDto字段name,age,根据userDtos查询)
- 7.choose标签
一、动态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 <= #{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 < 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 <= #{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>