Mybatis中Size()方法的作用是什么

75次阅读
没有评论

共计 10140 个字符,预计需要花费 26 分钟才能阅读完成。

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

前言

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

其方法代码

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

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

public int java.util.Collections$SingletonList.size()

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

升级版本

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

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

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

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-03发表,共计10140字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)