mybatis基本使用
mybatis基本使用流程
- 基本使用
- 封装工具类
- mybatis配置
- 开启日志和驼峰映射
- `#{}`与`${}`区别
- `#{}`(预编译参数)
- `${}`(字符串替换)
- 总结区别
- mybatis接收参数语法
- 单个参数
- 多个参数
- map作为参数
- 实体类作为参数
- resultMap
- 如何自定义resultMap?
- 复杂场景映射
- 场景 1:一对一关联(association)
- 场景 2:一对多关联(collection)
- 高级映射特性
- 继承 resultMap
- 多层嵌套示例
- 核心动态 SQL 标签
- SQL 片段基本概念
- `<if>` 标签
- `<choose>`, `<when>`, `<otherwise>` 标签
- `<where>` 标签
- `<set>` 标签
- `<trim>` 标签
- `<foreach>` 标签
- `<bind>` 标签
- 特殊字符
- 转义字符
- 基础比较运算
- 复杂条件组合
- 在动态 SQL 的 test 属性中使用
- CDATA
- 基础使用 CDATA
- 比较运算
- 复杂查询条件
- 在 SQL 片段中使用 CDATA
- 适用场景对比
基本使用
@Testpublic void user() throws Exception {// 将配置文件转为输入流InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");// 创建SqlSessionFactoryBuilder,作用是根据配置文件,创建SqlSessionFactory工厂SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();// 根据配置文件,创建工程SqlSessionFactory,全局唯一SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);// 工厂创建SqlSession,建立一次会话SqlSession sqlSession = sqlSessionFactory.openSession();// 使用和指定语句的参数和返回值相匹配的接口,映射器实例UserMapper userMapper = sqlSession.getMapper(UserMapper.class);User user = userMapper.selectUserById(1);System.out.println("user = " + user);sqlSession.close();}
封装工具类
- 通过静态代码块来创建唯一的
SqlSessionFactory工厂。
public class SqlSessionUtils {// 提取出来的目的是,全局只需要一个SqlSessionFactory对象实例private static SqlSessionFactory sqlSessionFactory;static {try {// 配置文件流InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");// 创建SqlSessionFactoryBuilderSqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();// 通过配置文件流,创建SqlSessionFactorysqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);// 通过openSession,创建SqlSessionn} catch (IOException e) {throw new RuntimeException(e);}}/*** 手动提交事务* @return sqlSession*/public static SqlSession openSession() {return sqlSessionFactory.openSession();}/*** 增删改语句,需要手动提交事务,我们在获取sqlSession时,设置自动提交事务* @return sqlSession*/public static SqlSession openAutoCommitSession(){return sqlSessionFactory.openSession(true);}
}
mybatis配置
开启日志和驼峰映射
官网地址:https://mybatis.org/mybatis-3/zh_CN/configuration.html#settings
-
日志输出:
<setting name="logImpl" value="STDOUT_LOGGING"/> -
驼峰映射:
<setting name="mapUnderscoreToCamelCase" value="true"/>
#{}与${}区别
#{}(预编译参数)
- 安全性:使用
#{}会创建预编译语句(PreparedStatement),参数值会被安全地替换,不会导致SQL注入攻击。 - 性能:由于是预编译,可以重复利用执行计划,提高性能。
- 用法:适用于传入参数值,例如条件值、插入值等。
- 示例:
<select id="selectUserById" parameterType="int" resultType="User">SELECT * FROM user WHERE id = #{id}</select>
实际执行时,SQL语句会被编译为:SELECT * FROM user WHERE id = ?,然后通过PreparedStatement的set方法设置参数值。
${}(字符串替换)
- 安全性:使用
${}会直接进行字符串替换,不会对参数值进行转义,存在SQL注入风险。 - 性能:不会预编译,每次都可能需要重新编译SQL,性能较低。
- 用法:适用于动态指定表名、列名等非参数值的地方。如果需要动态指定SQL片段,且无法用
#{}替代的情况。 - 示例:
<select id="selectUserByTable" resultType="User">SELECT * FROM ${tableName} WHERE id = #{id}</select>
这里${tableName}会被直接替换为传入的字符串,比如传入tableName为"user",则SQL为:SELECT * FROM user WHERE id = ?。
总结区别
| 特性 | #{} | ${} |
|---|---|---|
| 安全性 | 高,防止SQL注入 | 低,有SQL注入风险 |
| 性能 | 高(预编译) | 低(直接拼接) |
| 使用场景 | 参数值 | 动态表名、列名等 |
| 括号内内容 | 可以是任意值,通常为属性名 | 通常是属性名,且要注意注入问题 |
-
绝大部分情况下使用
#{},特别是当参数是用户输入时。 -
只有在需要动态指定表名、列名等无法使用预编译参数的地方才使用
${},并且要确保这些值不是来自用户输入,或者已经进行了严格的过滤。
mybatis接收参数语法
单个参数
单个参数:在xml中可以写任意名称
- Mapper映射器定义方法。
public interface StudentMapper {Student getStudentById(int id);
}
Mapper.xml编写Sql语句
<mapper namespace="com.maven.mapper.StudentMapper"><select id="getStudentById" resultType="com.maven.pojo.Student" parameterType="int">select*from student where id = #{id}</select>
</mapper>
- 运行mybatis,执行Sql语句。
@Testpublic void getStudentById() throws Exception {InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);// 根据Id,查询学生Student student = mapper.getStudentById(1);System.out.println("user = " + student);List<Student> allStudents = mapper.getAllStudents();System.out.println("allStudents = " + allStudents);sqlSession.close();}
- 结果返回:
Opening JDBC Connection
Created connection 1261198850.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4b2c5e02]
==> Preparing: select * from student where id = ?
==> Parameters: 1(Integer)
<== Columns: id, student_no, name, age, gender, class_name, major, phone, email, address, enrollment_date, birthday, status, create_time, update_time
<== Row: 1, 2023001001, 张三, 20, 1, 计算机科学与技术1班, 计算机科学与技术, 13800138001, zhangsan@example.com, 北京市海淀区, 2023-09-01, 2003-05-15, 1, 2025-10-22 17:14:30, 2025-10-22 17:14:30
<== Total: 1
user = Student(id=1, studentNo=2023001001, name=张三, age=20, gender=1, className=计算机科学与技术1班, major=计算机科学与技术, phone=13800138001, email=zhangsan@example.com, address=北京市海淀区, enrollmentDate=2023-09-01, birthday=2003-05-15, status=1, createTime=2025-10-22T17:14:30, updateTime=2025-10-22T17:14:30)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4b2c5e02]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4b2c5e02]
Returned connection 1261198850 to pool.
多个参数
- 多个参数时,需要使用Mybaties注解
@Param("id") Integer id, @Param("name") String name注解来与形参对应。
- StudentMapper映射器定义方法。
public interface StudentMapper {Student getStudentByIdAndName(@Param("id") Integer id, @Param("name") String name);
}
- studentMapper.xml定义SQL语句。
<mapper namespace="com.maven.mapper.StudentMapper"><select id="getStudentByIdAndName" resultType="com.maven.pojo.Student">select * from student where id = #{id} and name = #{name}</select>
</mapper>
- 使用工具类调用Mybatis。
@Testpublic void getStudentByIdAndName() throws Exception {SqlSession sqlSession = SqlSessionUtils.openSession();StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);Student student = mapper.getStudentByIdAndName(1, "张三");System.out.println("student = " + student);}
- 返回结果:
Created connection 79438382.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4bc222e]
==> Preparing: select * from student where id = ? and name = ?
==> Parameters: 1(Integer), 张三(String)
<== Columns: id, student_no, name, age, gender, class_name, major, phone, email, address, enrollment_date, birthday, status, create_time, update_time
<== Row: 1, 2023001001, 张三, 20, 1, 计算机科学与技术1班, 计算机科学与技术, 13800138001, zhangsan@example.com, 北京市海淀区, 2023-09-01, 2003-05-15, 1, 2025-10-22 17:14:30, 2025-10-22 17:14:30
<== Total: 1
student = Student(id=1, studentNo=2023001001, name=张三, age=20, gender=1, className=计算机科学与技术1班, major=计算机科学与技术, phone=13800138001, email=zhangsan@example.com, address=北京市海淀区, enrollmentDate=2023-09-01, birthday=2003-05-15, status=1, createTime=2025-10-22T17:14:30, updateTime=2025-10-22T17:14:30)
map作为参数
- Map进行传参时,
- 方式一:必须使用注解
@Param(map) Map map为Map定义新的名称,使用时${map.id}调用参数。 - 方式二:不适用注解,直接将Map中的
key,作为xml中sql语句参数的具体名称。
- 方式一:必须使用注解
- 映射器定义方法。
public interface StudentMapper {// 方式一Student getStudentByMap(@Param("map")Map map);// 方式二Student getStudentByMap2(Map map);
}
- xml文件编写sql。
<mapper namespace="com.maven.mapper.StudentMapper"><select id="getStudentByMap" resultType="com.maven.pojo.Student">select * from student where id = #{map.id} and name = #{map.name}</select><select id="getStudentByMap2" resultType="com.maven.pojo.Student">select * from student where id = #{id} and name = #{name}</select>
</mapper>
实体类作为参数
- 单实体类,可以直接通过类属性字段,进行调用。
- mapper映射器定义方法。
public interface StudentMapper {// 使用类属性字段,进行映射Student getStudent(Student student);Student getStudent2(@Param("student") Student student);List<Student> getStudent3(@Param("student") Student student);
}
- xml文件编写Sql。
<mapper namespace="com.maven.mapper.StudentMapper"><!--类属性字段,进行映射--><select id="getStudent" resultType="com.maven.pojo.Student" parameterType="com.maven.pojo.Student" >select * from student where id = #{id}</select><select id="getStudent2" resultType="com.maven.pojo.Student" parameterType="com.maven.pojo.Student" >select*from studentwhere 1=1<if test="student.id != null" >id = #{student.id}</if><if test="student.name != null and student.name !=''" >and name = #{student.name}</if></select><select id="getStudent3" resultType="com.maven.pojo.Student" parameterType="com.maven.pojo.Student" >select*from studentwhere 1=1<if test="student.id != null" >id = #{student.id}</if><if test="student.name != null and student.name !=''" >and name = #{student.name}</if><if test="student.gender != null" >and gender = #{student.gender}</if></select>
</mapper>
resultMap
Mybatis默认封装规则(resultType):JavaBean职工的属性名去数据库中找对应列名的值,一一映射封装。- 自定义规则(
resultMap):我们来告诉Mybatis如何把结果封装到Bean中。
MyBatis中,自定义封装resultMap是一种强大的方式,用于将数据库查询结果映射到Java对象。当数据库列名与Java对象属性名不一致,或者需要处理复杂的关联关系(如一对一、一对多)时,使用resultMap非常有用。
如何自定义resultMap?
- 在Mapper XML文件中,使用
<resultMap>元素定义映射规则。 <resultMap>包含以下属性:- id: 当前resultMap的唯一标识
- type: 映射的Java类型,可以是别名或全限定名
- 子元素:
<id>: 用于映射主键列<result>: 用于映射普通列<association>: 用于映射一对一关系<collection>: 用于映射一对多关系
- 每个和元素包含以下属性:
- property: Java对象的属性名
- column: 数据库表的列名
- jdbcType: 列对应的JDBC类型(可选,MyBatis通常可以自动识别)
- typeHandler: 类型处理器(可选)
- 对于关联关系:
<association>用于另一个Java对象作为当前对象的属性。<collection>用于一个Java对象集合作为当前对象的属性,使用ofType属性指定集合内的java对象。
复杂场景映射
场景 1:一对一关联(association)
- User实体类内,引用了UserProfile实体类
// 用户详情实体
public class UserProfile {private Long id;private Long userId;private String realName;private String address;private String phone;
}// 用户实体包含详情
public class User {private Long id;private String username;private String email;private UserProfile profile; // 一对一关联
}
<!-- 方式1:嵌套结果映射 -->
<resultMap id="UserWithProfileResultMap" type="User"><id property="id" column="id"/><result property="username" column="username"/><result property="email" column="email"/><association property="profile" javaType="UserProfile"><id property="id" column="profile_id"/><result property="userId" column="id"/><result property="realName" column="real_name"/><result property="address" column="address"/><result property="phone" column="phone"/></association>
</resultMap><select id="selectUserWithProfile" resultMap="UserWithProfileResultMap">SELECT u.id, u.username, u.email,p.id as profile_id, p.real_name, p.address, p.phoneFROM user uLEFT JOIN user_profile p ON u.id = p.user_idWHERE u.id = #{id}
</select>
场景 2:一对多关联(collection)
// 订单实体
public class Order {private Long id;private String orderNo;private BigDecimal amount;private LocalDateTime createTime;
}// 用户实体包含订单列表
public class User {private Long id;private String username;private String email;private List<Order> orders; // 一对多关联
}
<!-- 一对多映射 -->
<resultMap id="UserWithOrdersResultMap" type="User"><id property="id" column="id"/><result property="username" column="username"/><result property="email" column="email"/><collection property="orders" ofType="Order"><id property="id" column="order_id"/><result property="orderNo" column="order_no"/><result property="amount" column="amount"/><result property="createTime" column="order_create_time"/></collection>
</resultMap><select id="selectUserWithOrders" resultMap="UserWithOrdersResultMap">SELECT u.id, u.username, u.email,o.id as order_id, o.order_no, o.amount, o.create_time as order_create_timeFROM user uLEFT JOIN orders o ON u.id = o.user_idWHERE u.id = #{id}
</select>
高级映射特性
继承 resultMap
<!-- 基础 resultMap -->
<resultMap id="BaseUserResultMap" type="User"><id property="id" column="id"/><result property="username" column="username"/><result property="email" column="email"/>
</resultMap><!-- 扩展的 resultMap -->
<resultMap id="ExtendedUserResultMap" type="User" extends="BaseUserResultMap"><result property="age" column="age"/><result property="createTime" column="create_time"/><association property="profile" javaType="UserProfile"><!-- 关联映射 --></association>
</resultMap>
多层嵌套示例
// 多层嵌套的实体结构
public class User {private Long id;private String username;private List<Order> orders;
}public class Order {private Long id;private String orderNo;private List<OrderItem> items;
}public class OrderItem {private Long id;private String productName;private Integer quantity;private Product product;
}public class Product {private Long id;private String name;private BigDecimal price;
}
<!-- 多层嵌套 resultMap -->
<resultMap id="UserDetailResultMap" type="User"><id property="id" column="user_id"/><result property="username" column="username"/><collection property="orders" ofType="Order"><id property="id" column="order_id"/><result property="orderNo" column="order_no"/><collection property="items" ofType="OrderItem"><id property="id" column="item_id"/><result property="productName" column="product_name"/><result property="quantity" column="quantity"/><association property="product" javaType="Product"><id property="id" column="product_id"/><result property="name" column="product_name"/><result property="price" column="price"/></association></collection></collection>
</resultMap><select id="selectUserDetail" resultMap="UserDetailResultMap">SELECT u.id as user_id, u.username,o.id as order_id, o.order_no,oi.id as item_id, oi.product_name, oi.quantity,p.id as product_id, p.name as product_name, p.priceFROM user uLEFT JOIN orders o ON u.id = o.user_idLEFT JOIN order_item oi ON o.id = oi.order_idLEFT JOIN product p ON oi.product_id = p.idWHERE u.id = #{id}
</select>
核心动态 SQL 标签
MyBatis 动态SQL允许我们在XML映射文件中,根据条件动态地构建SQL语句。它通过一系列标签来实现,如if、choose、when、otherwise、trim、where、set、foreach等。
SQL 片段基本概念
SQL 片段 是 MyBatis 中用于重用 SQL 代码块的特性,通过 <sql> 标签定义,通过 <include> 标签引用。
SQL 片段示例:
1. 基础字段列表
<!-- 定义用户表字段片段 -->
<sql id="userBaseColumns">id, username, password, email, age, create_time, update_time
</sql><!-- 定义用户表名片段 -->
<sql id="userTable">user
</sql><!-- 使用片段 -->
<select id="selectAllUsers" resultType="User">SELECT <include refid="userBaseColumns"/>FROM <include refid="userTable"/>
</select>
生成的 SQL:
SELECT id, username, password, email, age, create_time, update_time FROM user
2. 带参数的 SQL 片段
- 使用属性传递参数
<!-- 定义带参数的片段 -->
<sql id="userColumnsWithPrefix">${prefix}.id,${prefix}.username,${prefix}.email,${prefix}.create_time
</sql><!-- 使用片段并传递参数 -->
<select id="selectUserWithOrders" resultType="map">SELECT <include refid="userColumnsWithPrefix"><property name="prefix" value="u"/></include>,o.order_no,o.amountFROM user uLEFT JOIN orders o ON u.id = o.user_id
</select>
生成的 SQL:
SELECT u.id, u.username, u.email, u.create_time,o.order_no, o.amount
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
3. 复杂 SQL 片段
- 条件片段
<!-- 定义通用查询条件 -->
<sql id="userWhereCondition"><where><if test="username != null and username != ''">AND username LIKE CONCAT('%', #{username}, '%')</if><if test="email != null and email != ''">AND email = #{email}</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>
</sql><!-- 使用条件片段 -->
<select id="findUsers" parameterType="User" resultType="User">SELECT * FROM user <include refid="userWhereCondition"/>ORDER BY create_time DESC
</select>
4. 分页片段
<!-- 定义分页片段 -->
<sql id="pagination"><if test="pageSize != null and pageSize > 0">LIMIT #{pageSize}<if test="offset != null and offset >= 0">OFFSET #{offset}</if></if>
</sql><!-- 使用分页片段 -->
<select id="findUsersByPage" parameterType="map" resultType="User">SELECT * FROM user <include refid="userWhereCondition"/>ORDER BY id DESC<include refid="pagination"/>
</select>
<if> 标签
作用:根据条件判断是否包含 SQL 片段
属性:test:必需的 OGNL 表达式,返回 true/false
执行逻辑:
- 每个
<if>都是独立判断的 - 多个
<if>可能同时生效 - 条件之间没有互斥关系
<select id="findUsers" parameterType="User" resultType="User">SELECT * FROM user WHERE 1=1<if test="username != null and username != ''">AND username = #{username}</if><if test="email != null and email != ''">AND email = #{email}</if><if test="age != null">AND age = #{age}</if>
</select>
<choose>, <when>, <otherwise> 标签
作用:实现多条件选择,类似 Java 的 switch-case
choose的工作机制:
<choose> 类似于 Java 中的 switch-case 或 if-else if-else 结构:
- 按顺序检查每个
<when>的条件 - 第一个满足条件的
<when>会生效 - 后续的
<when>不再检查 - 如果所有
<when>都不满足,则<otherwise>生效
<select id="findUsers" parameterType="User" resultType="User">SELECT * FROM user WHERE 1=1<choose><when test="username != null and username != ''">AND username = #{username}</when><when test="email != null and email != ''">AND email = #{email}</when><otherwise>AND status = 1</otherwise></choose>
</select>
<where> 标签
作用:智能处理 WHERE 子句,自动去除多余的 AND/OR
解决的问题:
- 避免 WHERE 后面直接跟 AND/OR 的语法错误
- 自动管理 WHERE 关键字
智能处理规则:
- 如果内容不为空,自动添加 WHERE 关键字
- 自动去除第一个条件前的 AND/OR
- 如果内容为空,不生成 WHERE 子句
<select id="findUsers" parameterType="User" resultType="User">SELECT * FROM user <where><if test="username != null and username != ''">AND username = #{username}</if><if test="email != null and email != ''">AND email = #{email}</if><if test="age != null">AND age = #{age}</if></where>
</select>
生成的 SQL 示例:
- 如果所有条件都为空:
SELECT * FROM user - 如果只有 username 有值:
SELECT * FROM user WHERE username = ?
不同场景的生成结果:
| 输入条件 | 生成的 SQL | 说明 |
|---|---|---|
| 所有条件都为空 | SELECT * FROM user | 不生成 WHERE |
| 只有 username | SELECT * FROM user WHERE username = ? | 自动去除第一个 AND |
| username 和 age | SELECT * FROM user WHERE username = ? AND age = ? | 正常连接条件 |
<set> 标签
作用:智能处理 UPDATE 语句的 SET 子句,自动去除多余的逗号
解决的问题:
- 避免 SET 字段列表末尾有多余逗号
- 动态构建 UPDATE 语句
智能处理:
- 自动去除最后一个字段后的逗号
- 如果所有条件都不满足,SET 子句为空(会导致SQL错误)
<update id="updateUser" parameterType="User">UPDATE user <set><if test="username != null and username != ''">username = #{username},</if><if test="email != null and email != ''">email = #{email},</if><if test="age != null">age = #{age},</if><if test="updateTime != null">update_time = #{updateTime}</if></set>WHERE id = #{id}
</update>
<trim> 标签
作用:更灵活地处理 SQL 片段的前缀和后缀
语法:
<trim prefix="前缀" prefixOverrides="要移除的前缀" suffix="后缀" suffixOverrides="要移除的后缀"><!-- SQL片段 -->
</trim>
属性说明:
prefix:在整个内容前添加的前缀suffix:在整个内容后添加的后缀prefixOverrides:移除内容开头指定的字符串(多个用 | 分隔)suffixOverrides:移除内容末尾指定的字符串(多个用 | 分隔)
<!-- 等同于 <where> 标签 -->
<select id="findUsers" parameterType="User" resultType="User">SELECT * FROM user <trim prefix="WHERE" prefixOverrides="AND |OR "><if test="username != null and username != ''">AND username = #{username}</if><if test="email != null and email != ''">AND email = #{email}</if></trim>
</select><!-- 等同于 <set> 标签 -->
<update id="updateUser" parameterType="User">UPDATE user <trim prefix="SET" suffixOverrides=","><if test="username != null">username = #{username},</if><if test="email != null">email = #{email},</if></trim>WHERE id = #{id}
</update>
<foreach> 标签
作用:遍历集合,构建 IN 查询或批量操作
语法:
<foreach collection="集合" item="元素变量" index="索引变量" open="开始符号" close="结束符号" separator="分隔符"><!-- 循环内容 -->
</foreach>
属性说明:
collection:要遍历的集合属性名item:每次遍历时的元素变量名index:索引变量名(可选)open:整个循环开始时的字符串close:整个循环结束时的字符串separator:每次循环之间的分隔符
<!-- IN 查询 -->
<select id="findUsersByIds" resultType="User">SELECT * FROM user WHERE id IN<foreach collection="list" item="id" open="(" separator="," close=")">#{id}</foreach>
</select><!-- 批量插入 -->
<insert id="batchInsertUsers" parameterType="java.util.List">INSERT INTO user (username, email, age) VALUES <foreach collection="list" item="user" separator=",">(#{user.username}, #{user.email}, #{user.age})</foreach>
</insert><!-- 批量更新 -->
<update id="batchUpdateUsers" parameterType="java.util.List"><foreach collection="list" item="user" separator=";">UPDATE user SET username = #{user.username}, email = #{user.email}WHERE id = #{user.id}</foreach>
</update>
<bind> 标签
作用:创建变量并在当前上下文使用
语法:
<bind name="变量名" value="OGNL表达式"/>
使用场景:
- 模糊查询参数处理
- 复杂表达式计算
- 重复使用的值
<select id="findUsers" parameterType="User" resultType="User"><bind name="pattern" value="'%' + username + '%'" />SELECT * FROM user WHERE username LIKE #{pattern}
</select><!-- 处理复杂逻辑 -->
<select id="findUsers" parameterType="User" resultType="User"><bind name="namePattern" value="username != null ? '%' + username + '%' : '%'" /><bind name="minAge" value="age != null ? age : 0" />SELECT * FROM user WHERE username LIKE #{namePattern}AND age >= #{minAge}
</select>
特殊字符
转义字符
核心概念:
- 转义字符 是用特定的字符序列来表示 XML 中的特殊字符,让 XML 解析器能够正确识别。
| 特殊字符 | 转义序列 | 示例 | 说明 |
|---|---|---|---|
< | < | age < 18 | 小于号 |
> | > | age > 65 | 大于号 |
& | & | name = 'Tom&Jerry' | 和号 |
' | ' | name = 'John's' | 单引号 |
" | " | name = "John" | 双引号 |
基础比较运算
<!-- 使用转义字符处理比较运算符 -->
<select id="findUsersByAgeRange" parameterType="map" resultType="User">SELECT * FROM user WHERE age >= #{minAge} <!-- >= -->AND age <= #{maxAge} <!-- <= -->AND status <> 0 <!-- <> 不等于 -->
</select>
复杂条件组合
<select id="findProducts" parameterType="map" resultType="Product">SELECT * FROM product <where><!-- 价格范围条件 --><if test="minPrice != null">AND price >= #{minPrice}</if><if test="maxPrice != null">AND price <= #{maxPrice}</if><!-- 库存条件 --><if test="lowStock != null and lowStock == true">AND stock < 10</if><!-- 包含特殊字符的字符串 --><if test="brand != null">AND brand LIKE '%&%Co.' <!-- 查找包含 & 的品牌 --></if></where>
</select>
在动态 SQL 的 test 属性中使用
<select id="findUsers" parameterType="User" resultType="User">SELECT * FROM user <where><!-- 在 test 属性中使用转义字符 --><if test="age < 18">AND category = 'MINOR'</if><if test="age >= 18 and age < 65">AND category = 'ADULT'</if><if test="age >= 65">AND category = 'SENIOR'</if><!-- 复杂条件 --><if test="(score < 60 and retryCount > 3) or status <> 'ACTIVE'">AND need_review = 1</if></where>
</select>
CDATA
核心概念
CDATA(Character Data) 是 XML 中的特殊区块,告诉解析器区块内的内容应作为纯文本处理,不进行 XML 解析。- 不能在属性中使用。例如
<if test="">,不能再test中使用。
语法结构:
<![CDATA[这里的内容不会被XML解析器解析可以直接使用 < > & 等特殊字符
]]>
基础使用 CDATA
<select id="findProducts" parameterType="map" resultType="Product">SELECT * FROM product WHERE category = #{category}<!-- 只有比较运算符使用 CDATA -->AND price <![CDATA[ > ]]> #{minPrice}AND price <![CDATA[ < ]]> #{maxPrice}<!-- 普通条件不使用 CDATA -->AND status = 'ACTIVE'AND stock > 0
</select>
比较运算
<select id="findUsersByAge" parameterType="map" resultType="User">SELECT * FROM user WHERE 1=1<if test="minAge != null">AND age <![CDATA[ >= ]]> #{minAge}</if><if test="maxAge != null">AND age <![CDATA[ <= ]]> #{maxAge}</if><if test="excludeStatus != null">AND status <![CDATA[ <> ]]> #{excludeStatus}</if>
</select>
复杂查询条件
<select id="findComplexUsers" parameterType="map" resultType="User">SELECT * FROM user <where><!-- 年龄范围条件 --><if test="ageRange != null">AND age <![CDATA[ > ]]> #{ageRange.min} AND age <![CDATA[ < ]]> #{ageRange.max}</if><!-- 分数条件 --><if test="scoreCondition != null">AND (score <![CDATA[ < ]]> 60 OR score <![CDATA[ > ]]> 90)</if><!-- 时间范围条件 --><if test="startDate != null and endDate != null">AND create_time <![CDATA[ >= ]]> #{startDate} AND create_time <![CDATA[ <= ]]> #{endDate}</if></where>
</select>
在 SQL 片段中使用 CDATA
<!-- 定义包含 CDATA 的 SQL 片段 -->
<sql id="ageCondition"><if test="minAge != null">AND age <![CDATA[ >= ]]> #{minAge}</if><if test="maxAge != null">AND age <![CDATA[ <= ]]> #{maxAge}</if>
</sql><!-- 使用片段 -->
<select id="findUsers" parameterType="map" resultType="User">SELECT * FROM user <where><include refid="ageCondition"/><if test="status != null">AND status = #{status}</if></where>
</select>
适用场景对比
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 简单比较运算 | (转义字符) | 代码简洁,处理简单 |
| 复杂SQL逻辑 | (CDATA) | 可读性更重要 |
| test属性中的条件 | (转义字符) | CDATA不能在属性中使用 |
| 包含多个特殊字符 | (CDATA) | 避免多次转义,更清晰 |
| 性能敏感场景 | (转义字符) | 解析开销稍小 |
