0%

OGNL表达式

MyBatis 中的 OGNL 表达式:从基础语法到实战应用

在 MyBatis 的 XML 映射文件(如动态 SQL、结果映射)中,OGNL(Object-Graph Navigation Language,对象图导航语言)是核心表达式语言,负责解析参数、判断条件、导航对象属性。本文将系统梳理 OGNL 表达式在 MyBatis 中的基础语法、核心用法及常见场景,帮助你灵活编写动态 SQL 和处理复杂参数映射。

OGNL 表达式的核心作用

OGNL 是一种强大的表达式语言,支持对象属性导航、方法调用、集合操作、逻辑运算等功能。在 MyBatis 中,OGNL 主要用于以下场景:

  1. 动态 SQL 条件判断ifwhen 标签的 test 属性(如 test="name != null and name != ''");
  2. 参数取值与转换#{}占位符的参数解析(如 #{user.name} 导航对象属性);
  3. 结果映射与计算resultMap 中的 select 属性、bind 标签的变量定义(如 value="'%' + name + '%'");
  4. 内置对象引用:访问 MyBatis 内置参数(如 _parameter_databaseId)。

OGNL 基础语法(MyBatis 常用)

OGNL 语法简洁但功能强大,以下是 MyBatis 开发中高频使用的语法规则:

1. 基本数据类型与字面量

