这篇文章将为大家详细讲解有关Mybatis中Size()方法的作用是什么,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

前言

MyBatis 是一个开源的轻量级的半自动化的 ORM 框架,用于面向对象和关系型数据库的映射,其中 xml 文件,和sql语句结合,最大的特点,应用程序sql解耦。OGNL表达式,是MyBatis中的广泛应用,是一种EL语言,用于设置和获取 Java 对象的属性,并且可以对列表进行投影和执行lambda表达式,ognl提供了简单,便于执行的ognl表达式。一个线上服务,经常会出现一个异常,构造各种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.ibatis.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)...12more

List的size方法明明有public,还不可访问,该异常在测试环境未重现,但是在接口的完整调用链路中出错的次数占总的调用次数的0.01%,这是概率性事件。

模拟测试

编写模拟多线程并发读取公司列表的测试代码

<mappernamespace="CompanyMapper"><selectid="getCompanysByIds"resultType="cn.com.shaobingmm.Company">select*fromcompany<where><iftest="list!=nullandlist.size()>0">andidin<foreachcollection="list"item="id"open="("separator=","close=")">#{id}</foreach></if></where></select></mapper>

多线程下进行压力测试

Stringresource="mybatis-config.xml";InputStreamin=null;try{in=Resources.getResourceAsStream(resource);SqlSessionFactorysqlSessionFactory=newSqlSessionFactoryBuilder().build(in);finalList<Long>ids=Collections.singletonList(1L);finalSqlSessionsession=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();synchronized(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();}}

上述代码在并发的时候会出现异常。

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。查看源代码,可以知道锁定在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<ilast){buffer.append(",");}}}thrownewNoSuchMethodException(methodName+"("+buffer+")");}Objectvar14=invokeMethod(target,e,actualArgs);returnvar14;}catch(NoSuchMethodExceptionvar21){reason=var21;}catch(IllegalAccessExceptionvar22){reason=var22;}catch(InvocationTargetExceptionvar23){reason=var23.getTargetException();}finally{objectArrayPool.recycle(actualArgs);}thrownewMethodFailedException(source,methodName,(Throwable)reason);}

其方法代码

publicstaticObjectinvokeMethod(Objecttarget,Methodmethod,Object[]argsArray)throwsInvocationTargetException,IllegalAccessException{booleanwasAccessible=true;if(securityManager!=null){try{securityManager.checkPermission(getPermission(method));}catch(SecurityExceptionvar6){thrownewIllegalAccessException("Method["+method+"]cannotbeaccessed.");}}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;}

问题出现在meta是一个共享变量,即

publicintjava.util.Collections$SingletonList.size()

当,第一个线程t1到第一行代码允许method方法可以调用,第二个线程t2,执行到2把方法method设置为不可访问,接着t1又执行,此时行列3会发生异常。

升级版本

lgnl2.7,已经修复了这个问题,所以修复后的代码如下

publicstaticObjectinvokeMethod(Objecttarget,Methodmethod,Object[]argsArray)throwsInvocationTargetException,IllegalAccessException{booleansyncInvoke=false;booleancheckPermission=false;intmHash=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(SecurityExceptionvar12){_methodPermCache.put(Integer.valueOf(mHash),Boolean.FALSE);thrownewIllegalAccessException("Method["+method+"]cannotbeaccessed.");}}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(SecurityExceptionvar11){_methodPermCache.put(Integer.valueOf(mHash),Boolean.FALSE);thrownewIllegalAccessException("Method["+method+"]cannotbeaccessed.");}}result=method.invoke(target,argsArray);}returnresult;}

关于Mybatis中Size()方法的作用是什么就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。