当前位置: 首页 > 科技观察

警惕,MyBatis的size()方法有陷阱!

时间:2023-03-18 16:21:18 科技观察

来源:http://h5ip.cn/aJgJMybatis是一个开源的轻量级半自动化ORM框架,使得面向对象应用程序和关系数据库之间的映射更加容易。MyBatis使用xml描述符或注解将对象与存储过程或SQL语句结合起来。Mybatis最大的优点就是应用程序与Sql解耦,sql语句写在XmlMapper文件中。OGNL表达式在Mybatis中被广泛使用,其表达式的灵活性使得动态Sql非常强大。OGNL是Object-GraphNavigationLanguage的缩写,代表对象图导航语言。OGNL是一种EL表达式语言,用于设置和获取Java对象的属性,可以对列表进行投影选择和执行lambda表达式。Ognl类提供了许多方便的方法来执行表达式。Struts2各个版本出现的新的高危可执行漏洞也是因为它使用了灵活的OGNL表达式。公司后台使用Mybatis作为数据访问层,使用的版本是3.2.3。在线环境业务系统运行过程中出现了一个令人困惑的异常。异常时有出现,时有不出现。在特殊情况下不会重现异常,例如构造各种空的OGNL表达式。具体异常堆信息如下:###Errorqueryingdatabase.Cause:org.apache.ibatis.builder.BuilderException:Errorevaluatingexpression'list!=nullandlist.size()>0'.Cause:org.apache.ibatis.ognl.MethodFailedException:Method“size”failedforobject[1][java.lang.IllegalAccessException:Classorg.apache.ibatis.ognl.OgnlRuntimecannotaccessamemberofclassjava.util.Collections$SingletonListwithmodifiers“public”]###Cause:org.apache.ibatis.builder.BuilderException:Errorevaluatingexpression'list!=nullandlist.size()>0'.Cause:org.apache.ibatis.ognl.MethodFailedException:Method"size"failedforobject[1][java.lang.IllegalAccessException:Classorg.apache.ibatis.ognl.OgnlRuntimecannotaccessamemberofclassjava.util.Collections$SingletonListwithmodifiers"public"]atorg.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:23)org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:107)atorg.apache。伊巴特is.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:98)atcn.com.shaobingmm.MybatisBugTest$2.run(MybatisBugTest.java:88)atjava.lang.Thread.run(Thread.java:745)Causedby:org.apache.ibatis.builder.BuilderException:Errorevaluatingexpression'list!=nullandlist.size()>0'.Cause:org.apache.ibatis.ognl.MethodFailedException:Method"size"failedforobject[1][java.lang.IllegalAccessException:Classorg.apache.ibatis.ognl.OgnlRuntimecannotaccessamemberofclassjava.util.Collections$SingletonListwithmodifiers"public"]atorg.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.javaat:47)atorg.apache.ibatis.scripting.xmltags.ExpressionEvaluator.evaluateBoolean(ExpressionEvaluator.java:29)atorg.apache.ibatis.scripting.xmltags.IfSqlNode.apply(IfSqlNode.java:30)atorg.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:29)atorg.apache.ibatis.scripting.xmltags.TrimSqlNode.apply(TrimSqlNode.java:51)atorg.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:29)atorg.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:37)atorg.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:275)atorg.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:79)atorg.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:104)...3moreCausedby:org.apache.ibatis.ognl.MethodFailedException:Method"size"failedforobject[1][java.lang.IllegalAccessException:Classorg.apache.ibatis.ognl.OgnlRuntimecannotaccessamemberofclassjava.util.Collections$SingletonListwithmodifiers"public"]atorg.apache.ibatis.ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:837)atorg.apache.ibatis.ognl.ObjectMethodAccessor.callMethod(ObjectMethodAccessor.java:61)atorg.apache.ibatis.ognl.OgnlRuntime.callMethod(OgnlRuntime.java:860)atorg.apache.ibatis.ognl.ASTMethod.getValueBody(ASTMethod.java:73)atorg.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)atorg.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)atorg.apache.ibatis.ognl。ASTChain.getValueBody(ASTChain.java:109)atorg.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)atorg.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)atorg.apache。ibatis.ognl.ASTGreater.getValueBody(ASTGreater.java:49)atorg.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)atorg.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)atorg.apache.ibatis.ognl.ASTAnd.getValueBody(ASTAnd.java:56)atorg.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:170)atorg.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:210)atorg.apache.ibatis.ognl.Ognl.getValue(Ognl.java:333)atorg.apache.ibatis.ognl.Ognl.getValue(Ognl.java:413)atorg.apache.ibatis.ognl.Ognl。getValue(Ognl.java:395)atorg.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java:45)...12moreList的size()方法显然是公开的。为什么还是出现无法访问的异??常?这个问题不是每次都会出现。经过多次尝试,该异常在测试环境中并没有重现。该接口在整个调用链中的错误数占总调用数的比例为0.01%。不经意间想到,并发问题往往是周期性的概率发生。编写一段模拟多线程环境并发读取公司列表的测试代码:select*fromcompany0">andidin#{id}多线程并发环境压测代码Stringresource="mybatis-config.xml";InputStreamin=null;try{in=Resources.getResourceAsStream(resource);SqlSessionFactorysqlSessionFactory=newSqlSessionFactoryBuilder().build(in);finalListids=Collections.singletonList(1L);finalSqlSession=sqlSessionFactory.openSession();finalCountDownLatchmCountDownLatch=newCountDownLatch(1);for(inti=0;i<50;i++){Threadthread=newThread(newRunnable(){publicvoidrun(){try{mCountDownLatch.await();}catch(InterruptedExceptione){e.printStackTrace();}for(intk=0;k<100;k++){session.selectList("CompanyMapper.getCompanysByIds",ids);}}});thread.start();}mCountDownLatch.countDown();同步(MybatisBugTest.class){try{MybatisBugTest.class.wait();}catch(InterruptedExceptione){e.printStackTrace();}}}catch(IOExceptione){e.printStackTrace();}catch(Throwablee){e.printStackTrace();}finally{if(in!=null)try{in.close();}catch(IOExceptione){e.printStackTrace();}}并发环境中重现异常堆栈信息,根据Exception信息代码执行到这行代码时出现异常:Causedby:org.apache.ibatis.ognl.MethodFailedException:Method"size"failedforobject[1][java.lang.IllegalAccessException:Classorg.apache.ibatis.ognl.OgnlRuntimecannotaccessamemberofclassjava.util.Collections$SingletonListwithmodifiers"public"]atorg.apache.ibatis.ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:837)异常信息表明OgnlRuntime类无法访问java.util.Collections的私有成员SingletonList检查源码,发现可以抛出MethodFailedException,可以锁在invokeMethod方法里面。publicstaticObjectcallAppropriateMethod(OgnlContextcontext,Objectsource,Objecttarget,StringmethodName,StringpropertyName,Listmethods,Object[]args)throwsMethodFailedException{Objectreason=null;Object[]actualArgs=objectArrayPool.create(args.length);try{Methode=getAppropriateMethod(context,source,target),methodName,propertyName,methods,args,actualArgs);if(e==null||!isMethodAccessible(context,source,e,propertyName)){StringBufferbuffer=newStringBuffer();if(args!=null){inti=0;for(intilast=args.length-1;i<=ilast;++i){Objectarg=args[i];buffer.append(arg==null?NULL_STRING:arg.getClass().getName());if(i