Why Does MyBatis Randomly Throw an IllegalAccessException? Uncovering the OGNL Concurrency Bug
This article explains a rare MyBatis exception caused by a concurrency issue in OGNL expression evaluation, shows the stack trace, reproduces the bug with multithreaded test code, analyzes the root cause in method accessibility, and notes that upgrading to Ognl 2.7 or MyBatis 3.3.0 resolves the problem.
MyBatis is an open‑source lightweight semi‑automatic ORM framework that simplifies mapping between object‑oriented applications and relational databases. It uses XML descriptors or annotations to bind objects to stored procedures or SQL statements, and its biggest advantage is decoupling application code from SQL by placing statements in XML mapper files.
OGNL (Object‑Graph Navigation Language) expressions are extensively used in MyBatis; their flexibility makes dynamic SQL very powerful. OGNL is an EL language for accessing and setting Java object properties, performing list projections, and executing lambda expressions. The Ognl class provides many convenient methods for evaluating expressions. However, each new version of Struts2 introduced high‑risk executable vulnerabilities because it also relies on flexible OGNL expressions.
In a company’s backend, MyBatis version 3.2.3 is used as the data‑access layer. During runtime, an intermittent exception occurs that cannot be reproduced by constructing empty OGNL expressions or other special cases. The stack trace is shown below:
### 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"]
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:23)
... (additional stack frames) ...
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)The exception occurs only intermittently, with a failure rate of about 0.01% of total calls, suggesting a probabilistic concurrency issue.
To reproduce the problem, a multithreaded test is written. The MyBatis mapper XML used in the test:
<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>The concurrent load test creates 50 threads, each invoking session.selectList("CompanyMapper.getCompanysByIds", ids) 100 times:
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 latch = new CountDownLatch(1);
for (int i = 0; i < 50; i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); }
for (int k = 0; k < 100; k++) {
session.selectList("CompanyMapper.getCompanysByIds", ids);
}
}
});
thread.start();
}
latch.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(); } }
}Running this test reproduces the exception. The root cause is that the Method object used by OGNL is a shared variable. In thread t1, the method is made accessible (line (1)). In thread t2, the method is set back to inaccessible (line (2)). When t1 later invokes the method at line (3), the IllegalAccessException is thrown – a classic synchronization problem.
OGNL 2.7 has fixed this issue, and the fix is included in MyBatis 3.3.0, which bundles the newer OGNL version.
Relevant source snippets from OGNL that illustrate the problem:
public static Object callAppropriateMethod(OgnlContext context, Object source, Object target, String methodName, String propertyName, List methods, Object[] args) throws MethodFailedException {
// ... method lookup and accessibility checks ...
Object var14 = invokeMethod(target, e, actualArgs);
return var14;
} public static Object invokeMethod(Object target, Method method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException {
boolean wasAccessible = true;
if (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
if (!(wasAccessible = method.isAccessible())) {
method.setAccessible(true);
}
}
Object result = method.invoke(target, argsArray);
if (!wasAccessible) {
method.setAccessible(false);
}
return result;
}In the original buggy version, the method object is shared without proper synchronization, leading to the race condition described above.
Upgrading to a version of OGNL that synchronizes access to the Method object (as shown in the later code block with synchronized blocks and caches) eliminates the issue.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
