在Mybatis的动态SQL和${}形式的参数中都用到了OGNL表达式。
Mybatis常用的OGNL表达式
e1 or e2:或
<if test="userEmail != null or userEmail == '1'">
</if>
e1 and e2:且
<if test="userEmail != null and userEmail != ''">
</if>
e1 == e2 或e1 eq e2:相等
<if test="userEmail == null and userEmail == ''">
</if>
e1 != e2 或 e1 neq e2:不等
<if test="userEmail != null and userEmail != ''">
</if>
e1 lt e2:小于
<if test="age lt 10">
</if>
e1 lte e2:小于等于
e1 gt e2:大于
e1 gte e2:大于等于
e1 + e2(加),e1 - e2(减),e1 * e2(乘),e1/e2(除),e1%e2(余)
!e或not e:非,取反
e.method(args):调用对象方法
<if test="list != null and list.size() > 0 ">
#{userEmail,jdbcType=VARCHAR},
</if>
e.property:对象属性值
<!-- 多接口参数的查询方法(@Param + javaBean方式) -->
<select id="selectByUserIdAndEnabledUseBean" resultMap="BaseResultMap">
select r.id, r.role_name, r.enabled, r.create_by, r.create_time,
u.user_name as "user.userName", u.user_email as "user.userEmail"
from sys_user u
inner join sys_user_role ur on u.id = ur.user_id
inner join sys_role r on ur.role_id = r.id
where u.id = #{user.id} and r.enabled = #{role.enabled}
</select>
e1[e2]:按索引取值(List、数组和map)
@class@method(args):调用类的静态方法
<bind name="name" value="@ex.mybatis.rbac.mapper.UserMaperTest@setName()"/>
@class@field:调用类的静态字段值
<bind name="name" value="@ex.mybatis.rbac.mapper.UserMaperTest@NAME"/>
MyBatis中什么地方可以使用OGNL
下面这两处地方在MyBatis中处理的时候都是使用OGNL处理的。
动态SQL表达式中
示例1
<select id="xxx" ...>
select id,name,... from country
<where>
<if test="name != null and name != ''">
name like concat('%', #{name}, '%')
</if>
</where>
</select>
上面代码中test的值会使用OGNL计算结果。
示例2
<select id="xxx" ...>
select id,name,... from country
<bind name="nameLike" value="'%' + name + '%'"/>
<where>
<if test="name != null and name != ''">
name like '${nameLike}'
</if>
</where>
</select>
这里的value值会使用OGNL计算。
注:对<bind参数的调用只能通过${}方式获取,如${nameLike}。
在通用Mapper中支持一种UUID的主键,在通用Mapper中的实现就是使用了标签,这个标签调用了一个静态方法,大概方法如下:
<bind name="username_bind" value='@java.util.UUID@randomUUID().toString().replace("-", "")' />
这种方式虽然能自动调用静态方法,但是没法回写对应的属性值,因此使用时需要注意。
${param}参数中
上面like的例子中使用下面这种方式最简单
<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支持的所有表达式,可以写的很复杂,可以调用静态方法返回值,也可以调用静态的属性值。
示例1:查询多个父级的所有的子级组织
<select id="selectAllSubDeptByDeptIds" resultType="long">
SELECT
d.id
FROM
dept d
INNER JOIN(
SELECT * FROM (
(SELECT @ids :=
<!--将集合deptList使用 逗号 拼接起来, 此处使用foreach会查不出结果, 即使是使用了replace去掉了空格的情况下-->
'${@org.springframework.util.StringUtils@collectionToCommaDelimitedString(deptIdList)}'
) a
, (SELECT @ids AS _ids ,
(SELECT @ids := GROUP_CONCAT(id) FROM dept WHERE FIND_IN_SET(parent_id, @ids)) AS cids
FROM
dept d
WHERE
@ids IS NOT NULL AND d.is_del = 0
) b
)
) T ON FIND_IN_SET(d.id, T.cids)
WHERE
d.is_del = 0
</select>
示例2:查询未被邀请的组织
<select id="selectNoInvitedDepts" resultType="com.anbao.train.data.dto.dept.DeptTreeDto">
SELECT
d.id,
d.name,
d.parent_id,
d.hospital_area_id
FROM
dept d
WHERE
d.hospital_area_id = #{hospitalAreaId}
AND d.merchant_code = #{merchantCode}
<if test="deptIds != null and deptIds.size() != 0">
AND NOT FIND_IN_SET(d.id,
(SELECT
group_concat( _ids )
FROM
(
( SELECT @ids :=
'${@org.springframework.util.StringUtils@collectionToCommaDelimitedString(deptIds)}'
) a,
(
SELECT
@ids AS _ids,
(
SELECT
@ids := GROUP_CONCAT( id )
FROM
dept
WHERE
FIND_IN_SET( parent_id, @ids )) AS cids
FROM
dept
WHERE
@ids IS NOT NULL
) b
)
)
)
</if>
</select>
示例3:删除多个父级所有的子级组织
<delete id="deleteCourseInvitationSubDepts">
DELETE
FROM
course_invitation ci
WHERE
ci.course_id = #{courseId}
AND FIND_IN_SET (ci.dept_id,
(
SELECT
group_concat( cids )
FROM (
( SELECT @ids :=
'${@org.springframework.util.StringUtils@collectionToCommaDelimitedString(deptIdList)}'
) a,
(SELECT
@ids AS _ids,
(SELECT
@ids := GROUP_CONCAT( id )
FROM
dept
WHERE
FIND_IN_SET( parent_id, @ids )
) AS cids
FROM
dept
WHERE
@ids IS NOT NULL
) b
)
)
)
</delete>
示例4:使用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来获取值。
单独使用ognl表达式
其中,DefaultMemberAccess是复制下来,放到项目中的,不然会报错:RuntimeException: MemberAccess implementation must be provided!
OgnlContext context = new OgnlContext(null,null,new DefaultMemberAccess(true));
User user = new User();
user.setUserAccount("zzhua");
System.out.println(Ognl.getValue("userAccount", context,user)); // true
System.out.println(Ognl.getValue("userAccount == 'zzhua'",context, user)); / true
ystem.out.println(Ognl.getValue("userAccount == 'zj'",context, user)); // false
System.out.println(Ognl.getValue("userAccount != null && userAccount != ''", context,user)); // true
System.out.println(Ognl.getValue("userAccount.substring(0,2)", context,user)); // zz
System.out.println(Ognl.getValue("1==1", context,new Object())); //true
System.out.println(Ognl.getValue("1=='1'", context, new Object())); // false
//--------------------------------------------------------------------------
// Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// Neither the name of the Drew Davidson nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
//--------------------------------------------------------------------------
import org.apache.ibatis.ognl.MemberAccess;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.util.Map;
/**
* This class provides methods for setting up and restoring
* access in a Field. Java 2 provides access utilities for setting
* and getting fields that are non-public. This object provides
* coarse-grained access controls to allow access to private, protected
* and package protected members. This will apply to all classes
* and members.
*
* @author Luke Blanshard (blanshlu@netscape.net)
* @author Drew Davidson (drew@ognl.org)
* @version 15 October 1999
*/
public class DefaultMemberAccess implements MemberAccess
{
public boolean allowPrivateAccess = false;
public boolean allowProtectedAccess = false;
public boolean allowPackageProtectedAccess = false;
/*===================================================================
Constructors
===================================================================*/
public DefaultMemberAccess(boolean allowAllAccess)
{
this(allowAllAccess, allowAllAccess, allowAllAccess);
}
public DefaultMemberAccess(boolean allowPrivateAccess, boolean allowProtectedAccess, boolean allowPackageProtectedAccess)
{
super();
this.allowPrivateAccess = allowPrivateAccess;
this.allowProtectedAccess = allowProtectedAccess;
this.allowPackageProtectedAccess = allowPackageProtectedAccess;
}
/*===================================================================
Public methods
===================================================================*/
public boolean getAllowPrivateAccess()
{
return allowPrivateAccess;
}
public void setAllowPrivateAccess(boolean value)
{
allowPrivateAccess = value;
}
public boolean getAllowProtectedAccess()
{
return allowProtectedAccess;
}
public void setAllowProtectedAccess(boolean value)
{
allowProtectedAccess = value;
}
public boolean getAllowPackageProtectedAccess()
{
return allowPackageProtectedAccess;
}
public void setAllowPackageProtectedAccess(boolean value)
{
allowPackageProtectedAccess = value;
}
/*===================================================================
MemberAccess interface
===================================================================*/
public Object setup(Map context, Object target, Member member, String propertyName)
{
Object result = null;
if (isAccessible(context, target, member, propertyName)) {
AccessibleObject accessible = (AccessibleObject)member;
if (!accessible.isAccessible()) {
result = Boolean.FALSE;
accessible.setAccessible(true);
}
}
return result;
}
public void restore(Map context, Object target, Member member, String propertyName, Object state)
{
if (state != null) {
((AccessibleObject)member).setAccessible(((Boolean)state).booleanValue());
}
}
/**
Returns true if the given member is accessible or can be made accessible
by this object.
*/
public boolean isAccessible(Map context, Object target, Member member, String propertyName)
{
int modifiers = member.getModifiers();
boolean result = Modifier.isPublic(modifiers);
if (!result) {
if (Modifier.isPrivate(modifiers)) {
result = getAllowPrivateAccess();
} else {
if (Modifier.isProtected(modifiers)) {
result = getAllowProtectedAccess();
} else {
result = getAllowPackageProtectedAccess();
}
}
}
return result;
}
}