MyBatis的size()方法的坑

Mybatis是一个开源的轻量级半自动化ORM框架,使得面向对象应用程序与关系数据库的映射变得更加容易。MyBatis使用xml描述符或注解将对象与存储过程或SQL语句相结合。Mybatis最大优点是应用程序与Sql进行解耦,sql语句是写在Xml Mapper文件中。
OGNL表达式在Mybatis当中应用非常广泛,其表达式的灵活性使得动态Sql功能的非常强大。OGNL是Object-Graph Navigation Language的缩写,代表对象图导航语言。OGNL是一种EL表达式语言,用于设置和获取Java对象的属性,并且可以对列表进行投影选择以及执行lambda表达式。Ognl类提供了许多简便方法用于执行表达式的。Struts2发布的每个版本都会出现的新的高危可执行漏洞也是因为它使用了灵活的OGNL表达式。
公司后端采用Mybatis作为数据访问层,所使用版本为3.2.3。线上环境业务系统在运行过程中出现了一个令人困惑的异常, 该异常时而出现时而不出现,构造各种OGNL表达式为空等特殊情况均不会重现该异常。具体异常堆栈信息如下:

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” failed for object [1] [java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not access a member 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” failed for object [1] [java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not access a member 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)
at cn.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” failed for object [1] [java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not access a member of class java.util.Collections S i n g l e t o n L i s t w i t h m o d i f i e r s " p u b l i c " ] a t o r g . a p a c h e . i b a t i s . s c r i p t i n g . x m l t a g s . O g n l C a c h e . g e t V a l u e ( O g n l C a c h e . j a v a a t : 47 ) a t o r g . a p a c h e . i b a t i s . s c r i p t i n g . x m l t a g s . E x p r e s s i o n E v a l u a t o r . e v a l u a t e B o o l e a n ( E x p r e s s i o n E v a l u a t o r . j a v a : 29 ) a t o r g . a p a c h e . i b a t i s . s c r i p t i n g . x m l t a g s . I f S q l N o d e . a p p l y ( I f S q l N o d e . j a v a : 30 ) a t o r g . a p a c h e . i b a t i s . s c r i p t i n g . x m l t a g s . M i x e d S q l N o d e . a p p l y ( M i x e d S q l N o d e . j a v a : 29 ) a t o r g . a p a c h e . i b a t i s . s c r i p t i n g . x m l t a g s . T r i m S q l N o d e . a p p l y ( T r i m S q l N o d e . j a v a : 51 ) a t o r g . a p a c h e . i b a t i s . s c r i p t i n g . x m l t a g s . M i x e d S q l N o d e . a p p l y ( M i x e d S q l N o d e . j a v a : 29 ) a t o r g . a p a c h e . i b a t i s . s c r i p t i n g . x m l t a g s . D y n a m i c S q l S o u r c e . g e t B o u n d S q l ( D y n a m i c S q l S o u r c e . j a v a : 37 ) a t o r g . a p a c h e . i b a t i s . m a p p i n g . M a p p e d S t a t e m e n t . g e t B o u n d S q l ( M a p p e d S t a t e m e n t . j a v a : 275 ) a t o r g . a p a c h e . i b a t i s . e x e c u t o r . C a c h i n g E x e c u t o r . q u e r y ( C a c h i n g E x e c u t o r . j a v a : 79 ) a t o r g . a p a c h e . i b a t i s . s e s s i o n . d e f a u l t s . D e f a u l t S q l S e s s i o n . s e l e c t L i s t ( D e f a u l t S q l S e s s i o n . j a v a : 104 ) . . . 3 m o r e C a u s e d b y : o r g . a p a c h e . i b a t i s . o g n l . M e t h o d F a i l e d E x c e p t i o n : M e t h o d " s i z e " f a i l e d f o r o b j e c t [ 1 ] [ j a v a . l a n g . I l l e g a l A c c e s s E x c e p t i o n : C l a s s o r g . a p a c h e . i b a t i s . o g n l . O g n l R u n t i m e c a n n o t a c c e s s a m e m b e r o f c l a s s j a v a . u t i l . C o l l e c t i o n s SingletonList with modifiers "public"] at org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java at: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) ... 3 more Caused by: org.apache.ibatis.ognl.MethodFailedException: Method "size" failed for object [1] [java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not access a member of class java.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.CollectionsSingletonList 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)
… 12 more
List的size()方法明显是public为何还会出现不可访问的异常。该问题并不是每一次都会出现,经过多次尝试,该异常一直未在测试环境重现。该接口在完整调用链路中的出错次数占总调用次数的比率为0.01%,无意中联想到并发问题在周期性时间内往往是概率性发生。
编写模拟多线程环境并发读取公司列表测试代码:

<select id="getCompanysByIds"resultType=“cn.com.shaobingmm.Company”>
select *
from company


and id in
#{id}





多线程并发环境下的压测代码

String resource = “mybatis-config.xml”;
InputStream in = null;
try {
in = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
final List ids = Collections.singletonList(1L);
final SqlSession session = sqlSessionFactory.openSession();
final CountDownLatch mCountDownLatch = new CountDownLatch(1);
for (int i = 0; i < 50; i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
try {
mCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int k = 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();
            }
    }

上诉异常堆栈信息在并发环境下果然重现出现,根据异常信息代码执行至该行代码时发生异常:
Caused by: org.apache.ibatis.ognl.MethodFailedException: Method “size” failed for object [1] [java.lang.IllegalAccessException: Class org.apache.ibatis.ognl.OgnlRuntime can not access a member of class java.util.Collections$SingletonList with modifiers “public”]
at org.apache.ibatis.ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:837)

异常信息表明OgnlRuntime类不能够访问java.util.Collections的私有成员SingletonList。查看源代码发现能够抛出MethodFailedException异常可以锁定在invokeMethod方法内部。

public static Object callAppropriateMethod(OgnlContext context, Object source, Object target, String methodName, String propertyName, List methods, Object[] args) throws MethodFailedException {
Object reason = 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 = new StringBuffer();
            if(args != null) {
                int i = 0;

                for(int ilast = args.length - 1; i <= ilast; ++i) {
                    Object arg = args[i];
                    buffer.append(arg == null?NULL_STRING:arg.getClass().getName());
                    if(i < ilast) {
                        buffer.append(", ");
                    }
                }
            }

            throw new NoSuchMethodException(methodName + "(" + buffer + ")");
        }

        Object var14 = invokeMethod(target, e, actualArgs);
        return var14;
    } catch (NoSuchMethodException var21) {
        reason = var21;
    } catch (IllegalAccessException var22) {
        reason = var22;
    } catch (InvocationTargetException var23) {
        reason = var23.getTargetException();
    } finally {
        objectArrayPool.recycle(actualArgs);
    }

    throw new MethodFailedException(source, methodName, (Throwable)reason);
}

invokeMethod方法代码
public static Object invokeMethod(Object target, Method method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException {
boolean wasAccessible = true;
if(securityManager != null) {
try {
securityManager.checkPermission(getPermission(method));
} catch (SecurityException var6) {
throw new IllegalAccessException(“Method [” + method + “] cannot be accessed.”);
}
}

    if((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !(wasAccessible = method.isAccessible())) {
        method.setAccessible(true); (1)
    }

    Object result = method.invoke(target, argsArray); (3)
    if(!wasAccessible) {
        method.setAccessible(false); (2)
    }

    return result;
}

问题出现在method实际上是一个共享变量,也就是例子中的

public int java.util.Collections$SingletonList.size()
方法
当第一个线程t1至(1)行代码允许method方法可以被调用,第二个线程t2执行至(2)将method的方法设置为不可以访问。接着t1又开始执行到(3)行的时候就会发生该异常。这是一个很典型的同步问题。
Ognl2.7已经修复了该问题,因为ognl源码是直接打包内嵌在mybatis包中,mybatis3.3.0版本中也已经进行了修复升级。(划重点)
public static Object invokeMethod(Object target, Method method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException {
boolean syncInvoke = false;
boolean checkPermission = 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;
        }
    }

    boolean wasAccessible = true;
    Object result;
    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);
                    throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
                }
            }

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

            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);
                throw new IllegalAccessException("Method [" + method + "] cannot be accessed.");
            }
        }

        result = method.invoke(target, argsArray);
    }

    return result;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MyBatis中有多种方式可以实现分页,其中常用的两种方法是使用插件和使用RowBounds参数。 1. 插件方法MyBatis提供了一些插件,例如PageHelper分页插件,可以方便地实现分页功能。根据引用中的博客文章《MyBatis配置文件详解》,你可以在配置文件中添加相应的插件配置,并在需要分页的查询语句中使用插件提供的分页方法,即可实现数据的分页查询。 2. RowBounds参数方法:另一种实现分页的方法是使用RowBounds参数,根据引用中的说明,MyBatis会自动识别RowBounds参数,据此进行分页。你可以在查询方法中传入一个RowBounds对象,指定需要查询的起始行和查询的数量,MyBatis会根据这个参数进行分页查询。 以上是两种常用的MyBatis分页方法,你可以根据实际需求选择合适的方式来实现分页功能。如果需要更详细的使用方法和案例,可以参考引用和引用中提到的相关资料。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [MyBatis使用篇(十一)—— MyBatis实现分页](https://blog.csdn.net/weixin_36378917/article/details/85987287)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值