Mybatis是一个开源的轻量级半自动化ORM框架,使得面向对象应用程序与关系数据库的映射变得更加容易。
MyBatis使用xml描述符或注解将对象与存储过程或SQL语句相结合。Mybatis最大优点是应用程序与Sql进行解耦,sql语句是写在Xml Mapper文件中。
OGNL表达式在Mybatis当中应用非常广泛,其表达式的灵活性使得动态Sql功能的非常强大。OGNL是Object-Graph Navigation Language的缩写,代表对象图导航语言。
OGNL是一种EL表达式语言,用于设置和获取Java对象的属性,并且可以对列表进行投影选择以及执行lambda表达式。Ognl类提供了许多简便方法用于执行表达式的。Struts2发布的每个版本都会出现的新的高危可执行漏洞也是因为它使用了灵活的OGNL表达式。
公司后端采用Mybatis作为数据访问层,所使用版本为3.2.3。线上环境业务系统在运行过程中出现了一个令人困惑的异常, 该异常时而出现时而不出现,构造各种OGNL表达式为空等特殊情况均不会重现该异常。
具体异常堆栈信息如下:
### Error querying database. Cause: org.apache.ibatis.builder.BuilderException: Error evaluating expression 'list != null and list.size() > 0'. Cause: org.apache.ibatis.ognl.MethodFailedException: Method "size" failed for object [1] [java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not access a member of class java.util.Collections$SingletonList with modifiers "public"]
### Cause: org.apache.ibatis.builder.BuilderException: Error evaluating expression 'list != null and list.size() > 0'. Cause: org.apache.ibatis.ognl.MethodFailedException: Method "size" failed for object [1] [java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not access a member of class java.util.Collections$SingletonList with modifiers "public"]
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:23) org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:107)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:98)
at cn.com.shaobingmm.MybatisBugTest$2.run(MybatisBugTest.java:88)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.apache.ibatis.builder.BuilderException: Error evaluating expression 'list != null and list.size() > 0'. Cause: org.apache.ibatis.ognl.MethodFailedException: Method "size" failed for object [1] [java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not access a member of class java.util.Collections$SingletonList with modifiers "public"]
at org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java
at:47)
at org.apache.ibatis.scripting.xmltags.ExpressionEvaluator.evaluateBoolean(ExpressionEvaluator.java:29)
at org.apache.ibatis.scripting.xmltags.IfSqlNode.apply(IfSqlNode.java:30)
at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:29)
at org.apache.ibatis.scripting.xmltags.TrimSqlNode.apply(TrimSqlNode.java:51)
at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:29)
at org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:37)
at org.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:275)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:79)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:104)
... 3 more
Caused by: org.apache.ibatis.ognl.MethodFailedException: Method "size" failed for object [1] [java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not access a member of class java.util.Collections$SingletonList with modifiers "public"]
at org.apache.ibatis.ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:837)
at org.apache.ibatis.ognl.ObjectMethodAccessor.callMethod(ObjectMethodAccessor.java:61)
at org.apache.ibatis.ognl.OgnlRuntime.callMethod(OgnlRuntime.java:860)
at org.apache.ibatis.ognl.ASTMethod.getValueBody(ASTMethod.java:73)
at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)
at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)
at org.apache.ibatis.ognl.ASTChain.getValueBody(ASTChain.java:109)
at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)
at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)
at org.apache.ibatis.ognl.ASTGreater.getValueBody(ASTGreater.java:49)
at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)
at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)
at org.apache.ibatis.ognl.ASTAnd.getValueBody(ASTAnd.java:56)
at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)
at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)
at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:333)
at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:413)
at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:395)
at org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java:45)
... 12 more
List的size()方法明显是public为何还会出现不可访问的异常。该问题并不是每一次都会出现,经过多次尝试,该异常一直未在测试环境重现。
该接口在完整调用链路中的出错次数占总调用次数的比率为0.01%,无意中联想到并发问题在周期性时间内往往是概率性发生。
编写模拟多线程环境并发读取公司列表测试代码:
<mapper namespace="CompanyMapper">
<select id="getCompanysByIds"resultType="cn.com.shaobingmm.Company">
select *
from company
<where>
<if test="list != null and list.size() > 0">
and id in
<foreach collection="list" item="id" open="(" separator="," close=")">#{id}
</foreach>
</where>
</select>
</mapper>
多线程并发环境下的压测代码
String resource = "mybatis-config.xml";
InputStream in = null;
try {
in = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
final List<Long> ids = Collections.singletonList(1L);
final SqlSession session = sqlSessionFactory.openSession();
final CountDownLatch mCountDownLatch = new CountDownLatch(1);
for (int i = 0; i < 50; i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
try {
mCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
for (int k = 0; k < 100; k++) {
session.selectList("CompanyMapper.getCompanysByIds", ids);
thread.start();
mCountDownLatch.countDown();
synchronized (MybatisBugTest.class) {
try {
MybatisBugTest.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Throwable e) {
e.printStackTrace();
} finally {
if (in != null)
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
上诉异常堆栈信息在并发环境下果然重现出现,根据异常信息代码执行至该行代码时发生异常:
Caused by: org.apache.ibatis.ognl.MethodFailedException: Method "size" failed for object [1] [java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not access a member of class java.util.Collections$SingletonList with modifiers "public"]
at org.apache.ibatis.ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:837)
异常信息表明OgnlRuntime类不能够访问java.util.Collections的私有成员SingletonList。查看源代码发现能够抛出MethodFailedException异常可以锁定在invokeMethod方法内部。
public static Object callAppropriateMethod(OgnlContext context, Object source, Object target, String methodName, String propertyName, List methods, Object[] args) throws MethodFailedException {
Object reason = null;
Object[] actualArgs = objectArrayPool.create(args.length);
try {
Method e = getAppropriateMethod(context, source, target, methodName, propertyName, methods, args, actualArgs);
if(e == null || !isMethodAccessible(context, source, e, propertyName)) {
StringBuffer buffer = new StringBuffer();
if(args != null) {
int i = 0;
for(int ilast = args.length - 1; i <= ilast; ++i) {
Object arg = args[i];
buffer.append(arg == null?NULL_STRING:arg.getClass().getName());
if(i < ilast) {
buffer.append(", ");
throw new NoSuchMethodException(methodName + "(" + buffer + ")");
Object var14 = invokeMethod(target, e, actualArgs);
return var14;
} catch (NoSuchMethodException var21) {
reason = var21;
} catch (IllegalAccessException var22) {
reason = var22;
} catch (InvocationTargetException var23) {
reason = var23.getTargetException();
} finally {
objectArrayPool.recycle(actualArgs);
throw new MethodFailedException(source, methodName, (Throwable)reason);
invokeMethod方法代码
public static Object invokeMethod(Object target, Method method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException {
boolean wasAccessible = true;
if(securityManager != null) {
try {
securityManager.checkPermission(getPermission(method));
} catch (SecurityException var6) {
throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
if((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !(wasAccessible = method.isAccessible())) {
method.setAccessible(true); (1)
Object result = method.invoke(target, argsArray); (3)
if(!wasAccessible) {
method.setAccessible(false); (2)
return result;
问题出现在method实际上是一个共享变量,也就是例子中的
public int java.util.Collections$SingletonList.size()
当第一个线程t1至(1)行代码允许method方法可以被调用,第二个线程t2执行至(2)将method的方法设置为不可以访问。接着t1又开始执行到(3)行的时候就会发生该异常。这是一个很典型的同步问题。
Ognl2.7已经修复了该问题,因为ognl源码是直接打包内嵌在mybatis包中,mybatis3.3.0版本中也已经进行了修复升级。(划重点)
public static Object invokeMethod(Object target, Method method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException {
boolean syncInvoke = false;
boolean checkPermission = false;
int mHash = method.hashCode();
synchronized(method) {
if(_methodAccessCache.get(Integer.valueOf(mHash)) == null || _methodAccessCache.get(Integer.valueOf(mHash)) == Boolean.TRUE) {
syncInvoke = true;
if(_securityManager != null && _methodPermCache.get(Integer.valueOf(mHash)) == null || _methodPermCache.get(Integer.valueOf(mHash)) == Boolean.FALSE) {
checkPermission = true;
boolean wasAccessible = true;
Object result;
if(syncInvoke) {
synchronized(method) {
if(checkPermission) {
try {
_securityManager.checkPermission(getPermission(method));
_methodPermCache.put(Integer.valueOf(mHash), Boolean.TRUE);
} catch (SecurityException var12) {
_methodPermCache.put(Integer.valueOf(mHash), Boolean.FALSE);
throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
if(Modifier.isPublic(method.getModifiers()) && Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
_methodAccessCache.put(Integer.valueOf(mHash), Boolean.FALSE);
} else if(!(wasAccessible = method.isAccessible())) {
method.setAccessible(true);
_methodAccessCache.put(Integer.valueOf(mHash), Boolean.TRUE);
} else {
_methodAccessCache.put(Integer.valueOf(mHash), Boolean.FALSE);
result = method.invoke(target, argsArray);
if(!wasAccessible) {
method.setAccessible(false);
} else {
if(checkPermission) {
try {
_securityManager.checkPermission(getPermission(method));
_methodPermCache.put(Integer.valueOf(mHash), Boolean.TRUE);
} catch (SecurityException var11) {
_methodPermCache.put(Integer.valueOf(mHash), Boolean.FALSE);
throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
result = method.invoke(target, argsArray);
return result;
作者:蓬蒿
来源:zhuanlan.zhihu.com/p/30085658
分享一份最新 Java 架构师学习资料
- END -
推荐阅读:
1、硬核!尽量避免 bug 手法
2、HashMap 为什么线程不安全?
3、3种骚操作,教你查看 Java 字节码!
4、疯了!同事又问我为什么不能用 isXXX
5、不能用 + 拼接字符串? 这次我要吊打面试官!
关注Java技术栈公众号在后台回复:Java,可获取一份栈长整理的最新 Java 技术干货。
点击「阅读原文」带你飞~
原文链接:https://mp.weixin.qq.com/s/flzGIy5QHqs2VN_bu5__ag#rd
在我使用MyBatis中使用Mapper接口的时候MyBatis出现以下错误:
"nested exception is org.apache.ibatis.builder.BuilderException: Error evaluating expression 'param.userId != null and param.userId !='' '. Cause: org.apache.ibatis.ognl.OgnlException: source is null for getProperty
Action 向页面传输时,定义 Action 内部的 VO 类,无法传输
MyBatis 调用 Mapper 接口,传入List 集合,List = subList 的提示如下异常
Caused by: org.apache.ibatis.builder.BuilderException: Error evaluating expression ‘orderIdList...
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.apache.ibatis.builder.BuilderException: Error evaluating expression 'name !=null and name != '. Cause: org.apache.ibatis.ognl.ExpressionSyntaxException: Malformed O.
OgnlException这个Mybatis的错误其实就是包装了Java的NullPointException异常。所以显而易见,就是你的这个属性是空的。但是前几天我在用Mybatis时,出现了这个错误,却没有查出原因。于是我查找了csdn里面的一篇博客才发现其实问题很简单。博客地址:https://blog.csdn.net/qq_32331073/article/details/762...
1、错误描述2017-10-08 23:58:06,672 ERROR [core.interceptor.ExceptionResolverCustom] (http-apr-8686-exec-4:) nested exception is org.apache.ibatis.builder.BuilderException: Error evaluating expression 'pd.l
卡在这个问题困扰了一整天了,没有理解这个mybatis的运行机制,最后抽丝剥茧面向csdn编程一下,解决了此问题,特写博文分享。解决了.builder.BuilderException: Error parsing SQL这个问题。