MyBatis 中的 OGNL 表达式:从基础语法到实战应用
在 MyBatis 的 XML 映射文件(如动态 SQL、结果映射)中,OGNL(Object-Graph Navigation Language,对象图导航语言)是核心表达式语言,负责解析参数、判断条件、导航对象属性。本文将系统梳理 OGNL 表达式在 MyBatis 中的基础语法、核心用法及常见场景,帮助你灵活编写动态 SQL 和处理复杂参数映射。
OGNL 表达式的核心作用
OGNL 是一种强大的表达式语言,支持对象属性导航、方法调用、集合操作、逻辑运算等功能。在 MyBatis 中,OGNL 主要用于以下场景:
- 动态 SQL 条件判断:
if、when标签的test属性(如test="name != null and name != ''"); - 参数取值与转换:
#{}占位符的参数解析(如#{user.name}导航对象属性); - 结果映射与计算:
resultMap中的select属性、bind标签的变量定义(如value="'%' + name + '%'"); - 内置对象引用:访问 MyBatis 内置参数(如
_parameter、_databaseId)。
OGNL 基础语法(MyBatis 常用)
OGNL 语法简洁但功能强大,以下是 MyBatis 开发中高频使用的语法规则:
1. 基本数据类型与字面量
| 类型 | 示例 | 说明 |
|---|---|---|
| 字符串 | 'hello'、"world" |
单引号或双引号包裹,支持转义字符(如 'a\'b') |
| 数值 | 100、3.14、-5 |
整数、浮点数直接书写 |
| 布尔值 | true、false |
注意小写,区分 Java 的 True/False |
| 空值 | null |
判断参数是否为空(如 test="name == null") |
2. 对象属性导航
OGNL 支持通过.符号导航对象的多层属性,即使属性是嵌套对象也能直接访问。
示例:参数为复杂对象
假设有实体类 User:
1 | public class User { |
在动态 SQL 中通过 OGNL 导航属性:
1 | <select id="findUser" resultType="User"> |
3. 集合操作
OGNL 支持对 List、Array、Map 等集合类型进行判断和遍历,是 foreach 标签的核心表达式基础。
(1)判断集合是否为空
| 集合类型 | 非空判断表达式 | 空判断表达式 |
|---|---|---|
| List/Array | list != null and list.size() > 0 |
list == null or list.size() == 0 |
| Map | map != null and map.size() > 0 |
map == null or map.size() == 0 |
| 字符串 | str != null and str != '' |
str == null or str == '' |
示例:集合参数条件判断
1 | <select id="findUserByIds" resultType="User"> |
(2)Map 集合的键值访问
若参数是 Map,OGNL 支持通过 map.key 或 map['key'] 访问值:
1 | <select id="findUserByMap" resultType="User"> |
(3)数组的长度判断
数组通过 array.length 判断长度(注意是 length 属性,非方法):
1 | <select id="findUserByArray" resultType="User"> |
4. 逻辑运算与比较运算
OGNL 支持常见的逻辑运算符和比较运算符,需注意语法与 Java 的细微差异。
(1)逻辑运算符
| 运算符 | 作用 | 示例 | ||
|---|---|---|---|---|
&& / and |
逻辑与 | test="name != null and age > 18" |
||
| ` | /or` |
逻辑或 | test="name != null or age > 18" |
|
! / not |
逻辑非 | test="not (name == null)" |
(2)比较运算符
| 运算符 | 作用 | 示例 | 注意事项 |
|---|---|---|---|
== / eq |
等于 | test="age == 18" 或 test="age eq 18" |
字符串比较需加引号(如 name == '张三') |
!= / ne |
不等于 | test="age != 18" 或 test="age ne 18" |
|
> / gt |
大于 | test="age > 18" 或 test="age gt 18" |
避免使用 gt(可读性差,推荐 >) |
< / lt |
小于 | test="age < 18" 或 test="age lt 18" |
|
>= / ge |
大于等于 | test="age >= 18" 或 test="age ge 18" |
|
<= / le |
小于等于 | test="age <= 18" 或 test="age le 18" |
示例:多条件逻辑判断
1 | <select id="findUserByCondition" resultType="User"> |
5. 方法调用
OGNL 支持调用 Java 对象的非静态方法(需符合 JavaBean 规范),常见用于字符串处理、日期判断等场景。
常用方法示例
| 方法类型 | 示例 | 说明 |
|---|---|---|
| 字符串方法 | name != null and name.length() > 0 |
判断字符串长度 |
name != null and name.contains('张') |
判断字符串是否包含子串 | |
name != null and name.startsWith('李') |
判断字符串是否以某前缀开头 | |
| 日期方法 | createTime != null and createTime.after(new Date()) |
判断日期是否在当前时间之后(需注意时区) |
| 集合方法 | list != null and list.contains(100) |
判断集合是否包含指定元素 |
注意事项
- 方法调用需遵循 Java 语法,如参数列表需加括号(
length()而非length); - 避免调用复杂方法(如业务逻辑方法),仅用于简单的属性处理或判断;
- 静态方法调用需通过
@符号(如@java.lang.Math@random()),但 MyBatis 中不推荐使用(影响可读性)。
6. 内置对象与上下文变量
MyBatis 在 OGNL 上下文中内置了两个核心对象,用于简化参数处理:
(1)_parameter:代表整个参数
- 若参数是单个简单类型(如
Integer id),_parameter直接等于该参数(test="_parameter != null"等价于test="id != null"); - 若参数是复杂对象(如
User user),_parameter代表该对象(test="_parameter.id != null"等价于test="id != null"); - 若参数是多个参数(无
@Param注解),_parameter是封装参数的 Map(键为arg0、arg1或param1、param2)。
示例:使用 _parameter 简化多参数判断
1 | <!-- Mapper 接口:多个参数(无 @Param 注解) --> |
(2)_databaseId:代表当前数据库方言
若在 MyBatis 全局配置中配置了 databaseIdProvider,_databaseId 可用于多数据库适配(如 MySQL 与 Oracle 语法差异):
1 | <select id="findUser" resultType="User"> |
OGNL 实战场景与避坑指南
1. 动态 SQL 中的常见用法
(1)if 标签条件判断(非空 + 非空串)
1 | <if test="username != null and username != ''"> |
(2)bind 标签定义变量(避免数据库语法差异)
1 | <select id="findUserByLikeName" resultType="User"> |
(3)foreach 标签遍历集合(结合 OGNL 集合判断)
1 | <select id="findUserByIds" resultType="User"> |
2. 常见陷阱与解决方案
(1)字符串比较时未加引号
问题:test="username == admin"(错误,OGNL 会将 admin 视为变量,而非字符串);
解决方案:字符串需加单引号或双引号,test="username == 'admin'" 或 test='username == "admin"'。
(2)布尔值判断错误
问题:test="isActive == 'true'"(错误,布尔值无需加引号,会被解析为字符串比较);
解决方案:直接使用布尔值字面量,test="isActive == true" 或简化为 test="isActive"。
(3)日期比较时未处理时区
问题:test="createTime > new Date()"(new Date() 是当前 JVM 时间,可能与数据库时区不一致);
解决方案:传递标准化日期参数(如 LocalDateTime),或使用数据库函数(如 CURRENT_TIMESTAMP())。
(4)多参数未用 @Param 导致 _parameter 混乱
问题:多个参数无 @Param 注解,test="id != null" 报错(OGNL 无法识别 id);
解决方案:
- 方案 1:添加
@Param注解(推荐),List<User> findUser(@Param("id") Integer id, @Param("name") String name); - 方案 2:通过
_parameter的 Map 键访问,test="_parameter.param1 != null"(param1对应第一个参数)。
(5)集合为空判断遗漏 null 检查
问题:test="list.size() > 0"(若 list 为 null,会抛出 NullPointerException);
解决方案:先判断 null,再判断长度,test="list != null and list.size() > 0"。
OGNL 与 EL 表达式的区别(避免混淆)
新手常将 MyBatis 的 OGNL 与 JSP 的 EL 表达式(${})混淆,两者核心差异如下:
| 维度 | OGNL 表达式(MyBatis) | EL 表达式(JSP/Spring) |
|---|---|---|
| 应用场景 | MyBatis XML 动态 SQL、结果映射 | JSP 页面渲染、Spring 注解(如 @Value) |
| 语法符号 | 无特殊符号(直接写表达式,如 test="name != null") |
${} 包裹(如 ${user.name}) |
| 参数访问 | 支持对象导航、方法调用、集合操作 | 仅支持简单属性导航,不支持方法调用 |
| 上下文对象 | 内置 _parameter、_databaseId |
内置 pageContext、request、session |
关键提醒:MyBatis 中 #{}是预编译占位符(防 SQL 注入),${} 是字符串拼接(使用 OGNL 解析),需区分两者用途:
- 传递参数用
#{}(如#{username}); - 动态表名、排序字段用
${}(如ORDER BY ${sortField},需注意 SQL 注入风险)。
总结
OGNL 是 MyBatis 动态 SQL 的 “灵魂”,掌握其基础语法(属性导航、集合操作、逻辑运算)和核心场景(条件判断、参数解析),能大幅提升动态 SQL 的编写效率和灵活性。实际开发中需注意:
- 非空判断需同时检查
null和空值(如字符串name != null and name != ''); - 多参数优先使用
@Param注解,避免_parameter的 Map 键(param1/arg0); - 避免复杂方法调用和静态方法,保持表达式简洁可读;
- 区分
#{}和${}的用途,防止 SQL 注入
v1.3.10