点击上方“方志朋”,选择“设为星标”

回复”666“获取新整理的面试资料

来源:http://h5ip.cn/aJgJ

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

热门内容:   

  

  • 请停止学习框架

  • IntelliJ IDEA 2019.3这回真的要飞起来了,新特性抢先看!

  • 想不到!面试官问我:Redis 内存满了怎么办?

  • 用了这么久 IDEA,你竟然不知道有个功能叫自动补全!

  • 5 门可能衰落的编程语言

  • 优秀的 Java 项目代码都是如何分层的?

最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

明天见(。・ω・。)ノ♡

警惕,MyBatis的size()方法竟然有坑!相关推荐

  1. 警惕 | 警惕,mybatis的size()方法竟然有坑!

    Hi ! 我是小小,今天我们又见面了,今日的主要内容是MyBatis的size方法使用的主要的注意事项. 前言 MyBatis 是一个开源的轻量级的半自动化的 ORM 框架,用于面向对象和关系型数据库 ...

  2. 用mybatis的generator自动生成代码--坑我都走了一遍,后面的同学别踩了

    先说什么是mybatis-generator? mybatis-geneator是一款mybatis自动代码生成工具,可以通过配置,快速生成mapper和xml文件. 步骤一:在pom文件中添加插件配 ...

  3. java queue size_Java中的PriorityQueue size() 方法 - Break易站

    Java.util.PriorityQueue.size()方法用于获取PriorityQueue的大小或PriorityQueue中存在的元素数. 句法: Priority_Queue.size() ...

  4. 数组实现栈,实现push,pop,size方法 -- 面试算法

    思考: 有几个关键的地方, 第一,数组的话,如果我们一直push,需要扩容. 第二,因为产生了扩容所以当前数组的大小,不代表存放的数据多少,所以我们需要一个count 表示当前的容量. 第三,扩容之后 ...

  5. python文件读取方法read(size)的含义是_Python基于read(size)方法读取超大文件

    pyhon读取文件很方便,但是,如果文件很大,而且还是一行文件,那就蛋疼了. 不过还好有read(size)方法,这个方法就是每次读取size大小的数据到内存中 下面来个示例 def readline ...

  6. Java List.size()方法:返回列表中元素的个数(亲测)

    Java 集合类中的 List.size() 方法以 int 形式返回列表中元素的个数. 语法: size() 返回列表中元素的个数.如果列表中元素的个数超过 2 147 483 647,则返回 2 ...

  7. java map大小_Java中HashMap的size()方法: HashMap.size() - Break易站

    Java中的HashMap HashMap类的java.util.HashMap.size()方法用于获取哈希映射HashMap中映射的大小,该映射引用Map中键值对或映射的数量. 句法: Hash_ ...

  8. Java 中array.size()_Java ArrayDeque size()方法与示例

    ArrayDeque类size()方法size()方法在java.lang包中可用. size()方法用于返回存储在此双端队列中的大小(元素数). size()方法是一个非静态方法,只能通过类对象访问 ...

  9. java size_Java length() 方法,length 属性和 size() 方法的区别

    豆妮的小奴隶 length() 方法,length 属性和 size() 方法的区别: 1.length()方法是针对字符串来说的,要求一个字符串的长度就要用到它的length()方法: 2.leng ...

最新文章

  1. 课程表美化 css_通过这门11小时的免费课程学习HTML和CSS
  2. sqlplus command
  3. python编程django项目中ModuleNotFoundError: No module named ‘django.core.urlresolvers‘解决方法
  4. vue模板html,VueJS模板
  5. Python利用matplotlib.animation和matplotlib.pyplot和ffmpeg录制动画并保存为MP4文件
  6. flask 检测post是否为空_使用Flask搭建一个校园论坛-4
  7. 25 岁的 JavaScript 都经历了什么?
  8. mysql适配器_MySQL适配器之PyMySQL的详细介绍
  9. MyBatisPlus:获取SQL传递过来的参数
  10. 那些年我们一起追寻过的考研(华科计算机)
  11. DXP导入网络报表的方法
  12. 商业银行的起源与经营模式-分业经营与混业经营
  13. 开源硕士毕业论文算法
  14. 微信环境中扫描二维码下载APP(APK)文件的解决方案-自动跳转到手机默认浏览器(AppStore)打开
  15. 光滑性准则(Smoothness Rule) 递推方程(Recursive Equation)
  16. android user-agent iso-8859-1,微信大众,平台消息接口开辟(31)微信浏览器HTTP_USER_AGENT断定...
  17. vue3 动态传值给子组件
  18. Marvolo Gaunt's Ring(类似于dp的做法)
  19. Jmeter模拟上传图片
  20. 802.11无线权威指南读书笔记(12)直接序列物理层DSSS与HR/DSSS(802.11b)

热门文章

  1. 【bzoj3150】 cqoi2013—新Nim游戏
  2. Node.js express 之mongoose 从异步回调函数返回值,类似于同步
  3. 越南一难倒博士的趣味数学题
  4. Ubuntu安装Flash视频插件
  5. UVA 10954 Add All
  6. GridView 始终显示 Pager 分页行的一种方法
  7. 【青少年编程(第26周)】一下子多了很多事!
  8. redis删除过期key的算法_面试官别再问我Redis内存满了该怎么办了
  9. 携手中国电信、中国联通,华为正式发布首个5G超级刀片站 A+P 2.0天线商用网络
  10. Python分析101位《创造营2020》小姐姐,谁才是你心中的颜值担当?