ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

MyBatis动态SQL核心标签详解与Spring Boot集成实战

2026/8/8 6:17:54 拓冰建站 浏览量
MyBatis动态SQL核心标签详解与Spring Boot集成实战

在实际 Java Web 项目中,持久层框架 MyBatis 因其灵活性和对 SQL 的直接掌控能力而被广泛使用。然而,当业务逻辑变得复杂,需要根据不同条件拼接 SQL 时,传统的字符串拼接方式不仅代码冗长、难以维护,还极易引入 SQL 注入的安全风险。MyBatis 提供的动态 SQL 功能正是为了解决这一问题而生,它允许你在 XML 映射文件中,使用一组强大的标签来构建条件化的 SQL 语句。掌握动态 SQL,意味着你能用更少的代码实现更复杂的查询逻辑,显著提升开发效率和代码可读性。本文将带你从零开始,深入理解 MyBatis 动态 SQL 的核心标签及其应用场景,并通过一个完整的 Spring Boot 集成案例,展示如何在实际项目中运用这些标签来简化代码,最终形成一套可复用的查询构建模式。

1. 理解 MyBatis 动态 SQL 的核心价值与工作机制

1.1 为什么需要动态 SQL?

在数据库操作中,我们经常遇到根据前端传入的不同参数组合进行查询的场景。例如,一个用户查询接口,可能支持按姓名、年龄、状态等多个字段进行筛选,且这些条件都是可选的。如果使用静态 SQL,你需要为每一种可能的条件组合都编写一个独立的查询方法,这会导致 Mapper 接口和 XML 文件急剧膨胀,产生大量重复代码。更糟糕的是,使用 Java 代码进行字符串拼接来动态生成 SQL,不仅可读性差,而且一旦忘记处理参数中的特殊字符(如单引号),就会为 SQL 注入攻击打开大门。

MyBatis 动态 SQL 的本质,是在 XML 映射文件中,通过一组预定义的标签(如<if>,<choose>,<where>,<set>,<foreach>等),在 SQL 语句被发送到数据库执行之前,根据传入参数的值,智能地组装出最终的 SQL 字符串。这个过程由 MyBatis 框架在运行时完成,开发者无需关心字符串拼接的细节,只需声明逻辑规则。

1.2 动态 SQL 的执行流程与底层原理

当 MyBatis 执行一条映射语句时,它会经历一个完整的生命周期:从解析 Mapper XML 文件开始,到创建 SqlSession,最终执行 JDBC 操作。动态 SQL 的处理发生在 SQL 语句的构建阶段

  1. 解析阶段:MyBatis 启动时会加载所有*Mapper.xml文件,并将其中的 SQL 语句解析成一颗包含动态标签的抽象语法树(AST)。
  2. 参数绑定:当调用 Mapper 方法时,传入的参数对象会被封装到一个ParameterHandler中。
  3. 动态处理SqlSource接口的实现类(通常是DynamicSqlSource)会根据传入的参数,遍历之前解析好的 AST。它会评估每个动态标签中的 OGNL 表达式(如test=“name != null”),根据表达式的结果(true/false)来决定是否包含该标签内的 SQL 片段,并处理标签本身的逻辑(如<where>标签会自动去除首个ANDOR)。
  4. SQL 生成:经过动态处理后,一颗静态的 SQL 树被生成,并转换为一个BoundSql对象,其中包含了最终要发送到数据库的、带有?占位符的 SQL 字符串,以及对应的参数映射关系。
  5. 执行PreparedStatement使用这个BoundSql对象来设置参数并执行。

理解这个流程有助于你在遇到动态 SQL 拼接错误时,能够从原理层面进行排查,而不是盲目尝试。

2. 环境准备与项目基础搭建

在开始编写动态 SQL 之前,我们需要一个可以运行的基础项目。这里以 Spring Boot 集成 MyBatis 为例。

2.1 技术栈与依赖配置

