How MyBatis Executes Queries and Fixes a Pre‑3.4.5 Bug

This article walks through MyBatis's complete query lifecycle—from configuration parsing, SqlSessionFactory creation, and mapper loading to the actual SELECT execution—while reproducing a pre‑3.4.5 bug caused by foreach variable leakage and presenting the official fix and upgrade recommendations.

JD Cloud Developers
JD Cloud Developers
JD Cloud Developers
How MyBatis Executes Queries and Fixes a Pre‑3.4.5 Bug

Introduction

The article starts with a low‑version MyBatis bug (versions before 3.4.5) and uses it to dissect the entire MyBatis query process, helping readers understand how MyBatis builds and executes a query from configuration to result.

Problem Description

A simple mapper method queryStudents is used to query the student table with a list of names. Expected SQL is SELECT * FROM student WHERE student_name IN ('lct','lct2'), but the actual execution adds an extra AND student_name = ? clause, revealing that the foreach variable leaks into the outer scope.

MyBatis Architecture

Four Layers

Interface layer : Direct interaction with the database.

Data processing layer : Builds dynamic SQL and wraps query results into List<E>.

Framework support layer : Provides transaction management, connection pooling, caching, and SQL configuration.

Bootstrap layer : Starts MyBatis via XML configuration or Java API.

Four Core Objects

ParameterHandler : Sets parameters for prepared statements.

ResultSetHandler : Processes the result set returned by the database.

StatementHandler : Handles SQL preparation and parameter binding.

Executor : Executes CRUD operations.

Source‑Code Walkthrough of a Query

Data Preparation

CREATE TABLE `student` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `student_name` varchar(255) NULL DEFAULT NULL,
  `age` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1;

INSERT INTO `student` VALUES (1,'lct',1);
INSERT INTO `student` VALUES (2,'lct2',2);

Mapper XML

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mybatis.StudentDao">
  <resultMap id="resultMap" type="mybatis.Student">
    <id column="id" property="id" jdbcType="BIGINT" />
    <result column="student_name" property="studentName" jdbcType="VARCHAR" />
    <result column="age" property="age" jdbcType="INTEGER" />
  </resultMap>
  <select id="queryStudents" parameterType="mybatis.StudentCondition" resultMap="resultMap">
    SELECT * FROM student
    <where>
      <if test="studentNames != null and studentNames.size > 0">
        AND student_name IN
        <foreach collection="studentNames" item="studentName" open="(" separator="," close=")">
          #{studentName, jdbcType=VARCHAR}
        </foreach>
      </if>
      <if test="studentName != null and studentName != ''">
        AND student_name = #{studentName, jdbcType=VARCHAR}
      </if>
    </where>
  </select>
</mapper>

Java Code

public static void main(String[] args) throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    StudentDao mapper = sqlSession.getMapper(StudentDao.class);
    StudentCondition condition = new StudentCondition();
    List<String> studentNames = new LinkedList<>();
    studentNames.add("lct");
    studentNames.add("lct2");
    condition.setStudentNames(studentNames);
    List<Student> students = mapper.queryStudents(condition);
}

Query Process Analysis

The process can be divided into several stages:

SqlSessionFactory construction : SqlSessionFactoryBuilder.build() creates an XMLConfigBuilder to parse mybatis-config.xml, builds the Configuration object, and registers mappers.

SqlSession creation : sqlSessionFactory.openSession() creates a DefaultSqlSession with a Transaction and an Executor (SimpleExecutor by default, wrapped by CachingExecutor if caching is enabled).

Mapper retrieval : sqlSession.getMapper(StudentDao.class) obtains a proxy instance from MapperRegistry.

Method invocation : The proxy calls MapperMethod.execute(), which routes to SqlSession.selectList() for SELECT statements.

Statement preparation : MappedStatement is fetched from the configuration, BoundSql is generated by DynamicSqlSource, and the StatementHandler (PreparedStatementHandler) binds parameters.

SQL execution : The prepared statement is executed, the result set is processed by ResultSetHandler, and the final list of Student objects is returned.

During the foreach processing, the variable studentName was incorrectly added to the global bindings, causing the extra AND student_name = ? clause. The bug was fixed in MyBatis 3.4.5 by isolating foreach’s local variables from the global context (see GitHub pull request #966).

Solution

Upgrade MyBatis to version 3.4.5 or later.

If staying on an older version, ensure that variable names used inside <foreach> do not clash with outer‑scope variables.

References

Official MyBatis documentation: https://mybatis.org/mybatis-3/zh/index.html

MyBatis SqlSessionFactory build method diagram
MyBatis SqlSessionFactory build method diagram
MyBatis query process flowchart
MyBatis query process flowchart
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendDebuggingJavaSQLMyBatisORM
JD Cloud Developers
Written by

JD Cloud Developers

JD Cloud Developers (Developer of JD Technology) is a JD Technology Group platform offering technical sharing and communication for AI, cloud computing, IoT and related developers. It publishes JD product technical information, industry content, and tech event news. Embrace technology and partner with developers to envision the future.

0 followers
Reader feedback

How this landed with the community

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.