Notes on the MyBatis 3.5 Source Code: Executors, Dynamic SQL, and Caching Boundaries
Baseline: MyBatis 3.5.x, Spring Boot 3, Java 17+.
1. From SqlSession to JDBC
The typical call chain is:
SqlSession -> Executor -> StatementHandler -> JDBC Statement
-> ResultSetHandler
SqlSession acts as a facade, while Executor handles SQL execution, transactions, and caching. MappedStatement describes the mapped SQL, StatementHandler handles parameter binding and execution, and ResultSetHandler constructs the result objects.
2. Dynamic SQL and Parameters
XML dynamic nodes are ultimately assembled into a SqlSource. #{} generates a parameter mapping and uses a PreparedStatement; ${} performs string substitution and may only be used for allowlisted table names, column names, and sort fields.
OGNL evaluates some dynamic expressions, but its expression capabilities do not define the security boundary. Never allow untrusted input to control expressions or SQL structure.
3. Executor
SimpleExecutor creates a new Statement for each execution; ReuseExecutor reuses Statements; BatchExecutor defers updates for batch processing; CachingExecutor decorates another Executor to provide a namespace-level second-level cache.
The choice of Executor should account for transactions, failure-retry behavior, memory usage, and JDBC driver behavior. BatchExecutor is not necessarily faster just because it has been enabled.
4. First- and Second-Level Caches
The first-level cache usually lives within the SqlSession/Executor lifecycle, so long-lived reuse may return stale data. The second-level cache is namespace-scoped and must account for serialization, transaction commits, and invalidation consistency. Second-level caching should not be enabled by default in scenarios that require strong consistency across instances.
5. How to Read the Source Code
Start by following the Mapper proxy, then trace MappedStatement registration, the Executor decorator chain, ParameterMapping, TypeHandler, and ResultMap. When upgrading, rely on the source code for the exact version you have pinned, and do not treat internal fields as a stable API.