How MyBatis Locates Mapper SQL: A Full Call‑Chain Trace
The article explains MyBatis’s three‑stage process—building an in‑memory mapping dictionary at startup, using JDK dynamic proxies to bind interface methods to that dictionary, and the runtime execution flow that translates a mapper call into JDBC operations, highlighting design choices such as builder chains, pre‑loading, and composition over inheritance.
In everyday development MyBatis‑Plus is often used, but the core of MyBatis remains the same. This article walks through several key knowledge points of MyBatis, focusing on how a mapper method finds its corresponding SQL.
Startup Phase: Building the Mapping Dictionary
When the application starts, MyBatis reads the global configuration file and loads each mapper XML file. Every SQL tag (select, insert, update, delete) is parsed, and its SQL text, parameter type, return type, and dynamic fragments are packaged into a metadata object called MappedStatement. All MappedStatement objects are stored in an in‑memory Map whose key format is "fully‑qualified‑interface-name.methodName"; for example, the key for com.example.UserMapper.selectById is exactly that string. This construction happens only once, and all later SQL lookups rely on this map without scanning the XML files again.
The construction follows a Builder chain: session‑factory builder → global‑config parser → mapper‑file parser → single‑SQL parser, each layer handling a single responsibility and finally producing a Configuration object that centrally manages all metadata.
Dynamic Proxy: Interface Becomes Implementation
When a mapper interface is injected and its method is called, MyBatis does not provide a concrete implementation class. Instead, it returns a JDK dynamic‑proxy object that intercepts every method call. The proxy maintains a method‑level cache; on the first invocation it composes the key (interface name + method name), looks up the corresponding MappedStatement in the startup map, and caches the mapping. Subsequent calls hit the cache directly.
The proxy’s work is essentially converting a Java method call into a map‑lookup. For example, calling userMapper.selectById(1) causes the proxy to build the key com.example.UserMapper.selectById, which matches the key stored during startup, linking the call to the correct SQL.
MyBatis chooses JDK dynamic proxies over CGLIB because mapper interfaces are already interfaces; no byte‑code enhancement is needed, and the native proxy is sufficient. The method cache uses a thread‑safe ConcurrentHashMap, avoiding redundant construction.
Runtime Phase: Full Call Chain
After the proxy resolves the key, the call is dispatched to SqlSession based on the SQL type (SELECT → selectOne or selectList, INSERT → insert, etc.). SqlSession delegates to an Executor, which manages first‑level caching and transaction handling. The executor forwards the request to a StatementHandler, which builds a JDBC PreparedStatement. A ParameterHandler sets Java parameters into the statement, and after execution a ResultSetHandler maps the ResultSet back to Java objects.
Importantly, the entire runtime call chain never reads XML files from disk; all SQL locations are resolved via the in‑memory map, making the lookup an O(1) operation. This explains why changes to mapper XML require an application restart.
The four runtime handlers (Executor, StatementHandler, ParameterHandler, ResultSetHandler) are composed rather than inherited. MyBatis provides multiple executor implementations (simple, batch, reusable); swapping one changes behavior without altering the core flow, illustrating the "composition over inheritance" principle.
Design Thoughts in the Source Code
Two main design lines emerge:
Decoupling via two independent lines : SQL text is registered to the in‑memory dictionary, while Java code accesses the dictionary through dynamic proxies. Modifying SQL does not affect Java code and vice‑versa, a pattern useful in rule engines or workflow orchestration.
Space‑for‑time trade‑off : Loading all metadata into memory at startup consumes extra memory and startup time but yields constant‑time lookups at runtime. This mirrors strategies used by Spring’s bean definition registry and servlet routing tables.
Composition over inheritance : The runtime handlers are linked by composition, allowing each layer to be replaced independently (e.g., custom executor or result‑set handler) without touching the core logic.
Conclusion
Understanding a framework’s internals is valuable not because of memorizing class names, but because it reveals the design trade‑offs the authors made—why they chose dynamic proxies, pre‑loading, and composition. Those insights guide developers when facing similar architectural problems.
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.
samdeepthink
Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.
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.
