Mybatis 中如何判断集合的size

Mybatis中判断集合的size,可以用下面的方法来做。

<if test="null != staffCodeList and staffCodeList.size > 0">
and gui.USER_CODE not in
<foreach collection="staffCodeList" item="staffCode" open="(" separator="," close=")">
#{staffCode}
</foreach>
</if>

补充:警惕,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$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>

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

上诉异常堆栈信息在并发环境下果然重现出现,根据异常信息代码执行至该行代码时发生异常:

异常信息表明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;
 }

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。如有错误或未考虑完全的地方,望不吝赐教。

(0)

相关推荐

  • Mybatis查询多条记录并返回List集合的方法

    实体对象如下: /** 使用lobmok插件 */ @Getter @Setter @NoArgsConstructor @ToString @EqualsAndHashCode public class Vendor { private String vend_id; private String vend_name; private String vend_address; private String vend_city; private String vend_state; privat

  • MyBatis传入数组集合类并使用foreach遍历

    这篇文章主要介绍了MyBatis传入数组集合类并使用foreach遍历,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在mapper中传入数组或集合类,使用foreach标签遍历出其中的值与SQL语句拼接 JAVA dao层接口 public interface UserDao { public List<User> getUsersByCollection(Collection collection); } mapper文件 <sel

  • SpringMVC与Mybatis集合实现调用存储过程、事务控制实例

    在SSM框架中经常会用到调用数据库中的存储过程.以及事务控制,下面以保存某单据为例,介绍一下: 1.Oracle中存储过程代码如下(主要逻辑将单据编码自动加1,并将该单据编码返回): CREATE OR REPLACE PROCEDURE "UPDATE_DJBHZT" (p_GSID in varchar2, p_TBLNAME in varchar2, NewRecNo out Number) as begin update BHDJ set BHDJ02 = BHDJ02+1 w

  • SpringBoot集合Mybatis过程解析

    玩了两三天的SpringBoot,集成其他框架,就是配置.配置.再配置. 这次配置一下Mybatis: 第一步.pom.xml中引入Mybatis依赖: (注意:我的SpringBoot版本是2.0.3) <!-- mybatis依赖begin --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter<

  • Mybatis中Collection集合标签的使用详解

    mybatis简单的CURD就不用多说了,网上相关博客文档一大堆.分析一下Mybatis里面的collection聚集查询. 假设一个班级有多名学生为例,通过班级号查询出该班级的信息,和班级里面的所有学生的信息,一般的做法就是通过班级号把班级的信息查询出来,再通过班级ID号把该班级里面的所有学生查询出来,我们不用这种通用的方法 1.班级实体类可以定义为这样: import java.util.List; public class ClazzEntity { private int clazzID

  • Mybatis 中如何判断集合的size

    Mybatis中判断集合的size,可以用下面的方法来做. <if test="null != staffCodeList and staffCodeList.size > 0"> and gui.USER_CODE not in <foreach collection="staffCodeList" item="staffCode" open="(" separator="," c

  • mybatis if test条件判断语句中的判断问题分析

    目录 iftest条件判断语句中的判断问题 我在mybatis中定义的sql语句如下 或使用equals() mybatis中iftest判断大坑 使用Mybatis时,常常会判断属性是否为空 原因分析 if test条件判断语句中的判断问题 写这个主要是描述在mybatis中要注意的问题,很不幸,自己没注意,跳坑了. 我在mybatis中定义的sql语句如下 <if test="facilityOccupied != null and facilityOccupied != '' and

  • 基于mybatis中test条件中单引号双引号的问题

    目录 test条件中单引号双引号问题 具体原因 动态sql中test的一些问题 mybatis动态sql中OGNL中type=="1"和type='1'的区别 解决方案 test条件中单引号双引号问题 在mybatis中test判断条件中使用单引号会报错 通常使用双引号 通常test后的判断条件写在双引号内,但是当条件中判断使用字符串时应该如下方式开发 <when  test="channel ==null" > <when  test='chan

  • mybatis中<if>标签bool值类型为false判断方法

    昨天实现一个功能,根据文章的id或者别名查找文章. 起初采用mybatis的Example进行查询,对参数artName进行判断,如果是纯数字就byId查询,否则就by别名.由于查询文章的同时,需要关联查询文章分类标签,所以选择采用select语句映射的方式查询,但又不想写两个查询方法,就使用了mybatis中动态sql. /** * 查询文章 * @param artName id 或 别名 * @param byId 如果是 true 则按照id查询 * 否则 按照别名查询 * @retur

  • python中判断集合范围的方法小结

    我们在比较数值大小的时候,会使用一些比较符号来进行判断.在python集合中也有这样的比较,但有一点要注意的是,我们比较的是集合之间的包容性,而不是简单数值之间的大小比较,这点在文章的开头就进行明确,也是对于我们python初学者的提醒. 集合可以使用大于(>).小于(<).大于等于(>=).小于等于(<=).等于(==).不等于(!=)来判断某个集合是否完全包含于另一个集合,也可以使用子父集判断函数. 定义三个集合s1,s2,s3: >>> s1=set([1,

  • mybatis中的多重if 条件判断

    目录 mybatis多重if条件判断 要注意的是 mybatis常用判断语法(标签) 1.if判断 2.choose判断 mybatis 多重if 条件判断 要注意的是 当指定某种情况的时候,条件需要添加 toString() 方法 mybatis常用判断语法(标签) 作为java开发,我们常用的判断有if.switch语句,其实在MyBatis中也有对应的标签,用于动态生成sql语句. 1. if判断 <where>     <if test="null != statusC

  • mybatis中的多重if 条件判断

    目录 mybatis 多重if 条件判断 要注意的是 mybatis常用判断语法(标签) if判断 choose判断 mybatis 多重if 条件判断 要注意的是 当指定某种情况的时候,条件需要添加 toString() 方法 mybatis常用判断语法(标签) 作为java开发,我们常用的判断有if.switch语句,其实在MyBatis中也有对应的标签,用于动态生成sql语句. if判断 <where>     <if test="null != statusCode a

  • mybatis中的if test判断入参的值问题

    目录 mybatis if test判断入参的值 1.第一种判断方式 2.第二种判断方式 if test动态判断数字时出现的错误 mybatis中if test判断数字 mybatis if test判断入参的值 1.第一种判断方式 <if test=' requisition != null and requisition == "Y" '>    AND 表字段 = #{requisition} </if> 2.第二种判断方式 <if test=&qu

  • mybatis中 if-test 数字判断的坑及解决

    目录 if-test数字判断的坑 mybatis的test判断注意事项 if-test数字判断的坑 在项目中偶然发现一个判断数字的if没有起任何作用,代码如下 <if test="timeType !=null and timeType!='' and timeType == '3'">     AND     ... </if> 经过查询资料发现,mybatis是用OGNL表达式来解析的,在OGNL的表达式中,数字’3’会被解析成字符,java是强类型的,ch

随机推荐