TL;DR
- 场景:Java工程师学习MyBatis动态SQL特性的开发者
- 结论:MyBatis通过if/where/foreach/sql标签实现灵活SQL构建,参数null值需显式判断
- 产出:动态SQL四标签用法、参数拼接示例、foreach批量查询、SQL片段复用、错误速查

版本矩阵
| 数据项 | 数值/事实 | 来源 | 核查状态 |
|---|---|---|---|
| MyBatis版本 | 3.5.16(2024年4月发布) | MyBatis官方文档 | ✅ 已核查 |
<if>标签 |
条件判断,test属性使用OGNL表达式 | MyBatis官方 | ✅ 已核查 |
<where>标签 |
自动处理WHERE关键字及多余AND/OR | MyBatis官方 | ✅ 已核查 |
<foreach>标签 |
循环拼接,支持collection/open/close/separator/item属性 | MyBatis官方 | ✅ 已核查 |
<sql>+<include> |
SQL片段定义与引用复用 | MyBatis官方 | ✅ 已核查 |
| selectList方法 | 动态多条件查询,null值需显式判断 | 博客示例代码 | ✅ 已核查 |
| selectListByIdList | 批量IN查询,@Param("idList")接收List参数 | 博客示例代码 | ✅ 已核查 |
| selectOneBySegment | 使用sql/include片段复用 | 博客示例代码 | ✅ 已核查 |
核心配置

动态 SQL
动态 SQL 是 MyBatis 的核心特性之一,它允许开发者根据不同的业务条件动态生成 SQL 语句。这一特性解决了复杂查询场景下 SQL 拼接的痛点,不仅提升了开发效率,也增强了代码的可读性与可维护性。
在实际开发中,业务逻辑往往复杂多变,导致 SQL 语句需要根据传入参数的不同而动态调整。MyBatis 的动态 SQL 机制正是为此而生。
动态 SQL 的用途
- 灵活性:能够处理动态变化的查询条件,例如用户界面中的多条件筛选表单。
- 避免冗余:通过动态语法将多个相似的 SQL 查询逻辑合并,减少代码重复。
- 提高效率:仅在需要时生成对应的 SQL 片段,避免加载不必要的数据。

