bind
导学
本节你将掌握 MyBatis 动态 SQL 中用于 OGNL 上下文变量绑定的元素 bind。学习目标是:
- 理解
bind元素的核心作用(在 OGNL 上下文中创建新变量) - 掌握
name和value两个属性 - 能够使用
bind实现模糊查询的通配符拼接 - 能够使用
bind复用复杂的 OGNL 表达式
定义
bind 是 MyBatis 动态 SQL 中用于在 OGNL 上下文中创建新变量的元素。它允许开发者在 XML 映射文件中定义一个变量,该变量可以在当前 OGNL 上下文中被后续的 SQL 片段引用。
bind 最典型的应用场景是模糊查询中的 % 通配符拼接。在传统的 MyBatis 写法中,模糊查询通常这样写:
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
这种方式依赖数据库的字符串拼接函数(如 MySQL 的 CONCAT),不同数据库的函数名可能不同(Oracle 用 ||,SQL Server 用 +),导致 SQL 的可移植性降低。使用 bind 可以在 XML 层就完成 % 的拼接,生成统一的 LIKE ? 语法:
<bind name="pattern" value="'%' + name + '%'"/>
AND name LIKE #{pattern}
这样生成的 SQL 是数据库无关的 LIKE ?,由 JDBC 预编译参数传递拼接后的字符串值,既简洁又可移植。
适用位置与核心属性
bind 可以嵌套在 select、insert、update、delete 语句内部,或嵌套在 where、set、trim 等容器元素内部。通常放在语句的开头位置,以便后续 SQL 片段引用。
| 属性 | 是否必填 | 说明 |
|---|---|---|
name | 是 | 变量的名称,在 OGNL 上下文中通过该名称引用变量 |
value | 是 | OGNL 表达式,表达式的求值结果会绑定到 name 指定的变量上 |
核心原理
变量绑定与引用流程图
bind 在 OGNL 上下文中的作用
完整示例
场景说明
乐途学院的学生管理系统需要实现以下两个使用 bind 的场景:
- 模糊查询学生姓名:在 XML 中拼接
%通配符,生成数据库无关的LIKE ?语法 - 动态排序字段绑定:将传入的排序字段名绑定到变量中,用于
ORDER BY(注意:排序字段名不能使用#{ },需要使用${ })
操作前的数据库表结构及初始数据
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
age INT,
major VARCHAR(20),
score DECIMAL(5,2)
);
INSERT INTO student (name, age, major, score) VALUES
('大翔', 22, '计算机科学', 95.5),
('白歌', 21, '软件工程', 88.0),
('小崔', 20, '计算机科学', 92.0),
('黄俪', 21, '信息安全', 90.5),
('李眉', 22, '软件工程', 87.0);
当前表数据:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
| 2 | 白歌 | 21 | 软件工程 | 88.00 |
| 3 | 小崔 | 20 | 计算机科学 | 92.00 |
| 4 | 黄俪 | 21 | 信息安全 | 90.50 |
| 5 | 李眉 | 22 | 软件工程 | 87.00 |
完整的映射文件片段
StudentMapper.xml
<?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.flying.mapper.StudentMapper">
<!-- 示例1:bind 拼接模糊查询通配符 -->
<select id="findByNameLike" resultType="Student"
parameterType="com.flying.entity.Student">
<bind name="pattern" value="'%' + name + '%'"/>
SELECT id, name, age, major, score
FROM student
<where>
<if test="name != null and name != ''">
AND name LIKE #{pattern}
</if>
<if test="major != null and major != ''">
AND major = #{major}
</if>
</where>
</select>
<!-- 示例2:bind 绑定排序字段(使用 ${} 直接替换) -->
<select id="findWithOrder" resultType="Student"
parameterType="java.util.HashMap">
<bind name="orderColumn" value="orderColumn"/>
SELECT id, name, age, major, score
FROM student
<where>
<if test="major != null and major != ''">
AND major = #{major}
</if>
</where>
ORDER BY ${orderColumn}
</select>
<!-- 示例3:bind 复用复杂 OGNL 表达式 -->
<select id="findByComplexCondition" resultType="Student"
parameterType="com.flying.entity.Student">
<bind name="hasName" value="name != null and name != ''"/>
<bind name="hasMajor" value="major != null and major != ''"/>
<bind name="isHighScore" value="minScore != null and minScore >= 90"/>
SELECT id, name, age, major, score
FROM student
<where>
<if test="hasName">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="hasMajor">
AND major = #{major}
</if>
<if test="isHighScore">
AND score >= #{minScore}
</if>
</where>
</select>
</mapper>
StudentMapper.java(接口)
package com.flying.mapper;
import com.flying.entity.Student;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface StudentMapper {
List<Student> findByNameLike(Student condition);
List<Student> findWithOrder(@Param("orderColumn") String orderColumn,
@Param("major") String major);
List<Student> findByComplexCondition(Student condition);
}
实际执行结果
示例1:bind 拼接模糊查询通配符
测试代码:
Student condition = new Student();
condition.setName("翔");
List<Student> list = mapper.findByNameLike(condition);
最终生成的 SQL 语句:
SELECT id, name, age, major, score FROM student WHERE name LIKE ?
参数值: %翔%
查询结果集:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
分析: bind 将 '%' + name + '%' 求值为 '%翔%',绑定到变量 pattern 中。后续 #{pattern} 引用该变量,最终 SQL 是数据库无关的 LIKE ?,由 JDBC 传递参数值 %翔%。这种方式不依赖任何数据库特定的字符串拼接函数,具有良好的数据库可移植性。
示例2:bind 绑定排序字段
测试代码:
List<Student> list = mapper.findWithOrder("score", "计算机科学");
最终生成的 SQL 语句:
SELECT id, name, age, major, score FROM student WHERE major = ? ORDER BY score
参数值: 计算机科学
查询结果集:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
| 3 | 小崔 | 20 | 计算机科学 | 92.00 |
分析: 排序字段名是 SQL 关键字的一部分,不能使用 #{ }(预编译参数占位符),必须使用 ${ }(直接文本替换)。bind 将传入的 orderColumn 绑定到 OGNL 上下文中,${orderColumn} 直接将其值替换到 SQL 中。注意:使用 ${ } 存在 SQL 注入风险,必须在 Java 代码中对 orderColumn 做白名单校验,只允许传入预定义的字段名。
示例3:bind 复用复杂 OGNL 表达式
测试代码:
Student condition = new Student();
condition.setName("白");
condition.setMajor("软件工程");
condition.setMinScore(85.0);
List<Student> list = mapper.findByComplexCondition(condition);
最终生成的 SQL 语句:
SELECT id, name, age, major, score FROM student WHERE name LIKE CONCAT('%', ?, '%') AND major = ? AND score >= ?
参数值: 白, 软件工程, 85.0
查询结果集:
| id | name | age | major | score |
|---|---|---|---|---|
| 2 | 白歌 | 21 | 软件工程 | 88.00 |
分析: 三个 bind 分别将复杂的 OGNL 表达式(name != null and name != '' 等)绑定为简单的布尔变量(hasName、hasMajor、isHighScore)。后续的 if 直接使用这些变量,使 test 表达式更简洁、可读性更高。这在条件判断逻辑复杂时特别有用。
易错场景 / 常见误区
| 误区 | 错误示例 | 后果 | 正解 |
|---|---|---|---|
在 value 中使用 #{ } | value="'%' + #{name} + '%'" | 语法错误,bind 的 value 是 OGNL 表达式,不是 SQL,不能用 #{ } | value="'%' + name + '%'",直接写属性名 |
用 #{ } 引用排序字段 | ORDER BY #{orderColumn} | 排序字段被当作字符串参数处理,SQL 变成 ORDER BY 'score',按常量字符串排序,无意义 | 排序字段用 ${ } 直接替换:ORDER BY ${orderColumn} |
使用 ${ } 但不做白名单校验 | ORDER BY ${orderColumn} | 如果 orderColumn 被恶意传入 id; DROP TABLE student--,存在 SQL 注入风险 | Java 层对排序字段做白名单校验,只允许预定义字段 |
bind 的 name 与参数属性名冲突 | name 与传入对象的属性同名 | OGNL 上下文中的变量可能覆盖原始属性,导致意外行为 | 使用不与参数属性冲突的变量名,如 pattern、orderColumn |
认为 bind 可以跨语句复用 | 在 statement A 中 bind,在 statement B 中使用 | bind 的作用域仅限于当前语句内部,跨语句无效 | 每个需要变量的语句内部单独定义 bind |
面试考点
Q1:bind 元素的主要用途是什么?
A:
bind的主要用途是在 OGNL 上下文中创建一个新变量,供当前 SQL 语句的后续片段引用。最典型的应用场景是:① 模糊查询时在 XML 层拼接%通配符,生成数据库无关的LIKE ?语法;② 将复杂的 OGNL 表达式绑定为简单变量,提高test表达式的可读性;③ 为${ }直接替换提供变量来源(如动态排序字段)。
Q2:以下两种模糊查询写法有什么区别?
写法 A:
<bind name="pattern" value="'%' + name + '%'"/>+name LIKE #{pattern}写法 B:name LIKE CONCAT('%', #{name}, '%')
A:写法 A 在 XML 层通过 OGNL 表达式拼接
%通配符,生成的 SQL 是数据库无关的name LIKE ?,由 JDBC 预编译参数传递完整的模式字符串。写法 B 依赖数据库特定的字符串拼接函数(如 MySQL 的CONCAT),不同数据库语法不同(Oracle 用||,SQL Server 用+),可移植性较差。写法 A 更推荐,因为它不依赖特定数据库函数。
Q3:#{ } 和 ${ } 有什么区别?bind 与它们如何配合?
A:
#{ }是预编译参数占位符,MyBatis 会将其替换为?,参数值由 JDBC 安全传递,可以防止 SQL 注入。${ }是直接的文本替换,MyBatis 会将其值直接拼接到 SQL 字符串中,不做预编译处理,存在 SQL 注入风险。bind创建的变量既可以用#{ }引用(如#{pattern},安全),也可以用${ }引用(如${orderColumn},需要白名单校验)。
Q4:以下代码能否达到预期效果?
<bind name="name" value="'%' + name + '%'"/>
A:不能,且存在严重问题。
bind的name属性指定了变量名为name,这会覆盖 OGNL 上下文中原始的name属性(传入参数对象的name字段)。后续如果还需要引用原始的name值,会发现它已经被修改了。正确做法是使用不与参数属性冲突的变量名,如pattern。
小结
bind 是 MyBatis 动态 SQL 中用于 OGNL 上下文变量绑定的实用元素。通过 name 和 value 两个属性,它可以在 XML 映射文件中创建临时变量,供后续 SQL 片段引用。最典型的应用是模糊查询的 % 通配符拼接,它让 SQL 摆脱了对数据库特定字符串函数的依赖,实现了更好的可移植性。
使用 bind 时需要注意:① value 中是 OGNL 表达式,不是 SQL,不能用 #{ };② 变量名不要与参数属性名冲突;③ 配合 ${ } 使用时必须在 Java 层做白名单校验,防止 SQL 注入。
至此,MyBatis 动态 SQL 的七大核心元素(if、choose/when/otherwise、where、set、trim、foreach、bind)已经全部学习完毕。掌握这些元素后,你可以编写出灵活、安全、优雅的动态 SQL,彻底告别 JDBC 时代的字符串拼接噩梦。
下一章引子
动态 SQL 让你能够灵活地构建查询和更新语句,但在实际项目中,你还需要掌握 MyBatis 的高级映射技巧——如何处理一对一、一对多的关联查询?如何使用结果映射(ResultMap)处理复杂的对象嵌套结构?下一章我们将深入探讨 MyBatis 的关联映射与 ResultMap 高级用法,让你的持久层代码更加强大。