Master MyBatis: From Basics to Advanced Features and Best Practices
This comprehensive guide explains what MyBatis is, compares it with Hibernate, details its core components, configuration, dynamic SQL, caching, plugins, pagination, and practical usage patterns, providing Java developers with clear examples, code snippets, and visual diagrams to master the framework efficiently.
Fundamentals
1. What is MyBatis?
First, a quick intro:
MyBatis is a semi‑ORM framework that wraps JDBC; developers write raw SQL and focus on the statements themselves, avoiding boilerplate such as driver loading, connection creation, and statement handling. This gives strict control over performance and high flexibility.
It can map POJOs to database records using XML or annotations, eliminating almost all JDBC code and manual parameter setting.
Drawbacks
Writing SQL can be labor‑intensive, especially with many fields or tables, requiring solid SQL skills.
SQL is tied to a specific database, reducing portability.
What is ORM?
ORM (Object‑Relational Mapping) is a technique that maps relational database tables to simple Java objects (POJOs) using metadata, allowing automatic persistence of objects.
Why is MyBatis called a semi‑automatic ORM tool? How does it differ from fully automatic tools?
Hibernate is a fully automatic ORM; it can retrieve associated objects directly from the object model.
MyBatis requires manual SQL for associations, thus it is semi‑automatic.
What shortcomings does JDBC have and how does MyBatis solve them?
Frequent connection creation/release wastes resources – solved by configuring a connection pool in mybatis-config.xml.
SQL embedded in code hurts maintainability – solved by placing SQL in mapper.xml.
Parameter handling is cumbersome – solved by MyBatis automatically mapping Java objects to SQL parameters.
Result‑set parsing is tedious – solved by MyBatis automatically mapping results to POJOs.
2. Differences between Hibernate and MyBatis
Both wrap JDBC and are persistence frameworks.
Mapping relationship
MyBatis: semi‑automatic mapping; simple configuration for multi‑table joins.
Hibernate: full‑automatic mapping; complex configuration for multi‑table joins.
SQL optimization and portability
Hibernate provides HQL, logging, caching, and cascade support, offering better DB‑independence but higher overhead.
MyBatis uses raw SQL, giving easy optimization but less portability.
When should you use MyBatis vs. Hibernate?
Hibernate: suitable for stable, medium‑size projects where rapid development is needed (e.g., office automation).
MyBatis: suitable for projects with frequent requirement changes and fast iteration (e.g., e‑commerce sites).
3. MyBatis usage process and lifecycle
The basic usage steps are:
1. Create SqlSessionFactory
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);2. Open SqlSession from the factory
SqlSession session = sqlSessionFactory.openSession();3. Execute database operations via SqlSession
Direct execution:
Blog blog = (Blog)session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);Using a mapper interface:
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);4. Commit transaction (for updates/deletes)
5. Close the session
MyBatis lifecycle?
The lifecycle covers the components introduced above:
SqlSessionFactoryBuilder : short‑lived, used only to build the factory.
SqlSessionFactory : application‑wide singleton, analogous to a connection pool.
SqlSession : not thread‑safe, lives per request or method.
Mapper : created from a SqlSession, lives within the session.
Integration with Spring can manage these components automatically.
4. How to pass multiple parameters in a mapper?
Method 1: Positional parameters
public User selectUser(String name, int deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user where user_name = #{0} and dept_id = #{1}
</select>Not recommended because the order is not explicit.
Method 2: @Param annotation
public User selectUser(@Param("userName") String name, @Param("deptId") int deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user where user_name = #{userName} and dept_id = #{deptId}
</select>Recommended for a small number of parameters.
Method 3: Map parameter
public User selectUser(Map<String, Object> params);
<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
select * from user where user_name = #{userName} and dept_id = #{deptId}
</select>Useful when the number of parameters varies.
Method 4: Java Bean parameter
public User selectUser(User user);
<select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
select * from user where user_name = #{userName} and dept_id = #{deptId}
</select>Clear and type‑safe; recommended when a fixed set of fields is needed.
5. What if entity field names differ from column names?
Use column aliases in the SQL to match entity property names.
<select id="getOrder" parameterType="int" resultType="com.jourwon.pojo.Order">
select order_id id, order_no orderno, order_price price from orders where order_id=#{id};
</select>Define a <resultMap> that maps columns to properties explicitly.
<select id="getOrder" parameterType="int" resultMap="orderResultMap"/>
<resultMap type="com.jourwon.pojo.Order" id="orderResultMap">
<id property="id" column="order_id"/>
<result property="orderno" column="order_no"/>
<result property="price" column="order_price"/>
</resultMap>6. Can MyBatis map Enum types?
Yes. Implement a custom TypeHandler that converts between the enum and the JDBC type.
7. Difference between #{} and ${}
#{}is a placeholder that is pre‑compiled; it prevents SQL injection. ${} performs string concatenation without pre‑compilation; it is vulnerable to injection.
8. How to write a fuzzy LIKE query?
Using CONCAT is recommended:
WHERE username LIKE CONCAT('%', #{question}, '%') <select id="listUserLikeUsername" resultType="com.jourwon.pojo.User">
<bind name="pattern" value="'%' + username + '%'"/>
select id,sex,age,username,password from person where username LIKE #{pattern}
</select>9. Can MyBatis perform one‑to‑one and one‑to‑many associations?
Yes, it supports one‑to‑one, one‑to‑many, many‑to‑many, and many‑to‑one mappings.
One‑to‑one ( <association> )
public class Order {
private Integer orderId;
private String orderDesc;
private Pay pay; // one‑to‑one
} <resultMap id="peopleResultMap" type="cn.fighter3.entity.Order">
<id property="orderId" column="order_id"/>
<result property="orderDesc" column="order_desc"/>
<association property="pay" javaType="cn.fighter3.entity.Pay">
<id column="payId" property="pay_id"/>
<result column="account" property="account"/>
</association>
</resultMap> <select id="getTeacher" resultMap="getTeacherMap" parameterType="int">
select * from order o left join pay p on o.order_id=p.order_id where o.order_id=#{orderId}
</select>One‑to‑many ( <collection> )
public class Category {
private int categoryId;
private String categoryName;
private List<Product> products;
} <select id="listCategory" resultMap="categoryBean">
select c.*, p.* from category_ c left join product_ p on c.id = p.cid
</select> <resultMap type="Category" id="categoryBean">
<id column="categoryId" property="category_id"/>
<result column="categoryName" property="category_name"/>
<collection property="products" ofType="Product">
<id column="product_id" property="productId"/>
<result column="productName" property="productName"/>
<result column="price" property="price"/>
</collection>
</resultMap>10. Does MyBatis support lazy loading? How does it work?
Yes. Lazy loading is enabled via lazyLoadingEnabled=true in the configuration.
It uses CGLIB to create proxy objects; when a getter is invoked, MyBatis intercepts the call, executes the necessary SQL, sets the property, and returns the value.
11. How to retrieve generated primary keys?
<insert id="insert" useGeneratedKeys="true" keyProperty="userId">
insert into user (user_name, user_password, create_time)
values (#{userName}, #{userPassword}, #{createTime, jdbcType=TIMESTAMP})
</insert>After execution, user.getId() contains the generated key.
12. Does MyBatis support dynamic SQL?
Yes. Tags such as <if>, <choose>, <when>, <otherwise>, <trim>, <where>, <set>, and <foreach> allow building SQL dynamically based on input parameters.
13. How to perform batch operations in MyBatis?
Method 1: Use <foreach> in an INSERT statement
<insert id="addEmpsBatch">
INSERT INTO emp (ename, gender, email, did)
VALUES
<foreach collection="emps" item="emp" separator=",">
(#{emp.eName}, #{emp.gender}, #{emp.email}, #{emp.dept.id})
</foreach>
</insert>Method 2: Use ExecutorType.BATCH
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
for (int i = 0; i < 1000; i++) {
mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0,5), "b", "1"));
}
session.commit();
session.close();14. MyBatis first‑level and second‑level cache
First‑level cache : Per‑session cache stored in a PerpetualCache HashMap; cleared when the session is closed or flushed.
Second‑level cache : Namespace‑level cache shared across sessions; can be backed by external stores like Ehcache. Disabled by default; enable by setting cacheEnabled=true and providing a serializable result type.
Principles
15. How does MyBatis work internally?
The workflow consists of two main steps: building the SqlSessionFactory and running a session.
Build Session Factory
Read configuration files (mybatis‑config.xml and mapper XMLs).
Initialize core components (type aliases, plugins, object factories, etc.).
Create a singleton Configuration object.
Instantiate DefaultSqlSessionFactory using the configuration.
Session Execution
Executor : Executes statements and manages caches.
StatementHandler : Prepares and runs the JDBC Statement.
ParameterHandler : Sets parameters on the prepared statement.
ResultSetHandler : Maps result sets to Java objects.
16. MyBatis functional architecture
API layer : Public interfaces used by developers.
Data processing layer : Handles SQL lookup, parsing, execution, and result mapping.
Infrastructure layer : Manages connections, transactions, configuration loading, and caching.
17. Why does a Mapper interface need no implementation?
MyBatis creates a dynamic proxy for the mapper interface. The proxy delegates method calls to a MapperProxy, which builds a MapperMethod and finally invokes the appropriate SqlSession operation.
18. What Executor types does MyBatis provide?
SimpleExecutor : Creates a new Statement for each operation.
ReuseExecutor : Reuses prepared statements based on the SQL key.
BatchExecutor : Accumulates statements for batch execution; suitable for bulk inserts/updates.
19. MyBatis plugin mechanism
How does a plugin work?
Plugins intercept the four core objects ( Executor, StatementHandler, ParameterHandler, ResultSetHandler) using JDK dynamic proxies. The Plugin class implements InvocationHandler and delegates to the interceptor's intercept method when a matched method is invoked.
To create a plugin, implement org.apache.ibatis.plugin.Interceptor, annotate with @Intercepts and @Signature, and configure it in mybatis-config.xml.
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("before…");
Object result = invocation.proceed();
System.out.println("after…");
return result;
}
}
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class MyInterceptor implements Interceptor { … } <plugins>
<plugin interceptor="com.example.MyInterceptor">
<property name="dbType" value="mysql"/>
</plugin>
</plugins>20. How does MyBatis handle pagination?
MyBatis provides the RowBounds object for in‑memory pagination, but physical pagination is usually achieved with a pagination plugin that intercepts the Executor.query method, rewrites the SQL according to the database dialect, and adds LIMIT/OFFSET clauses.
Example: original SELECT * FROM student becomes SELECT t.* FROM (SELECT * FROM student) t LIMIT 0, 10 after interception.
References
MyBatis interview questions (2020) – https://blog.csdn.net/ThinkWon/article/details/101292950
Official MyBatis documentation – https://mybatis.org/mybatis-3/zh/index.html
《深入浅出MyBatis基础原理与实战》
MyBatis caching mechanisms – https://tech.meituan.com/2018/01/19/mybatis-cache.html
《MyBatis从入门到精通》
If you found this article helpful, please like, share, and forward it to others. Thank you!
Follow the public account for more technical content
Previous popular articles
Redis distributed lock with Redisson – 15 Q&A
Zookeeper distributed lock with Curator – 11 Q&A
Circular dependencies and three‑level cache – interview essentials
LiteFlow rule engine deep dive – 28 diagrams
Thread pool mastery – 24 diagrams, 7000 words
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.
Sanyou's Java Diary
Passionate about technology, though not great at solving problems; eager to share, never tire of learning!
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.