动态 SQL 的注意事项
null 值的处理
MyBatis 不会自动过滤 null 值,开发者需要在 <if> 等标签中显式进行非空判断。
复杂逻辑的可读性
过多的动态 SQL 逻辑会导致 XML 文件变得臃肿复杂,建议合理拆分,保持结构清晰。
性能问题
动态生成的 SQL 应尽量保持简洁,避免因过度复杂而影响数据库查询性能。
调试
建议开启 MyBatis 的日志功能,以便查看最终生成的 SQL 语句,确保其正确性。
参数拼接
在实际开发中,我们经常需要根据实体对象中不同字段的取值来动态构建查询条件。例如,当 ID 不为空时按 ID 查询,当用户名不为空时再加入用户名作为条件。这种多条件组合查询是动态 SQL 的典型应用场景。
xml
<!-- 查询所有用户信息 -->
<select id="selectList" resultType="icu.wzk.model.UserInfo">
SELECT
*
FROM
user_info
<where>
<if test="username != null and username != ''">
and username=#{username}
</if>
<if test="password != null and password != ''">
and password=#{password}
</if>
<if test="age != null and age != ''">
and age=#{age}
</if>
</where>
</select>
对应的 Mapper 接口如下图所示: 
编写测试代码,复用之前的逻辑,并传入部分参数:
java
public class WzkIcu05 {
public static void main(String[] args) throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserInfoMapper userInfoMapper = sqlSession.getMapper(UserInfoMapper.class);
UserInfo userInfo = UserInfo
.builder()
.username("wzk")
.build();
List<UserInfo> dataList = userInfoMapper.selectList(userInfo);
dataList.forEach(System.out::println);
sqlSession.close();
}
}
执行后,控制台输出如下日志,可以看到 SQL 中只拼接了 username 条件:
shell
24/11/11 15:59:59 DEBUG jdbc.JdbcTransaction: Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7f010382]
24/11/11 15:59:59 DEBUG UserInfoMapper.selectList: ==> Preparing: SELECT * FROM user_info WHERE username=?
24/11/11 15:59:59 DEBUG UserInfoMapper.selectList: ==> Parameters: wzk(String)
24/11/11 15:59:59 DEBUG UserInfoMapper.selectList: <== Total: 1
UserInfo(id=1, username=wzk, password=icu, age=18)
对应的运行截图如下: 
循环拼接
当我们需要根据一个 ID 集合来批量查询数据时,可以使用 <foreach> 标签来实现循环拼接。
首先,在 UserInfoMapper 中添加一个新方法:
java
List<UserInfo> selectListByIdList(@Param("idList") List<Integer> idList);
对应的接口截图如下: 
接着,编写对应的 Mapper XML:
xml
<select id="selectListByIdList" parameterType="icu.wzk.model.UserInfo" resultType="icu.wzk.model.UserInfo">
SELECT
*
FROM
user_info
<where>
id IN
<foreach collection="idList" open="(" close=")" separator="," index="index" item="item">
#{item}
</foreach>
</where>
</select>
对应的 XML 截图如下: 
编写 Java 代码进行测试:
java
public class WzkIcu06 {
public static void main(String[] args) throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserInfoMapper userInfoMapper = sqlSession.getMapper(UserInfoMapper.class);
List<Integer> idList = Arrays.asList(1, 2, 3);
List<UserInfo> dataList = userInfoMapper.selectListByIdList(idList);
dataList.forEach(System.out::println);
sqlSession.close();
}
}
当前数据库中的数据如下: 
运行代码,控制台输出日志如下:
shell
24/11/11 16:15:16 DEBUG jdbc.JdbcTransaction: Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6d2a209c]
24/11/11 16:15:16 DEBUG UserInfoMapper.selectListByIdList: ==> Preparing: SELECT * FROM user_info WHERE id IN ( ? , ? , ? )
24/11/11 16:15:16 DEBUG UserInfoMapper.selectListByIdList: ==> Parameters: 1(Integer), 2(Integer), 3(Integer)
24/11/11 16:15:16 DEBUG UserInfoMapper.selectListByIdList: <== Total: 3
UserInfo(id=1, username=wzk, password=icu, age=18)
对应的运行截图如下: 
foreach 标签属性说明
collection:要遍历的集合元素,注意不要加#{}。open:语句的开始部分。close:语句的结束部分。item:集合遍历时的每个元素,即生成的变量名。separator:元素之间的分隔符。
片段抽取
在编写 SQL 时,经常会遇到重复的 SQL 片段(例如 SELECT * FROM user_info)。MyBatis 提供了 <sql> 标签来定义可复用的 SQL 片段,并通过 <include> 标签进行引用,从而实现 SQL 代码的复用。
首先,在 UserInfoMapper 接口中添加一个新方法:
java
UserInfo selectOneBySegment(UserInfo userInfo);
对应的接口截图如下: 
编写对应的 XML,使用 <sql> 定义公共片段,并用 <include> 引用:
xml
<sql id="SELECT_USER_INFO">
SELECT * FROM user_info
</sql>
<select id="selectOneBySegment" parameterType="icu.wzk.model.UserInfo" resultType="icu.wzk.model.UserInfo">
<include refid="SELECT_USER_INFO"></include>
<where>
id=#{id}
</where>
</select>
对应的 XML 截图如下: 
编写测试代码:
java
public class WzkIcu07 {
public static void main(String[] args) throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserInfoMapper userInfoMapper = sqlSession.getMapper(UserInfoMapper.class);
UserInfo userInfo = UserInfo
.builder()
.id(1L)
.build();
userInfo = userInfoMapper.selectOneBySegment(userInfo);
System.out.println(userInfo);
sqlSession.close();
}
}
执行后,控制台输出结果如下:
shell
24/11/11 17:04:08 DEBUG UserInfoMapper.selectOneBySegment: ==> Preparing: SELECT * FROM user_info WHERE id=?
24/11/11 17:04:08 DEBUG UserInfoMapper.selectOneBySegment: ==> Parameters: 1(Long)
24/11/11 17:04:08 DEBUG UserInfoMapper.selectOneBySegment: <== Total: 1
UserInfo(id=1, username=wzk, password=icu, age=18)
对应的运行截图如下: 
错误速查卡
| 症状 | 根因 | 定位 | 修复 |
|---|---|---|---|
| SQL中多了多余的AND/OR | where子句不以AND开头或结尾 | 查看标签包裹的if语句 | 使用标签而非手动写WHERE,它会自动处理 |
| foreach生成的SQL有语法错误 | collection属性写成#{idList}而非idList | 检查foreach标签collection属性 | collection不加#{},直接写参数名 |
| null值条件被拼接 | if标签中未判断null | 检查条件 | 显式判断:test="username != null and username != ''" |
| SQL片段引用失败 | refid与sql id不匹配 | 检查和 | 确保两边id完全一致 |
| foreach生成的IN语句多逗号 | separator设置错误 | 检查 | 确保separator为逗号而非其他 |
| selectList返回空列表 | 参数全部为null导致无查询条件 | 检查传入对象各字段是否为null | MyBatis不会自动过滤null,需在if中显式判断 |
作者:武子康的个人博客