1. 首页
  2. IT资讯

Mybatis 有坑,千万别踩

“u003Cblockquote class=”pgc-blockquote-abstract”u003Eu003Cpu003E作者:蓬蒿u003Cu002Fpu003Eu003Cpu003E来源:zhuanlan.zhihu.comu002Fpu002F30085658u003Cu002Fpu003Eu003Cu002Fblockquoteu003Eu003Cpu003EMybatis是一个开源的轻量级半自动化ORM框架,使得面向对象应用程序与关系数据库的映射变得更加容易。u003Cu002Fpu003Eu003Cpu003EMyBatis使用xml描述符或注解将对象与存储过程或SQL语句相结合。Mybatis最大优点是应用程序与Sql进行解耦,sql语句是写在Xml Mapper文件中。u003Cu002Fpu003Eu003Cpu003EOGNL表达式在Mybatis当中应用非常广泛,其表达式的灵活性使得动态Sql功能的非常强大。OGNL是Object-Graph Navigation Language的缩写,代表对象图导航语言。u003Cu002Fpu003Eu003Cpu003EOGNL是一种EL表达式语言,用于设置和获取Java对象的属性,并且可以对列表进行投影选择以及执行lambda表达式。Ognl类提供了许多简便方法用于执行表达式的。Struts2发布的每个版本都会出现的新的高危可执行漏洞也是因为它使用了灵活的OGNL表达式。u003Cu002Fpu003Eu003Cpu003E公司后端采用Mybatis作为数据访问层,所使用版本为3.2.3。线上环境业务系统在运行过程中出现了一个令人困惑的异常, 该异常时而出现时而不出现,构造各种OGNL表达式为空等特殊情况均不会重现该异常。u003Cu002Fpu003Eu003Cpu003E具体异常堆栈信息如下:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003E### 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"failedforobject [1] [java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not accessamember 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"failedforobject [1] [java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not accessamember 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)atcn.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"failedforobject [1] [java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not accessamember of class java.util.Collections$SingletonList with modifiers"public"]at org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.javaat: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)…3moreCaused by: org.apache.ibatis.ognl.MethodFailedException: Method"size"failedforobject [1] [java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not accessamember 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)…12moreu003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003EList的size()方法明显是public为何还会出现不可访问的异常。该问题并不是每一次都会出现,经过多次尝试,该异常一直未在测试环境重现。u003Cu002Fpu003Eu003Cpu003E该接口在完整调用链路中的出错次数占总调用次数的比率为0.01%,无意中联想到并发问题在周期性时间内往往是概率性发生。u003Cu002Fpu003Eu003Cpu003E编写模拟多线程环境并发读取公司列表测试代码:u003Cu002Fpu003Eu003Cdiv class=”pgc-img”u003Eu003Cimg src=”http:u002Fu002Fp3.pstatp.comu002Flargeu002Fpgc-imageu002F945633cdc26342f5b4e98d5530a3e6a8″ img_width=”590″ img_height=”312″ alt=”Mybatis 有坑,千万别踩” inline=”0″u003Eu003Cp class=”pgc-img-caption”u003Eu003Cu002Fpu003Eu003Cu002Fdivu003Eu003Cpu003E多线程并发环境下的压测代码u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003EString resource ="mybatis-config.xml";InputStreamin=null;try{in= Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory =newSqlSessionFactoryBuilder().build(in);final List<Long> ids = Collections.singletonList(1L);final SqlSession session = sqlSessionFactory.openSession();final CountDownLatch mCountDownLatch =newCountDownLatch(1);for(inti =0; i <50; i++) {Thread thread =newThread(newRunnable() {publicvoidrun(){try{mCountDownLatch.await();}catch(InterruptedException e) {e.printStackTrace();}for(intk =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();}}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E上诉异常堆栈信息在并发环境下果然重现出现,根据异常信息代码执行至该行代码时发生异常:u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003ECausedby:org.apache.ibatis.ognl.MethodFailedException:Method"size"failedforobject[1][java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not access a member of class java.util.Collections$SingletonList with modifiers "public"]atorg.apache.ibatis.ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:837)u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E异常信息表明OgnlRuntime类不能够访问java.util.Collections的私有成员SingletonList。查看源代码发现能够抛出MethodFailedException异常可以锁定在invokeMethod方法内部。u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003EpublicstaticObjectcallAppropriateMethod(OgnlContext context,Objectsource,Objecttarget,StringmethodName,StringpropertyName, List methods,Object[] args) throws MethodFailedException {Objectreason =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 =newStringBuffer();if(args !=null) {int i =0;for(int ilast = args.length -1; i <= ilast; ++i) {Objectarg = args[i];buffer.append(arg ==null?NULL_STRING:arg.getClass().getName());if(i < ilast) {buffer.append(", ");}}}thrownewNoSuchMethodException(methodName +"("+ buffer +")");}Objectvar14 = invokeMethod(target, e, actualArgs);returnvar14;}catch(NoSuchMethodException var21) {reason = var21;}catch(IllegalAccessException var22) {reason = var22;}catch(InvocationTargetException var23) {reason = var23.getTargetException();}finally{objectArrayPool.recycle(actualArgs);}thrownewMethodFailedException(source, methodName, (Throwable)reason);}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cp class=””u003EinvokeMethod方法代码u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003EpublicstaticObjectinvokeMethod(Objecttarget, Method method,Object[] argsArray) throws InvocationTargetException, IllegalAccessException {booleanwasAccessible =true;if(securityManager !=null) {try{securityManager.checkPermission(getPermission(method));}catch(SecurityException var6) {thrownewIllegalAccessException("Method ["+ method +"] cannot be accessed.");}}if((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !(wasAccessible = method.isAccessible())) {method.setAccessible(true); (1)}Objectresult = method.invoke(target, argsArray); (3)if(!wasAccessible) {method.setAccessible(false); (2)}returnresult;}u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003E问题出现在method实际上是一个共享变量,也就是例子中的u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003Epublicint java.util.Collections$SingletonList.size()u003Cu002Fcodeu003Eu003Cu002Fpreu003Eu003Cpu003Eu003Cstrongu003E方法u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E当第一个线程t1至(1)行代码允许method方法可以被调用,第二个线程t2执行至(2)将method的方法设置为不可以访问。接着t1又开始执行到(3)行的时候就会发生该异常。这是一个很典型的同步问题。u003Cu002Fpu003Eu003Cpu003EOgnl2.7已经修复了该问题,因为ognl源码是直接打包内嵌在mybatis包中,mybatis3.3.0版本中也已经进行了修复升级。(划重点)u003Cu002Fpu003Eu003Cpreu003Eu003Ccodeu003EpublicstaticObjectinvokeMethod(Objecttarget, Method method,Object[] argsArray) throws InvocationTargetException, IllegalAccessException {booleansyncInvoke =false;booleancheckPermission =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;}}booleanwasAccessible =true;Objectresult;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);thrownewIllegalAccessException("Method ["+ method +"] cannot be accessed.");}}if(Modifier.isPublic(method.getModifiers()) && Modifier.isPublic(method.getDeclaringClass().getModifiers())) {_methodAccessCache.put(Integer.valueOf(mHash),Boolean.FALSE);}elseif(!(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);thrownewIllegalAccessException("Method ["+ method +"] cannot be accessed.");}}result = method.invoke(target, argsArray);}returnresult;}u003Cu002Fcodeu003Eu003Cu002Fpreu003E”

原文始发于:Mybatis 有坑,千万别踩

主题测试文章,只做测试使用。发布者:乾奕,转转请注明出处:http://www.cxybcw.com/26270.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code