这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战
Mybatis支持OGNL的语法
OGNL 是 Object-Graph Navigation Language 的缩写,对象-图行导航语言,语法为:#{ }。
是不是有点懵,不知道这是个啥?
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调用类的静态字段值
还有一些表达式用来赋值或者增强属性。经常用来做模糊搜索的 bind标签:
<bind name="nameLike" value="'%'+ name + '%'"/>
这里的value也属于OGNL表达式 e1+e2,字符串是拼接,数字的话就是加法运算,我们可以引申出肯定还有:
e1*e2乘法e1/e2除法e1-e2减法e1%e2取模
用于解析静态方法
org.apache.ibatis.scripting.xmltags.TextSqlNode.BindingTokenParser#handleToken
org.apache.ibatis.scripting.xmltags.OgnlCache#getValue
org.apache.ibatis.scripting.xmltags.OgnlCache#parseExpression解析表达式
org.apache.ibatis.ognl.Ognl#parseExpression
org.apache.ibatis.ognl.OgnlParser#staticReference
org.apache.ibatis.ognl.OgnlParser#staticMethodCall
org.apache.ibatis.ognl.OgnlRuntime#callStaticMethod
在SQL映射语句中可以支持引入以下几种方式:
<select id="getUserById" resultMap="BaseResultMap">
select * from user
<if test="id != null">
<where>
name = #{name}
and id =${id}
and id = ${user.id}
and id = ${@@abs(-12345678)}
and id = ${@@parseInt("654")}
and id='${@cn.followtry.mybatis.bean.User@name()}'
and id='${new cn.followtry.mybatis.bean.User()}'
and id=${@cn.followtry.mybatis.bean.User@haha}
and id='${@cn.followtry.mybatis.bean.User@arr[1]}'
and id='${@cn.followtry.mybatis.bean.User@list[1]}'
and id='${@cn.followtry.mybatis.bean.User@map.get("123")}'
and id='${@cn.followtry.mybatis.bean.CodeTypeEnum@THREE.ordinal()}'
</where>
</if>
limit 100
</select>
-
变量:
id =${id} -
属性:
id = ${user.id} -
静态方法(public):
id='${@cn.followtry.mybatis.bean.User@name()}' -
静态属性(public):
id=${@cn.followtry.mybatis.bean.User@aaa} -
数组索引:
id='${@cn.followtry.mybatis.bean.User@arr[1]}' -
集合:
'${@cn.followtry.mybatis.bean.User@list[1]}' -
Map:
id='${@cn.followtry.mybatis.bean.User@map.get("123")}'id='${@cn.followtry.mybatis.bean.User@map}'- Enum:
id=${@cn.followtry.mybatis.bean.CodeTypeEnum@THREE.ordinal()}
-
构造方法:
id='${new cn.followtry.mybatis.bean.User()}' -
java.lang.Math方法:
id = ${@@abs(-12345678)}可以省略class的编写,方法的默认class是java.lang.Math
${}语法中通过两个@字符,前者定位到Java类,后者定位到类中的方法或属性,这里只列出的其中一部分,对于Mybatis支持的${}语法,可以参见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);
}
}
<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>
类的内置方法
其实Mybatis的Mapper.xml中还可以使用对象的内置方法,比如我们需要判断一个java.util.Collection集合是否为空,可以这么写:
<if test="collection!=null and collection.size()> 0">
and some_col = #{some_val}
</if>
这里就使用了对象的内置方法Collection.size()。
我们还可以调用自定义对象CollectionUtils的静态方法来判断集合是否为空:
package cn.felord.util;
public final class CollectionUtils {
public static boolean isNotEmpty( Collection<?> collection) {
return (collection != null && !collection.isEmpty());
}
}
那么上面的<if>判断改为:
<if test="@cn.felord.util.CollectionUtils@isNotEmpty(collection)">
and some_col = #{some_val}
</if>
不要忘了这里要带上类的全限定名。
取值操作
取值操作的话,如果是对象直接e.property,如果是集合或者Map可以e[index|key],通过索引或者键名来取值。分别举个例子:
# 对象取属性
user.username
# 集合取元素
array[1]
# map 取值
map['username']
其实静态属性也能取值调用,跟上面的静态方法类似:
@cn.felord.Cache@user
对应Java代码:
package cn.felord;
public final class Cache {
public static User user = new User ("felord.cn") ;
}
赋值操作
上面的取值除了可以做判断还可以用来SQL参数赋值:
<where>
<!-- 常用的赋值方式 -->
username = #{username}
<!-- $ 也可以赋值 -->
and user_id =${userId}
<!-- 对象取属性 -->
and id = ${user.id}
<!-- Math.abs 双@简写 -->
and age = ${@@abs(-12345678)}
<!-- 调用枚举 -->
and gender =${@cn.felord.GenderEnum@MALE.ordinal()}
and id=${@cn.felord.Cache@user.userId}
</where>
通过${}符号可以用OGNL表达式给SQL参数赋值,不过感觉平常比较少用。
- 对
<bind>参数的调用可以通过#{}或${}方式获取,#{}可以防止注入:<bind>的value值会使用OGNL计算
<bind name="username_bind" value='@java.util.UUID@randomUUID().toString().replace("-", "")' />
- 使用OGNL实现单表的分表功能
分表这个功能是通用Mapper中的新功能,允许在运行的时候指定一个表名,通过指定的表名对表进行操作。这个功能实现就是使用了OGNL。
首先并不是所有的表都需要该功能,因此定义了一个接口,当参数(接口方法只有实体类一个参数)对象继承该接口的时候,就允许使用动态表名。
public interface IDynamicTableName {
/**
* 获取动态表名 - 只要有返回值,不是null和'',就会用返回值作为表名
* @return
*/
String getDynamicTableName();
}
然后在XML中写表名的时候使用:
<if test="@tk.mybatis.mapper.util.OGNL@isDynamicParameter(_parameter)
and dynamicTableName != null
and dynamicTableName != ''">
${dynamicTableName}
</if>
<if test="@tk.mybatis.mapper.util.OGNL@isNotDynamicParameter(_parameter)
or dynamicTableName == null
or dynamicTableName == ''">
defaultTableName
</if>
由于我需要判断_parameter是否继承了IDynamicTableName接口,简单的写法已经无法实现,所以使用了静态方法,这两个方法如下:
/**
* 判断参数是否支持动态表名
*
* @param parameter
* @return true支持,false不支持
*/
public static boolean isDynamicParameter(Object parameter) {
if (parameter != null && parameter instanceof IDynamicTableName) {
return true;
}
return false;
}
/**
* 判断参数是否b支持动态表名
*
* @param parameter
* @return true不支持,false支持
*/
public static boolean isNotDynamicParameter(Object parameter) {
return !isDynamicParameter(parameter);
}
根据<if>判断的结果来选择使用那个表名。另外注意XML判断中有一个dynamicTableName,这个参数是根据getDynamicTableName方法得到的,MyBatis使用属性对应的getter方法来获取值,不是根据field来获取值。