Master MyBatis Interview: Advanced Tips, Performance Hacks, and Real-World Code
This article walks senior Java developers through MyBatis fundamentals, core components, result mapping, the trade‑offs between #{} and ${}, dynamic SQL, second‑level caching, batch inserts, plugin creation, thread‑safety concerns, and concrete interview‑question solutions, all illustrated with real code snippets and performance numbers.
Why MyBatis Matters for Senior Developers
Interviewers expect candidates to demonstrate deep knowledge of MyBatis internals—plugin mechanisms, caching strategies, and transaction management—rather than merely reciting API names. By framing answers around concrete project scenarios, candidates prove they can translate theory into performance‑critical solutions.
Fundamental Components
MyBatis consists of several core objects: SqlSessionFactory: creates SqlSession instances. SqlSession: executes SQL statements. Mapper: binds Java interfaces to XML or annotation‑based SQL. Executor: the engine that runs statements. StatementHandler: prepares the actual JDBC call. ResultSetHandler: maps result sets to Java objects.
Result Mapping Mechanics
MyBatis uses <resultMap> tags to define a one‑to‑one correspondence between database columns and Java fields, allowing fine‑grained control over type conversion and nested object construction.
#{} vs ${} – When to Use Which
In a data‑reporting scenario where table names are built dynamically (e.g., sales_2025_01), the author shows two approaches:
Using ${yearMonth} to concatenate the table name: SELECT * FROM sales_${yearMonth} Using #{yearMonth} would wrap the value in quotes, causing a syntax error.
The risk is SQL injection; the author recommends strict validation of the yearMonth format (e.g., yyyy_MM) before substitution.
Dynamic SQL in Practice
In a permission‑management system, the query must adapt to the user’s role. The author uses MyBatis’s <if> and <where> tags to conditionally add filters:
<select id="findUsers" resultType="User">
SELECT * FROM user
<where>
<if test="role != 'admin'">
AND department_id = #{deptId}
</if>
<if test="keyword != null">
AND name LIKE CONCAT('%', #{keyword}, '%')
</if>
</where>
</select>The <where> tag automatically removes stray AND/OR, preventing syntax errors.
Second‑Level Cache Strategies and Pitfalls
For an order‑query module that reads order status frequently but requires real‑time inventory data, the author enables a second‑level cache in OrderMapper.xml:
<cache eviction="LRU" flushInterval="60000"/>Queries that need fresh data are marked with useCache="false" to bypass the cache. A common pitfall is forgetting to clear the cache after updating related tables, which can be solved by adding flushCache="true" to the update statements.
Batch Insert Optimization
In an IoT project inserting 100,000 device logs per minute, the author switches to ExecutorType.BATCH and flushes every 1,000 rows:
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
DeviceLogMapper mapper = sqlSession.getMapper(DeviceLogMapper.class);
for (int i = 0; i < 100000; i++) {
mapper.insert(logList.get(i));
if (i % 1000 == 0) {
sqlSession.flushStatements(); // avoid OOM
}
}
sqlSession.commit();
}This reduces insertion time from 120 s to 15 s by minimizing transaction commits.
Plugin Mechanism for Auditing
To log every sensitive SQL operation in a financial system, the author implements a custom interceptor:
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class AuditLogPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1];
if (ms.getSqlCommandType() == SqlCommandType.UPDATE) {
logAudit(ms.getId(), parameter);
}
return invocation.proceed();
}
}The plugin records audit logs without modifying existing DAO code.
Thread‑Safety of SqlSession
Sharing a single SqlSession across threads leads to data overwrites (e.g., User A’s query results being overwritten by User B’s commit). The solution is to use ThreadLocal or Spring’s SqlSessionTemplate so each thread gets its own session.
Common Interview Questions and Solutions
How to solve MyBatis N+1 queries? In a blog system, the naïve approach issues a separate SELECT * FROM author WHERE id=#{authorId} for each article. The author replaces it with a single join:
<select id="selectArticles" resultMap="ArticleResult">
SELECT a.*, u.*
FROM article a
LEFT JOIN user u ON a.author_id = u.id
</select>
<resultMap id="ArticleResult" type="Article">
<association property="author" resultMap="UserResult"/>
</resultMap>This eliminates the N+1 problem by fetching authors in one query.
Key Takeaways for Senior Candidates
Adopt scenario‑driven thinking: choose dynamic SQL vs. caching based on business needs.
Show performance‑optimization awareness: batch operations, joins, and second‑level cache reduce DB load.
Demonstrate source‑code insight: plugin creation, executor internals, and thread‑local session handling.
Share pitfall‑avoidance experience: N+1 queries, cache consistency, and thread safety.
Java Architecture Stack
Dedicated to original, practical tech insights—from skill advancement to architecture, front‑end to back‑end, the full‑stack path, with Wei Ge guiding you.
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.