确保你的开发环境已安装 JDK 8+、Maven 或 Gradle 以及一个 IDE(如 IntelliJ IDEA 或 Eclipse)。我们将使用以下技术栈:

  • Spring Boot: 2.7.x (或 3.x,注意依赖包名变化)
  • MyBatis Spring Boot Starter: 用于快速集成
  • MySQL: 作为示例数据库
  • Lombok: 简化实体类编写(可选)

在项目的pom.xml文件中添加关键依赖:

<dependencies> <!-- Spring Boot Web Starter (如果构建Web应用) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis Spring Boot Starter --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.0</version> <!-- 请使用与Spring Boot版本匹配的版本 --> </dependency> <!-- MySQL Connector --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- 测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>

2.2 数据库与实体类准备

假设我们有一个user表,用于演示复杂的条件查询和批量操作。

CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` varchar(30) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年龄', `email` varchar(50) DEFAULT NULL COMMENT '邮箱', `status` tinyint(4) DEFAULT '1' COMMENT '状态:0-禁用,1-启用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

对应的 Java 实体类User.java

package com.example.demo.entity; import lombok.Data; import java.time.LocalDateTime; @Data public class User { private Long id; private String name; private Integer age; private String email; private Integer status; // 0:禁用, 1:启用 private LocalDateTime createTime; }

2.3 MyBatis 基础配置

application.ymlapplication.properties中配置数据源和 MyBatis:

# application.yml spring: datasource: url: jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: your_username password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: # 指定 mapper.xml 文件的位置,如果放在 resources 下对应目录 mapper-locations: classpath:mapper/*.xml # 配置实体类所在的包,方便在 XML 中使用短类名 type-aliases-package: com.example.demo.entity configuration: # 开启驼峰命名自动映射(数据库 user_name -> 实体类 userName) map-underscore-to-camel-case: true # 在控制台打印执行的 SQL,便于调试 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3. 核心动态 SQL 标签详解与实战应用

接下来,我们逐一剖析最常用的动态 SQL 标签,并通过具体的 Mapper 接口和 XML 配置来演示其用法。我们将创建一个UserMapper.java接口及其对应的UserMapper.xml

3.1<if>标签:基础条件判断

<if>标签用于简单的条件判断。其test属性中填写 OGNL 表达式,表达式结果为true时,标签内的 SQL 片段会被包含。

场景:实现一个多条件分页查询用户的功能,所有查询条件均为可选。

首先,创建一个查询参数封装类UserQuery.java

package com.example.demo.dto; import lombok.Data; @Data public class UserQuery { private String name; private Integer minAge; private Integer maxAge; private Integer status; // 分页参数 private Integer pageNum; private Integer pageSize; }

UserMapper.java接口中定义方法:

package com.example.demo.mapper; import com.example.demo.entity.User; import com.example.demo.dto.UserQuery; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface UserMapper { List<User> selectByCondition(UserQuery query); }

resources/mapper/UserMapper.xml中编写动态 SQL:

<?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.example.demo.mapper.UserMapper"> <select id="selectByCondition" resultType="User" parameterType="UserQuery"> SELECT id, name, age, email, status, create_time FROM user WHERE 1=1 <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="minAge != null"> AND age >= #{minAge} </if> <if test="maxAge != null"> AND age <= #{maxAge} </if> <if test="status != null"> AND status = #{status} </if> ORDER BY create_time DESC </select> </mapper>

关键解释

  • test表达式:name != null and name != ''是典型的非空且非空字符串判断。对于字符串,两者通常需要同时检查。
  • WHERE 1=1:这是一个小技巧,用于避免所有<if>条件都不满足时,SQL 变成SELECT ... FROM user WHERE导致语法错误。但这不是最佳实践,后面会用<where>标签优化。
  • #{name}:MyBatis 的参数占位符,能有效防止 SQL 注入。

3.2<where>,<set>,<trim>标签:智能处理关键字

直接使用WHERE 1=1虽然简单,但不够优雅,且在某些数据库优化器中可能影响性能。<where><set>标签可以智能地处理WHERESET子句。

<where>标签:会自动去除其内容开头多余的ANDOR,并且如果标签内没有任何内容,则不会生成WHERE关键字。

优化上面的查询 SQL:

<select id="selectByCondition" resultType="User" parameterType="UserQuery"> SELECT id, name, age, email, status, create_time FROM user <where> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="minAge != null"> AND age >= #{minAge} </if> <if test="maxAge != null"> AND age <= #{maxAge} </if> <if test="status != null"> AND status = #{status} </if> </where> ORDER BY create_time DESC </select>

现在,即使所有<if>条件都不满足,<where>标签也不会输出WHERE,SQL 语句完全正确。

<set>标签:常用于 UPDATE 语句,会自动去除末尾多余的逗号。

// Mapper 接口 int updateUserSelective(User user);
<update id="updateUserSelective" parameterType="User"> UPDATE user <set> <if test="name != null">name = #{name},</if> <if test="age != null">age = #{age},</if> <if test="email != null">email = #{email},</if> <if test="status != null">status = #{status},</if> </set> WHERE id = #{id} </update>

如果只有nameage不为空,生成的 SQL 会是UPDATE user SET name=?, age=? WHERE id=?<set>标签自动去掉了age=后面的逗号。

<trim>标签:是<where><set>的底层实现,功能更强大,可以自定义前缀、后缀以及要覆盖的多余字符串。

  • prefix:添加前缀。
  • prefixOverrides:去掉第一个指定的前缀(如AND|OR)。
  • suffix:添加后缀。
  • suffixOverrides:去掉最后一个指定的后缀(如,)。

<trim>实现<where>功能:

<trim prefix="WHERE" prefixOverrides="AND |OR "> <!-- 条件... --> </trim>

<trim>实现<set>功能:

<trim prefix="SET" suffixOverrides=","> <!-- 赋值... --> </trim>

3.3<choose>,<when>,<otherwise>标签:多路选择

类似于 Java 中的switch-case语句,用于实现多选一的逻辑。

场景:查询用户,优先按姓名精确匹配,若姓名为空则按邮箱模糊匹配,若邮箱也为空则查询所有启用状态的用户。

<select id="selectByPriority" resultType="User" parameterType="map"> SELECT * FROM user <where> <choose> <when test="name != null and name != ''"> name = #{name} </when> <when test="email != null and email != ''"> email LIKE CONCAT('%', #{email}, '%') </when> <otherwise> status = 1 </otherwise> </choose> </where> </select>

3.4<foreach>标签:遍历集合

这是动态 SQL 中最强大的标签之一,用于处理IN查询和批量操作。

场景1:IN 查询。根据一组 ID 列表查询用户。

// Mapper 接口 List<User> selectByIdList(@Param("idList") List<Long> idList);
<select id="selectByIdList" resultType="User"> SELECT * FROM user WHERE id IN <foreach collection="idList" item="id" index="index" open="(" separator="," close=")"> #{id} </foreach> </select>
  • collection: 指定要遍历的集合参数名。如果参数是单集合且未用@Param指定,则可用listarray
  • item: 遍历过程中每个元素的别名。
  • index: 遍历的索引(可选)。
  • open/close: 遍历开始和结束时添加的字符串。
  • separator: 每次遍历之间添加的分隔符。

最终生成的 SQL 类似:SELECT * FROM user WHERE id IN (1, 2, 3, 4)

场景2:批量插入。这是<foreach>标签的经典应用,能极大提升数据初始化效率。

// Mapper 接口 int batchInsert(@Param("userList") List<User> userList);
<insert id="batchInsert"> INSERT INTO user (name, age, email, status) VALUES <foreach collection="userList" item="user" separator=","> (#{user.name}, #{user.age}, #{user.email}, #{user.status}) </foreach> </insert>

生成的 SQL 类似:INSERT INTO user (...) VALUES (...), (...), (...)

注意:虽然批量插入高效,但单条 SQL 语句的长度和参数数量受数据库配置限制(如max_allowed_packet)。如果列表过大,需要分批处理。

3.5<sql><include>标签:代码复用

用于提取公共的 SQL 片段,避免重复编码。

<!-- 定义可复用的列名列表 --> <sql id="Base_Column_List"> id, name, age, email, status, create_time </sql> <!-- 定义可复用的查询条件片段 --> <sql id="Base_Where_Condition"> <where> <if test="name != null and name != ''">AND name LIKE CONCAT('%', #{name}, '%')</if> <if test="status != null">AND status = #{status}</if> </where> </sql> <!-- 在查询语句中引用 --> <select id="selectByCondition" resultType="User" parameterType="UserQuery"> SELECT <include refid="Base_Column_List"/> FROM user <include refid="Base_Where_Condition"/> ORDER BY create_time DESC </select>

4. 构建一个完整的多条件查询服务

现在,我们将上述标签组合起来,实现一个完整的、可复用的用户查询服务,并处理分页。

4.1 完善 Mapper 与 Service

首先,在UserMapper.xml中整合一个功能强大的查询:

<select id="selectByConditionWithPage" resultType="User" parameterType="UserQuery"> SELECT <include refid="Base_Column_List"/> FROM user <where> <!-- 姓名模糊查询 --> <if test="name != null and name != ''"> <bind name="namePattern" value="'%' + name + '%'"/> AND name LIKE #{namePattern} </if> <!-- 年龄范围查询 --> <if test="minAge != null"> AND age >= #{minAge} </if> <if test="maxAge != null"> AND age <= #{maxAge} </if> <!-- 状态精确查询 --> <if test="status != null"> AND status = #{status} </if> <!-- 复杂条件:邮箱不为空或状态为启用 --> <if test="emailNotNull == true"> AND email IS NOT NULL </if> <choose> <when test="statusList != null and statusList.size() > 0"> AND status IN <foreach collection="statusList" item="s" open="(" separator="," close=")"> #{s} </foreach> </when> <when test="defaultActive == true"> AND status = 1 </when> </choose> </where> ORDER BY create_time DESC </select>

新知识点

  • <bind>标签:创建一个新的变量并将其绑定到当前上下文。这里用于构建模糊查询的模式字符串,使得 SQL 更清晰,也便于在多个地方复用该模式。
  • <choose>中嵌套使用<foreach>,实现了更复杂的逻辑:优先按状态列表查询,否则按默认启用状态查询。

创建对应的 Mapper 接口方法和 Service:

// UserMapper.java List<User> selectByConditionWithPage(UserQuery query); // UserService.java @Service public class UserService { @Autowired private UserMapper userMapper; public List<User> queryUsers(UserQuery query) { // 在实际项目中,这里可以添加业务逻辑,如参数校验、数据转换等 return userMapper.selectByConditionWithPage(query); } }

4.2 编写控制器进行测试

创建一个简单的 REST 控制器来测试我们的动态查询:

@RestController @RequestMapping("/api/user") public class UserController { @Autowired private UserService userService; @GetMapping("/list") public ResponseEntity<List<User>> getUserList(UserQuery query) { // 示例:查询姓名包含“张”,年龄在20到30之间,状态为启用的用户 // 请求URL: /api/user/list?name=张&minAge=20&maxAge=30&status=1 List<User> users = userService.queryUsers(query); return ResponseEntity.ok(users); } }

启动 Spring Boot 应用,访问上述接口,观察控制台打印的 SQL。你会看到,根据传入参数的不同,MyBatis 动态生成了完全不同的 SQL 语句,这正是动态 SQL 的魅力所在。

5. 动态 SQL 的常见问题与深度排查

即使掌握了语法,在实际开发中依然会遇到各种问题。下面是一些典型场景的排查思路。

5.1 SQL 拼接错误与语法问题

问题现象可能原因检查与解决
控制台报错:SQL 语法错误,例如多出ANDWHERE后无条件等。1. 未使用<where>标签,且所有<if>条件都不满足,导致WHERE关键字后无内容。
2. 在<where>标签内的条件前手动添加了WHERE
3.<set>标签内条件都不满足,导致SET后无内容。
1.始终使用<where><set>标签来包裹动态条件,避免手动处理关键字。
2. 检查生成的 SQL 日志,对比预期与实际语句。
IN查询时,传入空列表导致 SQL 为IN (),语法错误。<foreach>标签外未对集合进行判空。<foreach>外层使用<if>判断集合是否为空或非空。例如:<if test=“idList != null and idList.size() > 0”>
模糊查询LIKE参数未正确拼接,导致查询不到数据。test表达式中直接拼接字符串,如LIKE ‘%#{name}%’,这会导致参数被当作字符串字面量而非预编译参数。使用CONCAT函数:LIKE CONCAT(‘%’, #{name}, ‘%’),或使用<bind>标签提前构建模式字符串。

5.2 参数绑定与类型处理问题

问题现象可能原因检查与解决
报错:There is no getter for property ‘XXX’ in class ‘java.lang.Integer’test表达式中,直接使用了基本类型或包装类型的属性,如test=“status == 1”。MyBatis 的 OGNL 表达式在处理基本类型时,会尝试将其作为对象获取属性。对于简单类型的参数,在test中直接使用参数名进行比较即可,或者使用_parameter关键字。更推荐的做法是将查询条件封装成对象传入。
传入的布尔类型参数在test表达式中判断失效。MyBatis OGNL 对于布尔值的判断比较严格。test中使用== true== false进行显式判断,例如test=“isAdmin == true”
日期类型参数在比较时出错。在 SQL 中直接比较java.util.DateLocalDateTime对象与数据库日期字段。确保传入的参数类型与数据库字段类型兼容。对于范围查询,可以考虑在 Java 层将日期转换为字符串(格式与数据库匹配),或者在 SQL 中使用数据库函数(如DATE())进行处理。

5.3 性能与最佳实践考量

  1. 避免过度使用动态 SQL:过于复杂的动态 SQL 会降低 SQL 的可读性和可维护性,也可能影响数据库的查询计划缓存。如果条件组合爆炸式增长,可以考虑使用更专业的查询构建器(如 MyBatis-Plus 的QueryWrapper)或将部分逻辑移到业务层。
  2. <foreach>的批量大小:批量操作时,单次INSERT的 VALUES 列表不宜过长。建议在 Service 层进行分批,每批处理 500-1000 条数据。
  3. 索引失效风险:动态 SQL 可能导致某些查询用不上索引。例如,WHERE status = #{status},当statusnull时,条件不拼接,可能导致全表扫描。需要结合数据库表的具体索引设计来评估。
  4. 使用<sql>片段:将常用的列列表、条件片段提取出来,不仅能减少代码量,还能统一修改点。

6. 进阶:与 MyBatis 其他特性结合

6.1 动态 SQL 与 ResultMap 结合

ResultMap用于解决数据库字段名与 Java 对象属性名不一致的映射问题。动态 SQL 的查询结果可以映射到复杂的ResultMap

<resultMap id="UserDetailMap" type="User"> <id property="id" column="id"/> <result property="name" column="user_name"/> <!-- 字段别名映射 --> <result property="createTime" column="create_time"/> <!-- 可以包含 association, collection 等复杂映射 --> </resultMap> <select id="selectComplexUser" resultMap="UserDetailMap" parameterType="UserQuery"> SELECT id, name as user_name, age, create_time FROM user <where> <!-- 动态条件... --> </where> </select>

6.2 关于 MyBatis 的二级缓存

在搜索热词中提到了“二级缓存”。二级缓存是 MyBatis 的会话工厂(SqlSessionFactory)级别的缓存,可以跨 SqlSession 共享。但在使用动态 SQL 时,需要特别注意缓存的失效问题。

  • 缓存键的生成:MyBatis 根据 Mapper 语句的 ID、传递给语句的参数、分页参数等生成一个唯一的缓存键。如果动态 SQL 根据不同的参数生成了不同的 SQL,它们会被视为不同的查询,缓存键也不同。
  • 缓存失效:对一张表执行了 INSERT、UPDATE、DELETE 操作后,所有与该表相关的二级缓存区域都会被清空。在读写频繁的场景下,开启二级缓存可能弊大于利。
  • 配置与使用:需要在 MyBatis 全局配置中开启二级缓存,并在具体的 Mapper XML 文件中使用<cache/>标签声明。对于动态 SQL 多的查询,需要仔细评估数据一致性和性能提升的平衡。
<!-- 在 MyBatis 配置文件中 --> <settings> <setting name="cacheEnabled" value="true"/> </settings> <!-- 在具体的 Mapper.xml 中 --> <mapper namespace="com.example.demo.mapper.UserMapper"> <cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true"/> <!-- 你的动态 SQL 语句 --> </mapper>

6.3 在 Spring Boot 中集成 PageHelper 分页

另一个热词是“springboot集成mybatis pagehelper”。PageHelper 是一个优秀的分页插件,它与动态 SQL 可以完美结合。

  1. 添加依赖:
    <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.4.6</version> </dependency>
  2. 在 Service 层使用:
    public PageInfo<User> queryUsersByPage(UserQuery query) { // 紧跟在查询方法前调用,传入页码和每页数量 PageHelper.startPage(query.getPageNum(), query.getPageSize()); // 接下来执行的第一条 MyBatis 查询方法会被自动分页 List<User> list = userMapper.selectByConditionWithPage(query); // 用 PageInfo 包装结果,包含总条数、总页数等信息 return new PageInfo<>(list); }

关键点PageHelper.startPage()必须紧贴在执行数据库查询的代码之前。它通过一个本地线程变量(ThreadLocal)设置分页参数,然后由插件在运行时动态修改你的 SQL(添加LIMIT子句和计算总数的查询)。你的动态 SQL 完全不需要为分页做任何修改。

7. 总结与最佳实践清单

通过本文,你应该已经掌握了 MyBatis 动态 SQL 从基础到进阶的核心用法。最后,我们整理一份在项目中使用动态 SQL 的最佳实践清单,帮助你规避常见陷阱,写出更健壮的代码:

  1. 优先使用<where><set>:永远不要手动拼接WHERE 1=1或处理SET子句末尾的逗号,让标签自动处理。
  2. 始终进行非空判断:在<if>test表达式中,对字符串判断null'',对集合判断nullsize()
  3. 善用<bind>处理复杂表达式:对于重复的字符串拼接(如模糊查询)或复杂的 OGNL 表达式,使用<bind>提升可读性。
  4. 控制<foreach>的集合大小:批量操作时,在业务层控制每次传入的集合大小,避免单条 SQL 过长。
  5. 提取公共 SQL 片段:使用<sql><include>维护列名、条件等公共部分,保证一致性并便于修改。
  6. 结合分页插件:对于列表查询,集成 PageHelper 等分页插件,让动态 SQL 专注于业务条件,分页逻辑由插件透明处理。
  7. 关注生成的 SQL:开发阶段开启 MyBatis SQL 日志 (log-impl: org.apache.ibatis.logging.stdout.StdOutImpl),仔细核对动态生成的 SQL 是否符合预期。
  8. 谨慎使用二级缓存:在包含大量动态 SQL 或数据更新频繁的场景下,评估二级缓存带来的收益和潜在的数据一致性问题。
  9. 参数封装:将多个查询条件封装成 QueryDTO 对象传入,避免 Mapper 接口出现过多参数,也便于在test表达式中使用。
  10. 保持 XML 可读性:合理的缩进和注释对于复杂的动态 SQL 至关重要。如果一段动态 SQL 超过 50 行,考虑是否可以通过拆分或使用查询构建器来简化。

动态 SQL 是 MyBatis 的灵魂特性之一,它将 SQL 的灵活性与 Java 代码的结构化控制完美结合。从简单的<if>条件判断到复杂的<foreach>批量操作,理解并熟练运用这些标签,能让你在面对复杂多变的业务查询时游刃有余,真正实现“少写代码,多做事”的目标。在实际项目中,建议从简单的条件查询开始实践,逐步应用到更复杂的场景,并时刻关注生成的 SQL 语句,确保其正确性和性能。