Mybatis中判斷集合的size,可以用下面的方法來做。
1
2
3
4
5
6
|
< 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表達式為空等特殊情況均不會重現該異常。
具體異常堆棧信息如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
### 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%,無意中聯想到并發問題在周期性時間內往往是概率性發生。
編寫模擬多線程環境并發讀取公司列表測試代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<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異??梢枣i定在invokeMethod方法內部。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
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方法代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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實際上是一個共享變量,也就是例子中的
1
|
public int java.util.Collections$SingletonList.size() |
方法
當第一個線程t1至(1)行代碼允許method方法可以被調用,第二個線程t2執行至(2)將method的方法設置為不可以訪問。接著t1又開始執行到(3)行的時候就會發生該異常。這是一個很典型的同步問題。
Ognl2.7已經修復了該問題,因為ognl源碼是直接打包內嵌在mybatis包中,mybatis3.3.0版本中也已經進行了修復升級。(劃重點)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
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; } |
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持服務器之家。如有錯誤或未考慮完全的地方,望不吝賜教。
原文鏈接:https://blog.csdn.net/bigtree_3721/article/details/53760125