Mybatis 有坑,千万别踩!
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$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)... 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$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)... 12 more
List的size()方法明显是public为何还会出现不可访问的异常。该问题并不是每一次都会出现,经过多次尝试,该异常一直未在测试环境重现。
该接口在完整调用链路中的出错次数占总调用次数的比率为0.01%,无意中联想到并发问题在周期性时间内往往是概率性发生。
编写模拟多线程环境并发读取公司列表测试代码:
<mapper namespace="CompanyMapper"><select id="getCompanysByIds"resultType="cn.com.shaobingmm.Company">select *from company<where><if test="list != null and list.size() > 0">and id in<foreach collection="list" item="id" open="(" separator="," close=")">#{id}
</foreach></if></where></select>
</mapper>
多线程并发环境下的压测代码
String resource = "mybatis-config.xml";
InputStream in = null;
try {in = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);final List<Long> 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;
}
作者:蓬蒿
来源:zhuanlan.zhihu.com/p/30085658
猜你喜欢
1、GitHub 标星 3.2w!史上最全技术人员面试手册!FackBoo发起和总结
2、如何才能成为优秀的架构师?
3、从零开始搭建创业公司后台技术栈
4、程序员一般可以从什么平台接私活?
5、37岁程序员被裁,120天没找到工作,无奈去小公司,结果懵了...
6、滴滴业务中台构建实践,首次曝光
7、不认命,从10年流水线工人,到谷歌上班的程序媛,一位湖南妹子的励志故事
8、15张图看懂瞎忙和高效的区别!
Mybatis 有坑,千万别踩!相关推荐
- python培训班 北京-北京python培训机构那个好?这几个坑千万别踩
现在大大小小的python培训机构实在是太多了,很多想学习python的人面对这些培训机构不知道选哪家比较好. 作为美国主流大学最受欢迎的入门编程语言Python,诞生至今已经过了25个年头.相对于其 ...
- 小白如何自学编程? 看完这篇这些雷和坑千万不要踩!
小沐平时会接触很多人员,但印象最深的却是这一位.刚接触时感觉他很着急有很多顾虑,常常一个问题没说完又马上跳到另一个问题,他最纠结的问题是马上要毕业了,突然发现自己在学校里学的理论知识找工作根本用不上, ...
- 这十个坑千万别踩,毫无保留免费分享给你,做自媒体视频赚钱
新手做自媒体最怕的就是踩坑,这会让自己多走很多弯路. 想要做好自媒体的前提条件就是避免踩坑,不然再好的账号都会被你的失误操作给毁掉. 想要做自媒体赚钱,一定要耐心看完本期内容,可以让你少走很多弯路,记 ...
- Bootstrap的坑--千万别踩
Bootstrap前端框架 1.移动设备优先策略 基础的CSS是移动优先.优先设计更小的宽度 媒体查询.针对于平板电脑.台式电脑 渐近增强.随着屏幕大小的增加而添加元素 2.响应式布局 响应式布局:同 ...
- 注意了,这些数值计算的坑千万别踩!
作者 | 故里学Java 责编 | 张文 头图 | CSDN 下载自视觉中国 来源 | 故里学Java(ID:WLQ171223) 在我们日常工作中数值计算是不可避免的,特别是电商类系统中,这个问题 ...
- 美团三面挂了....这个坑千万别踩!
昨天听说个事儿,觉得挺可惜的. 一个读者,面试美团的前端,技术面都过了,HR面给挂了.简单聊了下,才发现是面试经验不足,不懂得HR问题背后的意思,导致回答问题不在点上,被HR套路了.(比如HR问了他未 ...
- 美团三面,挂了……这个坑千万别踩!
最近听说个事儿,挺可惜的. 一个读者,面试美团的前端,技术面都过了,HR面给挂了.简单聊了下,才发现是面试经验不足,不懂得HR问题背后的意思,导致回答问题不在点上,被HR套路了.(比如HR问了他未来的 ...
- 草根创业,这三大坑千万别踩
创业一词,都快被大家说烂了,听的人也就差耳朵没起茧了.连个宝妈在朋友圈卖个三无产品都自称自己是在创业.交了点费用去到一个群里帮别人刷了几个单,也可以理直气壮的跟别人说自己在创业.还不忘群发消息跟亲朋好 ...
- MyBatis 的这些坑你有踩过吗?
点击蓝色"程序猿DD"关注我哟 加个"星标",不忘签到哦 来源:http://t.cn/EiVxHRU 作为一个高级工程师,你真的了解框架源码吗? 大多数开发者 ...
- 这些Java8官方挖的坑,你踩过几个?
导读:系统启动异常日志竟然被JDK吞噬无法定位?同样的加密方法,竟然出现部分数据解密失败?往List里面添加数据竟然提示不支持?日期明明间隔1年却输出1天,难不成这是天上人间?1582年神秘消失的10 ...
最新文章
- 基于DQN强化学习训练一个超级玛丽
- 找出不超过40亿个给定整数的整数
- pycharm 黄色(黄字)高亮警告 Default argument value is mutable 原因及解决办法(mutable 可变对象与 immutable不可变对象)
- 《剑指offer》数组中重复的数字
- 职场十个方法 让专业气质成为你的符号!
- 信息学奥赛一本通(1411:区间内的真素数)
- STM32工作笔记0097---OEM厂是什么意思
- [转贴]What's the Scroll Lock key on my computer for?
- Set static ip for ubuntu
- python在input输入数字为何是str_Python基础笔记:input()输入与数据类型转换
- Python学习笔记-2017.5.4thon学习笔记-2017.5.22
- 跨网段共享服务器文件夹,跨网段文件共享
- OIer__ZLY__OI计划
- 温州话的歌曲也很好听
- mysql在子查询中使用自定义变量和条件语句实现函数效果的查询语句
- 20210729-Codeforces Round #735 (Div. 2)
- 什么是项目风险?如何做好项目风险管理
- 献给还在加班的你:摸鱼一时爽,一直摸鱼一直爽~
- 飞塔防火墙之ACL配置
- 跟朋友合伙创业股权怎么分配
热门文章
- 我搭的神经网络不work该怎么办!看看这11条新手最容易犯的错误
- Azure Active Directory密码同步问题
- js中如何判断按钮是否被点击了
- 如何设置蓝牙音频设备与Mac配合使用呢?
- ConceptDraw Office for mac(跨平台图表办公程序)
- 如何在Mac电脑上自定义Spotlight的搜索结果?
- 如何使用Aiseesoft iPhone Ringtone Maker for Mac在Mac上制作铃声
- ecplise tomcat忽然出现404
- [原创] 数据库小工具 (BatchESQL.exe)
- 负margin使用注意的一个问题