类型 示例 说明
字符串 'hello'"world" 单引号或双引号包裹,支持转义字符(如 'a\'b'
数值 1003.14-5 整数、浮点数直接书写
布尔值 truefalse 注意小写,区分 Java 的 True/False
空值 null 判断参数是否为空(如 test="name == null"

2. 对象属性导航

OGNL 支持通过.符号导航对象的多层属性,即使属性是嵌套对象也能直接访问。

示例:参数为复杂对象

假设有实体类 User

1
2
3
4
5
6
7
8
9
10
11
12
public class User {
private Integer id;
private String username;
private Address address; // 嵌套对象:Address 含 province、city 属性
// getter/setter
}

public class Address {
private String province;
private String city;
// getter/setter
}

在动态 SQL 中通过 OGNL 导航属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="findUser" resultType="User">
SELECT * FROM user
<where>
<!-- 1. 直接访问 User 的一级属性 -->
<if test="id != null">AND id = #{id}</if>
<if test="username != null and username != ''">AND username LIKE #{username}</if>

<!-- 2. 导航嵌套对象 Address 的属性 -->
<if test="address != null and address.province != null">
AND province = #{address.province}
</if>
<if test="address != null and address.city != ''">
AND city = #{address.city}
</if>
</where>
</select>

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
2
3
4
5
6
7
8
9
10
11
12
<select id="findUserByIds" resultType="User">
SELECT * FROM user
<where>
<!-- 判断 List 是否非空,再用 foreach 遍历 -->
<if test="ids != null and ids.size() > 0">
id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
</select>
(2)Map 集合的键值访问

若参数是 Map,OGNL 支持通过 map.keymap['key'] 访问值:

1
2
3
4
5
6
7
8
9
10
11
12
13
<select id="findUserByMap" resultType="User">
SELECT * FROM user
<where>
<!-- Map 的 key 为 "username",访问对应值 -->
<if test="params != null and params.username != ''">
AND username LIKE #{params.username}
</if>
<!-- 等价写法:params['age'] -->
<if test="params != null and params.age != null">
AND age > #{params.age}
</if>
</where>
</select>
(3)数组的长度判断

数组通过 array.length 判断长度(注意是 length 属性,非方法):

1
2
3
4
5
6
7
8
9
10
11
<select id="findUserByArray" resultType="User">
SELECT * FROM user
<where>
<if test="ids != null and ids.length > 0">
id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
</select>

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
2
3
4
5
6
7
8
9
10
11
12
13
<select id="findUserByCondition" resultType="User">
SELECT * FROM user
<where>
<!-- 逻辑与:年龄大于18且用户名不为空 -->
<if test="age != null and age > 18">
AND age > #{age}
</if>
<!-- 逻辑或:用户名包含"张"或邮箱包含"@qq.com" -->
<if test="username != null and username.contains('张') or email != null and email.endsWith('@qq.com')">
AND (username LIKE CONCAT('%', #{username}, '%') OR email LIKE CONCAT('%', #{email}, '%'))
</if>
</where>
</select>

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(键为 arg0arg1param1param2)。
示例:使用 _parameter 简化多参数判断
1
2
3
4
5
6
7
8
9
10
11
12
<!-- Mapper 接口:多个参数(无 @Param 注解) -->
List<User> findUser(Integer id, String username);

<!-- 映射文件:通过 _parameter 访问 Map 中的参数 -->
<select id="findUser" resultType="User">
SELECT * FROM user
<where>
<!-- _parameter 是 Map,键为 param1(第一个参数)、param2(第二个参数) -->
<if test="_parameter.param1 != null">AND id = #{param1}</if>
<if test="_parameter.param2 != null and _parameter.param2 != ''">AND username = #{param2}</if>
</where>
</select>
(2)_databaseId:代表当前数据库方言

若在 MyBatis 全局配置中配置了 databaseIdProvider_databaseId 可用于多数据库适配(如 MySQL 与 Oracle 语法差异):

1
2
3
4
5
6
7
8
9
10
11
12
13
<select id="findUser" resultType="User">
SELECT * FROM user
<where>
<if test="id != null">AND id = #{id}</if>
</where>
<!-- 多数据库分页语法适配 -->
<if test="_databaseId == 'mysql'">
LIMIT 10
</if>
<if test="_databaseId == 'oracle'">
AND ROWNUM <= 10
</if>
</select>

OGNL 实战场景与避坑指南

1. 动态 SQL 中的常见用法

(1)if 标签条件判断(非空 + 非空串)
1
2
3
4
5
6
7
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<!-- 等价写法(使用方法调用) -->
<if test="username != null and username.length() > 0">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
(2)bind 标签定义变量(避免数据库语法差异)
1
2
3
4
5
6
7
8
9
10
<select id="findUserByLikeName" resultType="User">
<!-- 定义变量 _likeName,值为 "%username%" -->
<bind name="_likeName" value="'%' + username + '%'"/>
SELECT * FROM user
<where>
<if test="username != null and username != ''">
AND username LIKE #{_likeName} <!-- 直接使用变量 -->
</if>
</where>
</select>
(3)foreach 标签遍历集合(结合 OGNL 集合判断)
1
2
3
4
5
6
7
8
9
10
11
12
<select id="findUserByIds" resultType="User">
SELECT * FROM user
<where>
<!-- 先判断集合非空,再遍历 -->
<if test="ids != null and ids.size() > 0">
id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
</select>

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"(若 listnull,会抛出 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 内置 pageContextrequestsession

关键提醒:MyBatis 中 #{}是预编译占位符(防 SQL 注入),${} 是字符串拼接(使用 OGNL 解析),需区分两者用途:

  • 传递参数用 #{}(如 #{username});
  • 动态表名、排序字段用 ${}(如 ORDER BY ${sortField},需注意 SQL 注入风险)。

总结

OGNL 是 MyBatis 动态 SQL 的 “灵魂”,掌握其基础语法(属性导航、集合操作、逻辑运算)和核心场景(条件判断、参数解析),能大幅提升动态 SQL 的编写效率和灵活性。实际开发中需注意:

  1. 非空判断需同时检查 null 和空值(如字符串 name != null and name != '');
  2. 多参数优先使用 @Param 注解,避免 _parameter 的 Map 键(param1/arg0);
  3. 避免复杂方法调用和静态方法,保持表达式简洁可读;
  4. 区分 #{}${} 的用途,防止 SQL 注入

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10