Mybatis居然有坑,千万别踩

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);

}

返回结果;

}

你可能感兴趣的:(Mybatis居然有坑,千万别踩)