Backend Development 19 min read

Analyzing MyBatis Query Process and Fixing a Pre‑3.4.5 foreach Variable Bug

This article examines a MyBatis bug present in versions prior to 3.4.5 by reproducing the issue, walking through the complete query execution flow from configuration parsing to result handling, identifying the root cause in foreach variable binding, and presenting official fixes and practical work‑arounds.

JD Tech
JD Tech
JD Tech
Analyzing MyBatis Query Process and Fixing a Pre‑3.4.5 foreach Variable Bug

The article starts by reproducing a low‑version MyBatis bug (pre‑3.4.5) where a query using a foreach clause incorrectly binds an extra student_name parameter, leading to unexpected SQL like WHERE student_name IN (?, ?) AND student_name = ? .

Sample data preparation:

List<String> studentNames = new LinkedList<>();
studentNames.add("lct");
studentNames.add("lct2");
condition.setStudentNames(studentNames);

Mapper XML snippet that triggers the bug:

select * from student
AND student_name IN
#{studentName, jdbcType=VARCHAR}
AND student_name = #{studentName, jdbcType=VARCHAR}

The expected SQL is SELECT * FROM student WHERE student_name IN ('lct','lct2') , but the actual execution logs show an extra bound parameter for student_name , indicating that the foreach loop polluted the global context.

The article then dissects the full MyBatis query lifecycle:

Construction of SqlSessionFactory via SqlSessionFactoryBuilder and parsing of mybatis-config.xml .

Parsing of mapper files using XMLMapperBuilder , creation of MappedStatement objects.

Opening a SqlSession which creates an Executor (Simple, Reuse, or Batch) and possibly wraps it with a CachingExecutor .

Retrieving the mapper proxy via MapperRegistry#getMapper and invoking the mapped method.

Execution path inside MapperMethod#execute leading to DefaultSqlSession#selectList and finally CachingExecutor#query .

SQL building, parameter binding, and the role of DynamicSqlSource , ForEachSqlNode , and IfSqlNode in processing the dynamic foreach construct.

During the ForEachSqlNode#apply phase, both the loop item and an indexed version are bound to the context, unintentionally leaving a global studentName entry that satisfies the subsequent if test, causing the extra AND student_name = ? clause.

Official MyBatis release notes for version 3.4.5 contain a bug‑fix entry that isolates the foreach variable modifications from the global context. The fix introduces a separate context object to store local variables, preventing the leakage.

Two practical solutions are offered:

Upgrade MyBatis to version 3.4.5 or later.

If staying on an older version, ensure that the variable name used inside foreach does not clash with any outer‑scope variable.

The article also highlights the design patterns employed by MyBatis, such as Composite (ChooseSqlNode, IfSqlNode), Template Method (BaseExecutor, SimpleExecutor), Builder (SqlSessionFactoryBuilder, XMLConfigBuilder), Factory (SqlSessionFactory, MapperProxyFactory), and Proxy (MapperProxy, ConnectionLogger).

References and further reading links to the official MyBatis documentation and related GitHub pull‑request for the bug fix are provided.

backendJavaSQLMyBatisORMBugFix
JD Tech
Written by

JD Tech

Official JD technology sharing platform. All the cutting‑edge JD tech, innovative insights, and open‑source solutions you’re looking for, all in one place.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.