简介
OGNL
是 Object-Graph Navigation Language
的缩写,全称为对象图导航语言
,是一种功能强大的表达式语言
,它通过简单一致的语法,可以任意存取对象的属性或者调用对象的方法
,能够遍历整个对象的结构图
,实现对象属性类型的转换
等功能。
须知
1 OGNL表达式的计算是围绕OGNL上下文进行的
。
OGNL 上下文 实际上就是一个 Map对象 ,由 ognl.OgnlContext 类表示。它里面可以 存放很多个JavaBean对象 。它有一个上下文根对象。
上下文中的 根对象 可以 直接使用名来访问 或 直接使用它的属性名 访问它的属性值。否则要加前缀 #key
2 Struts2的标签库都是使用OGNL表达式来访问ActionContext中的对象数据的。
<s:propertyvalue="xxx"/>
3 Struts2
将 ActionContext
设置为OGNL上下文
,并将值栈作为OGNL的根对象放置到ActionContext中。
4 值栈(ValueStack)
可以在值栈中放入、删除、查询对象
。访问值栈中的对象不用“#”
Struts2
总是把当前Action实例放置在栈顶
。所以在 OGNL 中引用Action中的属性也可以省略“#”
5 调用ActionContext
的 put(key,value)
放入的数据,需要使用#
访问。
3个符号:#、%、$
#、% 和 $
符号在OGNL表达式中经常出现
# 号
#
符号的用途一般有三种。
访问非根对象属性,例如 #session.msg 表达式,由于Struts 2中值栈 被视为根对象,所以访问其他非根对象时,需要加#前缀。
实际上,# 相当于 ActionContext. getContext()
#session.msg 表达式相当于 ActionContext.getContext().getSession(). getAttribute("msg")
用于过滤和投影(projecting)集合,如
persons.{?#this.age>25},persons.{?#this.name=='pla1'}.{age}[0]。
用来构造Map,例如示例中的 #{'foo1':'bar1', 'foo2':'bar2'}
#
可以取出堆栈上下文中的存放的对象
%
%符号的用途是在标志的属性为字符串类型
时,计算OGNL表达式的值
,这个类似js中的eval,很暴力。
$
$ 符号主要有两个方面的用途。
在国际化资源文件
中,引用OGNL表达式
,例如国际化资源文件中的代码:reg.agerange=国际化资源信息:年龄必须在 ${min} 同 ${max} 之间
总结OGNL的使用方法
OGNL直接支持java中的方法
(只支持JDK中的方法,不支持其他JAR包中的方法
)
访问属性
名字属性获取:<s:property value="user.username"/><br>
地址属性获取:<s:property value="user.address.addr"/><br>
访问方法
调用值栈中对象的普通方法:<s:property value="user.get()"/><br>
访问静态属性和方法
调用Action中的静态方法:<s:property value="@struts.action.LoginAction@get()"/>
调用JDK中的类的静态方法:<s:property value="@java.lang.Math@floor(44.56)"/><br>
调用JDK中的类的静态方法(同上):<s:property value="@floor(44.56)"/><br>
调用JDK中的类的静态方法:<s:property value="@java.util.Calendar@getInstance()"/><br>
调用普通类中的静态属性:<s:property value="@struts.vo.Address@TIPS"/><br>
访问构造方法
调用普通类的构造方法: <s:property value="new struts.vo.Student('李晓红' , '美女' , 3 , 25).username"/>
访问数组
获取List: <s:property value="testList"/><br>
获取 List 中的某一个元素(可以使用类似于数组中的下标获取List中的内容):
<s:property value="testList[0]"/><br>
获取 Set:<s:property value="testSet"/><br>
获取 Set 中的某一个元素(Set由于没有顺序,所以不能使用下标获取数据):
<s:property value="testSet[0]"/><br>
获取 Map: <s:property value="testMap"/><br>
获取 Map 中所有的键: <s:property value="testMap.keys"/><br>
获取 Map 中所有的值: <s:property value="testMap.values"/><br>
获取 Map 中的某一个元素(可以使用类似于数组中的下标获取List中的内容):
<s:property value="testMap['m1']"/><br>
获取 List 的大小:<s:property value="testSet.size"/><br>
访问集合 – 投影、选择
? [0] 或者 ^ 第一个
$ 最后一个
利用选择获取List中成绩及格的对象: <s:property value="stus.{?#this.grade>=60}"/><br>
利用选择获取List中成绩及格的对象的username:
<s:property value="stus.{?#this.grade>=60}.{username}"/><br>
利用选择获取List中成绩及格的第一个对象的username:
<s:property value="stus.{?#this.grade>=60}.{username}[0]"/><br>
<s:property value="stus.{^#this.grade>=60}.{username}"/><br>
利用选择获取List中成绩及格的最后一个对象的username:
<s:property value="stus.{$#this.grade>=60}.{username}"/><br>
利用选择获取List中成绩及格的第一个对象然后求大小:
<s:property value="stus.{^#this.grade>=600}.{username}.size"/><br>
集合的伪属性
OGNL
能够引用集合的一些特殊的属性
, 这些属性并不是 JavaBeans 模式, 例如size(),length()
等等. 当表达式引用这些属性时,
OGNL会调用相应的方法,
这就是伪属性
Ognl 类方法源码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.hotent.core.util.BeanUtils;
public class Ognl {
public Ognl() {
}
public static boolean isEmpty(Object o) throws IllegalArgumentException {
return BeanUtils.isEmpty(o);
}
public static boolean isNotEmpty(Object o) {
return !isEmpty(o);
}
public static boolean isNotEmpty(Long o) {
return !isEmpty(o);
}
public static boolean isNumber(Object o) {
return BeanUtils.isNumber(o);
}
}
mybaits 中
# 和 $
${}
为 在预编的时候会直接被变量替换
,但是存在被注入的问题
,原样输出,你传什么,sql里就填入什么
,比如有引号它也会原样填到sql里
#{}
在预编的时候会被解析为?
,占位符会使用 PreparedStatement
,变量处用 ? 代替
,当被变量替换的时候会加上 ‘ ’ 单引号,表明不允许加单引号(但是反引号 `` 是可以的)
在能使用 #{} 尽量使用它吧,可以防止sql注入
动态 SQL 中 OGNL
MyBatis中用于实现动态SQL的元素
主要有:
if
choose(when,otherwise)
trim
where
set
foreach
if
if
就是简单的条件判断,利用if语句我们可以实现某些简单的条件选择
<select id="dynamicIfTest" parameterType="Blog" resultType="Blog">
select * from t_blog where 11 = 1
<if test="title != null">
and title = #{title}
</if>
<if test="content != null">
and content = #{content}
</if>
<if test="owner != null">
and owner = #{owner}
</if>
</select>
在使用<if>时
,注意在对”int”型对象判断时
,不仅要判断是否为“null”,还得判断是否为“0”
public class Content implements Serializable {
private int id;
private int commandId;
private String content;
}
Content content = new Content();
content.setCommandId(Integer.parseInt(id));
//此时的Content对象是这样的,id被默认赋值为0了
Content content = new Content(0, commandId, null);
//此时若sql语句如下
<if test="id != null">
AND id = #{id}
</if>
<if test="commandId != null">
AND command_id = #{commandId}
</if>
//这两条sql语句都将被执行(而我们的本意是执行后面一条)
//解决办法,更改sql语句如下
<if test="id != null and id !=0">
AND id = #{id}
</if>
<if test="commandId != null and commandId !=0">
AND command_id = #{commandId}
</if>
OGNL直接支持java中的方法(只支持JDK中的方法,不支持其他JAR包中的方法)。
例: <if test="Command != null and !"".equals(Command.trim())">and command=#{Command}</if>
给参数赋值:#{command}是由mybaits处理的,遇到#{},mybaits会自动替换为?
choose
choose 元素
的作用就相当于JAVA中的switch语句
,基本上跟JSTL中的choose的作用和用法是一样的,通常都是与when和otherwise搭配的
<select id="dynamicChooseTest" parameterType="Blog" resultType="Blog">
select * from t_blog where 11 = 1
<choose>
<when test="title != null">
and title = #{title}
</when>
<when test="content != null">
and content = #{content}
</when>
<otherwise>
and owner = "owner1"
</otherwise>
</choose>
</select>
当 when 中有条件满足的时候,就会跳出 choose
即所有的 when 和 otherwise 条件中,只有一个会输出,当所有的我很条件都不满足的时候就输出otherwise中的内容。
上面例子 当 title!=null 的时候就输出 and titlte = #{title},不再往下判断条件
当 title 为空且 content!=null 的时候就输出 and content = #{content}
当所有条件都不满足的时候就输出 otherwise 中的内容
where
where语句的作用主要是简化SQL语句中where中的条件判断的
<select id="dynamicWhereTest" parameterType="Blog" resultType="Blog">
select * from t_blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="content != null">
and content = #{content}
</if>
<if test="owner != null">
and owner = #{owner}
</if>
</where>
</select>
where元素的作用是会在写入where元素的地方输出一个where
另外一个好处是你不需要考虑 where 元素里面的条件输出是什么样子的,MyBatis会智能的帮你处理,如果所有的条件都不满足那么 MyBatis 就会查出所有的记录
如果输出后是 and 开头的,MyBatis会把第一个and忽略,当然如果是or开头的,MyBatis也会把它忽略
此外,在where元素中你不需要考虑空格的问题,MyBatis会智能的帮你加上。
上述例子中,如果 title=null, 而content != null,那么输出的整个语句会是
select * from t_blog where content = #{content},而不是select * from t_blog where and content = #{content},因为MyBatis会智能的把首个and 或 or 给忽略。
trim
trim
元素的主要功能是可以在自己包含的内容前加上某些前缀
,也可以在其后加上某些后缀
,与之对应的属性是 prefix 和 suffix
;可以把包含内容的首部某些内容覆盖
,即忽略,也可以把尾部的某些内容覆盖,对应的属性是 prefixOverrides 和 suffixOverrides
;我们也可以利用trim来代替where元素的功能
<select id="dynamicTrimTest" parameterType="Blog" resultType="Blog">
select * from t_blog
<trim prefix="where" prefixOverrides="and | or">
<if test="title != null">
title = #{title}
</if>
<if test="content != null">
and content = #{content}
</if>
<if test="owner != null">
or owner = #{owner}
</if>
</trim>
</select>
set
set 元素主要是用在更新操作的时候
,它的主要功能和where元素其实是差不多的
,主要是在包含的语句前输出一个set
,然后如果包含的语句是以逗号结束的话将会把该逗号忽略
,如果set包含的内容为空的话则会出错
。有了set元素我们就可以动态的更新那些修改了的字段
<update id="dynamicSetTest" parameterType="Blog">
update t_blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="content != null">
content = #{content},
</if>
<if test="owner != null">
owner = #{owner}
</if>
</set>
where id = #{id}
</update>
上述代码中,如果set中一个条件都不满足,即set中包含的内容为空的时候就会报错
foreach
foreach
的主要用在构建 in 条件
中,它可以在SQL语句中进行迭代一个集合
。foreach 元素的属性主要有item,index,collection,open,separator,close
。
item
表示集合中
每一个元素进行迭代时的别名
index
指定一个名字
,用于表示在迭代过程中
,每次迭代到的位置
open
表示该语句以什么开始
separator
表示在每次进行迭代之间以什么符号作为分隔符
close
表示以什么结束
collection
是必须指定的
,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:
collection 只能取 list/array/_parameter 三个值
如果传入的是 单参数 且参数类型是一个 List 的时候,collection 属性值为 list
如果传入的是 单参数 且参数类型是一个 array数组 的时候,collection 的属性值为 array
如果传入的参数是 多个的时候,我们就需要把它们 封装成一个Map了
当然单参数也可以 封装成map,实际上如果你在 传入参数的时候 ,在 MyBatis 里面也是会把它封装成一个 Map 的,map 的 key 就是参数名
所以这个时候 collection属性值 就是传入的 List 或 array对象 在自己封装的 map 里面的 key
<select id="dynamicForeachTest" resultType="Blog">
select * from t_blog where id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
对应mapper
public List<Blog> dynamicForeachTest(List<Integer> ids);
<select id="dynamicForeach2Test" resultType="Blog">
select * from t_blog where id in
<foreach collection="array" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
对应mapper
public List<Blog> dynamicForeach2Test(int[] ids);
<select id="dynamicForeach3Test" resultType="Blog">
select * from t_blog where title like "%"#{title}"%" and id in
<foreach collection="ids" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
上述 collection 的值为 ids,是传入的参数 Map 的 key,对应的 Mapper 代码:
public List<Blog> dynamicForeach3Test(Map<String, Object> params);
注意
foreach
标签遍历的集合元素
类型是
Map.Entry
类型时
index
属性指定的变量
代表对应的Map.Entry的key
item
属性指定的变量
代表对应的Map.Entry的value
此时如果对应的集合是 Map.entrySet
,则对应的 collection 属性用collection
。
foreach 在进行遍历的时候
如果 传入的参数是 List 类型 ,则其 collection 属性的值可以是 list 或collection
如果 传入的参数是 Set 类型,则 collection 属性的值 只能用 collection
<select id="dynamicForeachTest" resultType="Blog">
select * from t_blog where id in
<!-- 遍历的对象是Map.Entry时,index代表对应的key,item代表对应的value -->
<foreach collection="collection" index="key" item="value" open="(" separator="," close=")">
#{key}, #{value}
</foreach>
</select>
mapper
public List<Blog> dynamicForeachTest(Set<Map.Entry<Integer, Integer>> ids);
bind
功能是在当前OGNL上下文中创建一个变量并绑定一个值
。有了它以后我们以前的模糊查询就可以改成这个样子
<select id="fuzzyQuery" resultType="Blog" parameterType="java.lang.String">
<!-- bind 标签用于创建新的变量 -->
<bind name="titleLike" value="'%'+_parameter+'%'"/>
select * from t_blog where title like #{titleLike}
</select>
<bind> 的value值会使用OGNL计算
bind 的参数调用 只能 用 $ 获取
对<bind参数的调用可以通过 #{} 或 ${} 方式获取,#{} 可以防止注入
通用Mapper中支持一种UUID的主键,在通用Mapper中的实现就是使用了<bind>标签,这个标签调用了一个静态方法,大概方法如下:
<bind name="username_bind"
value='@java.util.UUID@randomUUID().toString().replace("-", "")' />
下面 test 的值会使用 OGNL 计算结果
<select id="xxx" ...>
select id,name,... from country
<where>
<if test="name != null and name != ''">
name like concat('%', #{name}, '%')
</if>
</where>
</select>
${param} 参数中 OGNL
<select id="xxx" ...>
select id,name,... from country
<where>
<if test="name != null and name != ''">
name like '${'%' + name + '%'}'
</if>
</where>
</select>
这里注意写的是 ${'%' + name + '%'}
,而不是 %${name}%
,这两种方式的结果一样
,但是处理过程不一样
。
在 MyBatis 中处理 ${}
的时候,只是使用OGNL计算这个结果值
,然后替换SQL中对应的 ${xxx}
,OGNL 处理的只是 ${这里的表达式}
这里表达式可以是 OGNL支持的所有表达式,可以写的很复杂,可以调用静态方法返回值,也可以调用静态的属性值
<select id="getAll" parameterType="java.util.Map" resultMap="TaskEntity">
SELECT task.*,run.subject subject,run.processName processName
FROM ACT_RU_TASK task left join BPM_PRO_RUN run
on task.PROC_INST_ID_=run.actInstId
where 1=1
<if test="@Ognl@isNotEmpty(name)"> AND task.name_ LIKE #{name} </if>
<if test="@Ognl@isNotEmpty(subject)"> AND run.subject LIKE #{subject} </if>
<if test="@Ognl@isNotEmpty(processName)"> AND run.processName LIKE #{processName} </if>
<if test="@Ognl@isEmpty(orderField)">
order by task.CREATE_TIME_ desc
</if>
<if test="@Ognl@isNotEmpty(orderField)">
order by ${orderField} ${orderSeq}
</if>
</select>
<select id="getRecentQuestionTitle" parameterType="java.lang.String" resultType="java.lang.String">
select title from song_question where questionState = #{value}
<if test="@Ognl@isSolve(value[0],0)">
order by questionTime desc
</if>
<if test="@Ognl@isSolve(value[0],1)">
order by answerTime desc
</if>
limit 0,1
</select>
MyBatis常用OGNL表达式
e1 or e2
e1 and e2
e1 == e2,e1 eq e2
e1 != e2,e1 neq e2
e1 lt e2:小于
e1 lte e2:小于等于,其他gt(大于),gte(大于等于)
e1 in e2
e1 not in e2
e1 + e2,e1 * e2,e1/e2,e1 - e2,e1%e2
!e,not e:非,求反
e.method(args)调用对象方法
e.property对象属性值
e1[ e2 ]按索引取值,List,数组和Map
@class@method(args)调用类的静态方法
@class@field调用类的静态字段值
工作中
使用枚举Enum判断
<if test="dtEnum == @com.xxx.xxx.TestTypeEnum@HOUR">
DATE_FORMAT(TM,'%Y-%m-%d %H') as keyStr,
</if>
TestTypeEnum定义如下:
HOUR("hour"),
DAY("day"),
MONTH("month"),
YEAR("year");
where and 条件,多个条件 or
<if test="param.xxxTypes != null and param.xxxTypes.size() > 0 ">
and (
<foreach collection="param.xxxTypes" index="index" item="item" separator="or">
<if test= "item.equals(@com.xxx.common.xxxTypeEnum@枚举值)">
xxx is null
</if>
<if test= "!item.equals(@com.xxx.common.xxxTypeEnum@枚举值)">
json_length(xxx, CONCAT('$.',#{item,typeHandler=com.xxx.core.dao.typehandler.xxxTypeEnumHandler})) != 0
</if>
</foreach>
)
foreach 嵌套使用 if 标签对象取值问题
foreach
中获取到的 item 是一个 json 对象
<foreach collection="advanceSearchList" item="item" index="index" >
<if test="item.searchType == 10 ">
and abc like CONCAT('%', #{item.searchText}, '%')
</if>
</foreach>
<select id="getFieldsValue" parameterType="java.util.Map" resultType="java.util.HashMap">
SELECT
<foreach collection="colList" item="col" index="index" separator=",">
<if test="optionList[index] != 'null'">
${col}.dic_value as ${col}
</if>
<if test="optionList[index] == 'null'">
${col}
</if>
</foreach>
FROM
${tableName} t
<foreach collection="optionList" item="option" index="index">
<if test="option != 'null'">
left join t_admin_dic_values ${colList[index]} ON t.${colList[index]}=${colList[index]}.id
</if>
</foreach>
WHERE
t.id IN
<foreach collection="recordList" item="item" separator="," open="(" close=")">
#{item}
</foreach>
</select>