MyBatis是一个开源的轻量级半自动化ORM框架,面向对象应用程序与关系数据库的映射变得更加容易。MyBatis使用xml对齐或注解将对象与存储过程或SQL语句相结合。压缩,sql语句是写在Xml Mapper文件中。
OGNL是一种EL表达语言,用的是OGLL表达在Mybatis的主要应用中非常广泛,其表达式的补充和动态SQL功能的功能非常强大。OGNL是对象图导航语言的缩写,代表对象图导航语言。设置和获取Java对象的属性,并且可以对列表进行投影选择以及执行lambda表达式。Ognl类提供了许多替代方法进行执行的表达式。Struts2发布的每个版本都会出现的新的高危重构。进攻也是因为它使用了灵活的OGNL表达。
公司环境采用Mybatis作为数据访问层,所用版本为3.2.3。在线环境业务系统在运行过程中出现了一个令人震惊的异常,该异常时而出现时而不出现,构造各种OGNL表达为空等特殊情况均不会重现该异常。具体异常例外信息如下:
###查询数据库时出错。原因:org.apache.ibatis.builder.BuilderException:计算表达式'list!= null和list.size()> 0'时出错。原因:org.apache.ibatis.ognl.MethodFailedException:对象[1]的方法“大小”失败。[java.lang.IllegalAccessException:类org.apache.ibatis.ognl.OgnlRuntime无法访问类java.util的成员。带有修饰符“ public”的Collections $ SingletonList]
###原因:org.apache.ibatis.builder.BuilderException:计算表达式'list!= null和list.size()> 0'时出错。原因:org.apache.ibatis.ognl.MethodFailedException:对象[1]的方法“大小”失败。[java.lang.IllegalAccessException:类org.apache.ibatis.ognl.OgnlRuntime无法访问类java.util的成员。具有修饰符“ public”的Collections $ SingletonList
在org.apache.ibatis.session.defaults处的org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:23)org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:107)
处。
在cn.com.shaobingmm.MybatisBugTest $ 2.run(MybatisBugTest.java:88)
处的DefaultSqlSession.selectList(DefaultSqlSession.java:98)在java.lang.Thread.run(Thread.java:745)
产生原因:org.apache。 ibatis.builder.BuilderException:计算表达式'list!= null和list.size()> 0'时出错。原因:org.apache.ibatis.ognl.MethodFailedException:对象[1]的方法“大小”失败。[java.lang.IllegalAccessException:类org.apache.ibatis.ognl.OgnlRuntime无法访问类java.util的成员。具有修饰符“ public”的Collections $ SingletonList]
在org.apache.ibatis的org.apache.ibatis.scripting.xmltags.ExpressionEvaluator.evaluateBoolean(ExpressionEvaluator.java:29)的org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java
在:47)
处
。
org.apache.ibatis.scripting.xmltags.TrimSqlNode.apply()
上的scripting.xmltags.IfSqlNode.apply(IfSqlNode.java:30)在org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:29)TrimSqlNode.java:51)
在org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:29)
在org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:37)
在组织.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:275)
在org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(
DefaultSqlSession.java:104)处的org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:79)
... 3更多
原因:org。 apache.ibatis.ognl.MethodFailedException:对象[1]的方法“大小”失败。[java.lang.IllegalAccessException:类org.apache.ibatis.ognl.OgnlRuntime无法使用以下方式访问类java.util.Collections $ SingletonList的成员
在org.apache.ibatis处的org.apache.ibatis.ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:837)
处的修饰符“公共”] 在org.apache.ibatis.ognl.ObjectMethodAccessor.callMethod(ObjectMethodAccessor.java:61)
处。
org.apache.ibatis.ognl.ASTMethod.getValueBody(ASTMethod.java:上的ognl.OgnlRuntime.callMethod(OgnlRuntime.java:860)73)
在org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)
在org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)
在org.apache.ibatis.ognl.ASTChain.getValueBody (ASTChain.java:109)
在org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)
在org.apache.ogatil.SimpleNode.getValue(SimpleNode.java:210)
在org.apache。 ibatis.ognl.ASTGreater.getValueBody(ASTGreater.java:49)
在org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)
在org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java: 210)
在org.apache.ibatis.ognl.ASTAnd.getValueBody(ASTAnd.java:56)
在org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)
在org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)
在org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:333)
在org.apache.ibatis.ognl.Ognl.getValue (Ognl.java:413),
位于org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:395),
位于org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java:45)
...另外12个
该问题并非每一次都会出现,经过多次尝试,该异常一直未在测试环境重现。该接口在完整调用互连中的错误次数占总调用次数的比率为0.01%,无意中联想到并发问题在初步引起的是概率性发生。
编写模拟多线程环境并发读取公司列表测试代码:
从公司和* {#id}中的ID中选择*
多线程并发环境下的压测代码
字符串资源=“ mybatis-config.xml”; InputStream in = null; 尝试{in = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory =新的SqlSessionFactoryBuilder()。build(in); 最终名单ids = Collections.singletonList(1L); 最后的SqlSession会话= sqlSessionFactory.openSession(); 最后的CountDownLatch mCountDownLatch = new CountDownLatch(1); for(int i = 0; i <50; i ++){线程=新线程(new Runnable(){public void run(){try {mCountDownLatch.await();} catch(InterruptedException e){e.printStackTrace( );} for(int k = 0; k <100; k ++){
session.selectList(“ CompanyMapper.getCompanysByIds”,id);
}
}
});
thread.start();
}
mCountDownLatch.countDown();
同步(MybatisBugTest.class){
试试{
MybatisBugTest.class.wait();
} catch(InterruptedException e){
e.printStackTrace();
}
}
} catch(IOException e){
e.printStackTrace();
} catch(Throwable e){
e.printStackTrace();
}最后{
如果(in!= null)
尝试{
in.close();
} catch(IOException e){
e.printStackTrace();
}
}
吸引力异常信息在并发环境下果然重现出现,根据异常信息代码执行至该行代码时发生异常:
由以下原因引起:org.apache.ibatis.ognl.MethodFailedException:对象[1]的方法“大小”失败。。实用程序。在org处带有修饰符“ public”的集合$ SingletonList。阿帕奇。ibatis。ognl。OgnlRuntime。callAppropriateMethod(OgnlRuntime。java的:837)
异常信息表明OgnlRuntime类不能够访问java.util.Collections的私有成员SingletonList。查看源发现能够引发MethodFailedException异常可以锁定在invokeMethod方法内部。
公共静态对象callAppropriateMethod(OgnlContext上下文,对象源,对象目标,字符串methodName,字符串propertyName,列表方法,Object [] args)抛出MethodFailedException {对象reason = null; Object [] actualArgs = objectArrayPool.create(args.length); 尝试{方法e = getAppropriateMethod(上下文,源,目标,methodName,propertyName,方法,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){对象arg = args [i]; buffer.append(arg == null?NULL_STRING:arg.getClass()。getName()); if(i buffer.append(“,”); } } } 抛出新的NoSuchMethodException(methodName +“(” + buffer +“)”); } 对象var14 = invokeMethod(target,e,actualArgs); 返回var14; } catch(NoSuchMethodException var21){ 原因= var21; } catch(IllegalAccessException var22){ 原因= var22; } catch(InvocationTargetException var23){ 原因= var23.getTargetException(); }最后{ objectArrayPool.recycle(actualArgs); } 抛出新的MethodFailedException(source,methodName,(Throwable)reason); } invokeMethod方法代码 公共静态对象invokeMethod(Object target,Method method,Object [] argsArray)引发InvocationTargetException,IllegalAccessException { boolean wasAccessible = true; if(securityManager!= null){ 尝试{ securityManager.checkPermission(getPermission(method)); } catch(SecurityException var6){ 抛出新的IllegalAccessException(“ Method [” + method +“]无法访问。”); } } 如果((!Modifier.isPublic(method.getModifiers())||!Modifier.isPublic(method.getDeclaringClass()。getModifiers()))&&!(wasAccessible = method.isAccessible())){ method.setAccessible (真正); (1) } 对象结果= method.invoke(target,argsArray); (3) if(!wasAccessible){ method.setAccessible(false); (2) } 返回结果; } 问题出现在方法实际上是一个共享变量,也就是示例中的 public int java.util.Collections $ SingletonList.size() 方法 当第一个线程t1至(1)行代码允许方法方法可以被调用,第二个线程t2执行至(2)将方法的方法设置为不可以访问。接着t1又开始执行到(3)行的当时就会发生该异常。这是一个很典型的同步问题。 Ognl2.7已经修复了该问题,因为ognl二进制文件是直接打包内嵌在mybatis包中,mybatis3.3.0版本中也已经进行了修复升级。(划重点) 公共静态对象invokeMethod(Object target,Method method,Object [] argsArray)引发InvocationTargetException,IllegalAccessException { boolean syncInvoke = false; boolean checkPermission = false; int mHash = method.hashCode(); 同步的(方法){ 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; } } 布尔wasAccessible = TRUE; 对象结果; if(syncInvoke){ 同步(方法){ if(checkPermission){ 试试{ _securityManager.checkPermission(getPermission(method)); _methodPermCache.put(Integer.valueOf(mHash),Boolean.TRUE); } catch(SecurityException var12){_ methodPermCache.put(Integer.valueOf(mHash),Boolean.FALSE); 抛出新的IllegalAccessException(“ Method [” + method +“]无法访问。”); } } 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); } 结果= method.invoke(target,argsArray); if(!wasAccessible){method.setAccessible (false); } } } else { if(checkPermission){ 试试{ _securityManager.checkPermission(getPermission(method)); _methodPermCache.put(Integer.valueOf(mHash),Boolean.TRUE); } catch(SecurityException var11){_ methodPermCache.put(Integer.valueOf(mHash),Boolean.FALSE); 抛出新的IllegalAccessException(“ Method [” + method +“]无法访问。”); } } 结果= method.invoke(target,argsArray); } 返回结果; }