How to Determine the size of a Set in Mybatis

  • 2021-08-12 02:54:30
  • OfStack

In Mybatis, the size of the set can be judged by the following method.


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

Add: Be wary, there are pits in size () method of MyBatis!

Mybatis is an open source lightweight semi-automated ORM framework that makes mapping object-oriented applications to relational databases easier.

MyBatis combines objects with stored procedures or SQL statements using xml descriptors or annotations. The greatest advantage of Mybatis is that the application is decoupled from Sql, and the sql statement is written in the Xml Mapper file.

OGNL expressions are widely used in Mybatis, and the flexibility of its expressions makes dynamic Sql very powerful.

OGNL is the abbreviation of Object-Graph Navigation Language and stands for the Object Graph Navigation Language.

OGNL is an EL expression language, which is used to set and get the properties of Java objects, and can make projection selection on lists and execute lambda expressions.

The Ognl class provides many simple methods for executing the. The new high-risk executable vulnerability that occurs with each release of Struts 2 is also due to its use of flexible OGNL expressions.

The back end of the company uses Mybatis as the data access layer, and the version used is 3.2. 3.

There is a puzzling exception in the running process of the online environment business system, which sometimes appears and sometimes does not appear, and the exception will not be reproduced in special cases such as the construction of various OGNL expressions being empty.

The specific exception stack information is as follows:


### 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

The size () method of List is obviously why public still has an inaccessible exception. This problem does not occur every time, and after many attempts, the exception 1 did not reappear in the test environment.

The ratio of the number of errors in the complete call link to the total number of calls is 0.01%, and it is inadvertently thought that concurrency problems often occur probabilistically in periodic time.

Write test code to simulate concurrent reading of company list in multithreaded environment:


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

Pressure measurement code in multithreaded concurrent environment

Appeal exception stack information reappears in concurrent environment, and an exception occurs when the code executes to this line of code according to the exception information:

The exception message indicates that the OgnlRuntime class cannot access SingletonList, a private member of java. util. Collections.

Looking at the source code, it is found that MethodFailedException exceptions that can be thrown can be locked inside the invokeMethod method.


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 method code


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

The problem is that method is actually a shared variable, which is in the example


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

Method

When the first thread t1 to (1) allows the method method to be called, the second thread t2 executes to (2) to set the method method to inaccessible. This exception will occur when t1 starts executing again to line (3). This is a typical synchronization problem.

Ognl 2.7 has fixed this problem because the ognl source code is directly packaged and embedded in the mybatis package, and the mybatis 3.3. 0 version has also been fixed and upgraded. (Focus)


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

Related articles: