MyBatis 从入门到精通:一篇就够的实战指南(Java)
目标读者:Java 初/中/高级开发、准备面试的同学、正在从 JPA 切换到 MyBatis 的团队。
文章结构:概念 → 快速上手 → 核心原理 → 高级特性 → 性能调优 → 常见问题 → 最佳实践 → 面试题。
目录
- [1. 什么是 MyBatis?为什么选择它](#1. 什么是 MyBatis?为什么选择它)
- [2. 快速开始(Spring Boot 版)](#2. 快速开始(Spring Boot 版))
- [3. 核心概念与运行机制](#3. 核心概念与运行机制)
- [4. 映射语法与动态 SQL](#4. 映射语法与动态 SQL)
- [5. 类型处理器 TypeHandler(含自定义 JSON/枚举示例)](#5. 类型处理器 TypeHandler(含自定义 JSON/枚举示例))
- [6. Spring 事务与多数据源整合](#6. Spring 事务与多数据源整合)
- [7. 分页:LIMIT、RowBounds 与 PageHelper](#7. 分页:LIMIT、RowBounds 与 PageHelper)
- [8. 缓存机制:一级/二级缓存](#8. 缓存机制:一级/二级缓存)
- [9. 性能优化与批处理](#9. 性能优化与批处理)
- [10. 插件(拦截器)机制](#10. 插件(拦截器)机制)
- [11. 代码生成:MyBatis Generator 与替代方案](#11. 代码生成:MyBatis Generator 与替代方案)
- [12. 常见问题排查(Troubleshooting)](#12. 常见问题排查(Troubleshooting))
- [13. 工程落地与包结构建议](#13. 工程落地与包结构建议)
- [14. 面试高频题与答题要点](#14. 面试高频题与答题要点)
- [15. 纯 MyBatis(非 Spring)最小可运行示例](#15. 纯 MyBatis(非 Spring)最小可运行示例)
- [16. 参考资料与学习路径](#16. 参考资料与学习路径)
1. 什么是 MyBatis?为什么选择它
MyBatis 是一个轻量级持久层框架,核心价值是:
- SQL 自由:你自己写 SQL,掌控查询、索引、复杂联表、性能。
- ORM 适度:相比 JPA/Hibernate,MyBatis 不做全自动映射,避免"黑盒",排查更直观。
- 可扩展:插件拦截器、TypeHandler、动态 SQL、二级缓存,足够灵活。
什么时候优先选 MyBatis?
- 报表/复杂查询/存储过程多、对 SQL 可控性要求强。
- 性能极致场景:需要自己精细化调优 SQL。
- 需要多租户/审计/数据权限等"跨切面"能力(拦截器很好用)。
什么时候考虑 JPA?
- 以 CRUD 为主,领域模型聚合清晰,希望更少 SQL(但也要接受性能可预期性降低)。
2. 快速开始(Spring Boot 版)
目标:5 分钟跑通一个
User
的增删改查。
2.1 数据表
sql
CREATE TABLE `user` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL,
`email` VARCHAR(100) NOT NULL,
`status` TINYINT NOT NULL DEFAULT 1, -- 1:启用 0:停用
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2 依赖(Maven)
xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
2.3 application.yml
yaml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username: root
password: root
mvc:
pathmatch:
matching-strategy: ant_path_matcher
mybatis:
mapper-locations: classpath:mapper/**/*.xml
type-aliases-package: com.example.demo.domain
configuration:
map-underscore-to-camel-case: true
default-fetch-size: 100
default-statement-timeout: 10
2.4 实体类
java
package com.example.demo.domain;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Long id;
private String username;
private String email;
private Integer status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
2.5 Mapper 接口
java
package com.example.demo.mapper;
import com.example.demo.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserMapper {
int insert(User user);
int updateById(User user);
int deleteById(@Param("id") Long id);
User selectById(@Param("id") Long id);
List<User> selectByUsernameLike(@Param("keyword") String keyword);
}
2.6 Mapper XML(resources/mapper/UserMapper.xml)
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="com.example.demo.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.example.demo.domain.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="email" property="email"/>
<result column="status" property="status"/>
<result column="created_at" property="createdAt"/>
<result column="updated_at" property="updatedAt"/>
</resultMap>
<sql id="Base_Column_List">
id, username, email, status, created_at, updated_at
</sql>
<insert id="insert" parameterType="com.example.demo.domain.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (username, email, status)
VALUES (#{username}, #{email}, #{status})
</insert>
<update id="updateById" parameterType="com.example.demo.domain.User">
UPDATE user
<set>
<if test="username != null">username = #{username},</if>
<if test="email != null">email = #{email},</if>
<if test="status != null">status = #{status},</if>
</set>
WHERE id = #{id}
</update>
<delete id="deleteById" parameterType="long">
DELETE FROM user WHERE id = #{id}
</delete>
<select id="selectById" parameterType="long" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user WHERE id = #{id}
</select>
<select id="selectByUsernameLike" parameterType="string" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
<where>
<if test="keyword != null and keyword != ''">
AND username LIKE CONCAT('%', #{keyword}, '%')
</if>
</where>
ORDER BY id DESC
LIMIT 100
</select>
</mapper>
2.7 Service & Controller(示例)
java
package com.example.demo.service;
import com.example.demo.domain.User;
import com.example.demo.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserMapper userMapper;
@Transactional
public Long create(User user) {
userMapper.insert(user);
return user.getId();
}
@Transactional
public void update(User user) {
userMapper.updateById(user);
}
public User get(Long id) {
return userMapper.selectById(id);
}
public List<User> search(String keyword) {
return userMapper.selectByUsernameLike(keyword);
}
}
3. 核心概念与运行机制
MyBatis 运行时关键对象:
-
SqlSessionFactory
:会话工厂(线程安全),由配置构建,创建SqlSession
。 -
SqlSession
:一次数据库会话(非线程安全),管理 Statement/事务。 -
Mapper
接口与代理 :MyBatis 使用MapperProxy
动态代理接口方法 →MappedStatement
→ 执行器(Executor
)。 -
执行器
Executor
:负责缓存、SQL 执行,类型有SIMPLE
、REUSE
、BATCH
。 -
处理器:
ParameterHandler
(参数预处理),ResultSetHandler
(结果映射),TypeHandler
(Java ↔ JDBC 类型转换)。
执行流程(简化):
Mapper
方法被调用 → 动态代理找到MappedStatement
(由 XML/注解解析)。ParameterHandler
绑定#{}
参数,生成PreparedStatement
。Executor
执行,命中缓存则返回,否则访问数据库。ResultSetHandler
将ResultSet
映射为对象(resultMap
/自动映射)。
4. 映射语法与动态 SQL
4.1 参数绑定:#{}
vs ${}
#{}
:预编译占位符,安全(防注入),自动做类型转换。${}
:字符串拼接,谨慎使用(表名/列名动态时),注意 SQL 注入风险。
4.2 resultType
vs resultMap
resultType
:直接映射到类/基本类型,适合简单查询。resultMap
:可定义<id/>
、<result/>
、<association/>
(一对一)、<collection/>
(一对多)、<discriminator/>
(鉴别器),适合复杂对象图。
4.3 关联映射:一对一 / 一对多
xml
<!-- 一对一:User 包含 Profile -->
<resultMap id="UserWithProfile" type="User">
<id column="u_id" property="id"/>
<result column="username" property="username"/>
<association property="profile" javaType="Profile" columnPrefix="p_">
<id column="p_id" property="id"/>
<result column="p_phone" property="phone"/>
</association>
</resultMap>
<select id="selectUserWithProfile" resultMap="UserWithProfile">
SELECT u.id AS u_id, u.username,
p.id AS p_id, p.phone AS p_phone
FROM user u LEFT JOIN profile p ON u.id = p.user_id
WHERE u.id = #{id}
</select>
xml
<!-- 一对多:User 包含 roles 列表(通过嵌套查询或嵌套结果) -->
<resultMap id="UserWithRoles" type="User">
<id column="u_id" property="id"/>
<result column="username" property="username"/>
<collection property="roles" ofType="Role">
<id column="r_id" property="id"/>
<result column="r_name" property="name"/>
</collection>
</resultMap>
<select id="selectUserWithRoles" resultMap="UserWithRoles">
SELECT u.id AS u_id, u.username,
r.id AS r_id, r.name AS r_name
FROM user u LEFT JOIN user_role ur ON u.id=ur.user_id
LEFT JOIN role r ON ur.role_id=r.id
WHERE u.id=#{id}
</select>
建议:能一次性 JOIN 出来就不要 N+1(嵌套查询),必要时才懒加载。
4.4 动态 SQL 常用标签
<if>
/<choose-when-otherwise>
条件拼接。<where>
自动处理多余 AND/OR。<set>
动态更新字段(自动处理逗号)。<trim prefix="(" suffix=")" suffixOverrides=","/>
高级裁剪。<foreach>
循环(IN 查询、批量插入)。<bind>
绑定变量(如LIKE
模糊匹配)。
综合示例:复杂查询
xml
<select id="queryUsers" resultMap="BaseResultMap">
SELECT <include refid="Base_Column_List"/>
FROM user
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="emails != null and emails.size > 0">
AND email IN
<foreach collection="emails" item="e" open="(" close=")" separator=",">
#{e}
</foreach>
</if>
<if test="statusList != null and statusList.size>0">
AND status IN
<foreach collection="statusList" item="s" open="(" close=")" separator=",">
#{s}
</foreach>
</if>
</where>
ORDER BY id DESC
<if test="limit != null">LIMIT #{limit}</if>
<if test="offset != null"> OFFSET #{offset}</if>
</select>
5. 类型处理器 TypeHandler(含自定义 JSON/枚举示例)
5.1 内置与枚举处理
- 内置
Date
,LocalDateTime
, 基本类型 等常见映射开箱即用。 - 枚举:使用
EnumTypeHandler
(存字符串)或EnumOrdinalTypeHandler
(存序号)。不建议用序号,改名/顺序调整有风险。
枚举示例
java
public enum UserStatus {
DISABLED(0), ENABLED(1);
private final int code;
UserStatus(int code){this.code=code;}
public int getCode(){return code;}
}
java
// 自定义枚举 TypeHandler:用 code 入库
@MappedJdbcTypes(JdbcType.TINYINT)
@MappedTypes(UserStatus.class)
public class UserStatusTypeHandler extends BaseTypeHandler<UserStatus> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, UserStatus parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.getCode());
}
@Override
public UserStatus getNullableResult(ResultSet rs, String columnName) throws SQLException {
int code = rs.getInt(columnName);
return code==1?UserStatus.ENABLED:UserStatus.DISABLED;
}
@Override
public UserStatus getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
int code = rs.getInt(columnIndex);
return code==1?UserStatus.ENABLED:UserStatus.DISABLED;
}
@Override
public UserStatus getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
int code = cs.getInt(columnIndex);
return code==1?UserStatus.ENABLED:UserStatus.DISABLED;
}
}
在 Mapper XML 中声明或通过全局配置扫描
xml
<result column="status" property="status" typeHandler="com.example.demo.type.UserStatusTypeHandler"/>
5.2 JSON 字段映射(以 Jackson 为例)
java
public class Profile {
private String phone;
private List<String> tags;
}
java
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(Profile.class)
public class JsonProfileTypeHandler extends BaseTypeHandler<Profile> {
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Profile parameter, JdbcType jdbcType) throws SQLException {
try { ps.setString(i, MAPPER.writeValueAsString(parameter)); }
catch (JsonProcessingException e) { throw new SQLException(e); }
}
@Override
public Profile getNullableResult(ResultSet rs, String columnName) throws SQLException {
String json = rs.getString(columnName);
if (json == null) return null;
try { return MAPPER.readValue(json, Profile.class); }
catch (IOException e) { throw new SQLException(e); }
}
@Override
public Profile getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String json = rs.getString(columnIndex);
if (json == null) return null;
try { return MAPPER.readValue(json, Profile.class); }
catch (IOException e) { throw new SQLException(e); }
}
@Override
public Profile getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String json = cs.getString(columnIndex);
if (json == null) return null;
try { return MAPPER.readValue(json, Profile.class); }
catch (IOException e) { throw new SQLException(e); }
}
}
6. Spring 事务与多数据源整合
6.1 事务
- 使用
@Transactional
(默认运行时异常回滚)。 - MyBatis 与 Spring 事务整合由
SqlSessionTemplate
完成,无需手工commit/rollback
。
注意 :同类内部方法调用不会触发 AOP 事务,可用 @Transactional
放到 public
对外方法,或通过 self-injection 解决。
6.2 多数据源(读写分离示例)
思路:定义两个 DataSource
,两个 SqlSessionFactory
,分别 @MapperScan
指向不同包。
java
@Configuration
@MapperScan(basePackages = "com.example.demo.mapper.read", sqlSessionFactoryRef = "readSqlSessionFactory")
public class ReadDataSourceConfig {
@Bean
public DataSource readDataSource() { /* 配置 HikariDataSource */ }
@Bean
public SqlSessionFactory readSqlSessionFactory(@Qualifier("readDataSource") DataSource ds) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(ds);
return bean.getObject();
}
}
也可使用路由数据源(AbstractRoutingDataSource)+ 拦截器/注解在运行时切换。
7. 分页:LIMIT、RowBounds 与 PageHelper
- 直接 LIMIT/OFFSET:最可控,适合大多数场景。
- RowBounds :会把所有结果查出后在内存截断,大表慎用。
- PageHelper 插件:拦截 SQL 自动改写为分页语句,并统计总数,开发效率高。
PageHelper 示例
xml
<!-- Maven -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.7</version>
</dependency>
java
Page<User> page = PageHelper.startPage(1, 20)
.doSelectPage(() -> userMapper.selectByUsernameLike("tom"));
return new PageResult<>(page.getResult(), page.getTotal());
8. 缓存机制:一级/二级缓存
- 一级缓存(SqlSession 级别) :同一会话内相同查询直接命中;会在
commit/close
后失效。 - 二级缓存(Mapper 命名空间级别) :多个会话共享;需开启
<cache/>
或@CacheNamespace
;更新/插入/删除默认会清空本命名空间缓存。
二级缓存开启示例
xml
<mapper namespace="com.example.demo.mapper.UserMapper">
<cache eviction="LRU" flushInterval="600000" size="1024" readOnly="false"/>
<!-- 其余 SQL ... -->
</mapper>
注意 :跨表更新导致数据不一致时,使用 cache-ref
引用同一缓存区域,或干脆关闭二级缓存,改用业务层缓存(如 Redis)。
9. 性能优化与批处理
9.1 批处理
ExecutorType.BATCH
:减少网络往返,成批提交。
java
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User u: users) { mapper.insert(u); }
session.commit();
}
- 批量插入 SQL(更快):
xml
<insert id="batchInsert">
INSERT INTO user (username, email, status) VALUES
<foreach collection="list" item="u" separator=",">
(#{u.username}, #{u.email}, #{u.status})
</foreach>
</insert>
9.2 其他优化要点
- 尽量命中 覆盖索引,避免回表。
- 合理选择 JOIN vs. 子查询 ,规避 N+1。
- 用
<sql>
片段重用列清单,保持一致性。 - 大列表分页:使用 游标/seek 方法 (
where id > ? limit ?
)替代传统 offset,提高性能。
10. 插件(拦截器)机制
MyBatis 允许对 4 大接口进行拦截:Executor
、StatementHandler
、ParameterHandler
、ResultSetHandler
。
示例:SQL 耗时打印 + 租户注入
java
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class SqlCostInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler handler = (StatementHandler) Plugin.getTarget(invocation.getTarget());
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
long t1 = System.nanoTime();
try { return invocation.proceed(); }
finally {
long t2 = System.nanoTime();
System.out.println("SQL耗时(ms): " + (t2 - t1)/1_000_000 + " => " + sql);
}
}
@Override
public Object plugin(Object target) { return Plugin.wrap(target, this); }
@Override
public void setProperties(Properties properties) {}
}
注意:拦截器容易引入隐形开销与副作用,审慎上线,可灰度测试。
11. 代码生成:MyBatis Generator 与替代方案
11.1 MyBatis Generator(MBG)
优点:稳健、官方、能根据表结构生成 model/mapper/xml
。
缺点:生成代码风格偏老,需要二次改造;复杂 SQL 仍需手写。
MBG 配置(片段)
xml
<generatorConfiguration>
<context id="MySqlContext" targetRuntime="MyBatis3Simple">
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/demo"
userId="root" password="root"/>
<javaModelGenerator targetPackage="com.example.demo.domain" targetProject="src/main/java"/>
<sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"/>
<javaClientGenerator targetPackage="com.example.demo.mapper" targetProject="src/main/java" type="XMLMAPPER"/>
<table tableName="user" domainObjectName="User"/>
</context>
</generatorConfiguration>
11.2 替代方案
- MyBatis-Plus:增强 CRUD、代码生成、分页、Wrapper 条件构造器(学习/上手快)。
- 自建代码生成器:基于模板(Freemarker/Beetl),按团队规范定制输出。
12. 常见问题排查(Troubleshooting)
Parameter 'xxx' not found
:方法参数未加@Param
,或 XML 使用的名字与实际不一致。Invalid bound statement (not found)
:namespace + id
未匹配;mapper-locations
扫描路径不对;包路径大小写错误。TooManyResultsException
:selectOne
返回多行;请改limit 1
或使用selectList
。- 驼峰映射不生效 :未开启
map-underscore-to-camel-case
或列别名未对齐。 javaType/jdbcType
冲突 :jdbcType
写NULL
可避免某些数据库驱动因null
无法推断类型而报错。- 二级缓存脏读 :跨命名空间更新;使用
cache-ref
或关闭二级缓存。 - SQL 注入 :使用
${}
拼接外部输入(尤其是order by
、表名)造成风险,优先#{}
或白名单校验。 - 事务不生效 :同类内部调用;数据源未配置到
DataSourceTransactionManager
;异常被吞。 RowBounds
内存炸裂 :大数据量分页必须改 SQL →LIMIT
或使用插件。- 批处理返回主键 :
useGeneratedKeys
+keyProperty
;批量插入需注意驱动/数据库是否支持批量返回主键。
13. 工程落地与包结构建议
com.example.demo
├── common // 公共组件(拦截器、枚举、异常、工具)
├── config // 数据源、MyBatis、事务、分页插件配置
├── domain // 领域模型(DO)
├── dto|vo // 入参/出参对象
├── mapper // Mapper 接口 + XML(resources/mapper)
├── repository // 组合多个 Mapper 的仓储实现(可选)
├── service // 业务服务层(含 @Transactional)
├── web // 控制器
└── starter // 启动类
建议:
- 基础列片段统一
<sql id="Base_Column_List"/>
,减少遗漏。 - Mapper 拆分:
BaseMapper
(通用) +XxxMapper
(个性化)。 - 复杂 SQL 封装成视图/存储过程时,务必评估可维护性与迁移成本。
14. 面试高频题与答题要点
- MyBatis 与 Hibernate 区别? ------ SQL 自由 vs. 自动 ORM;可控性/性能 vs. 生产力。
#{} 与 ${}
区别? ------ 预编译占位符(安全) vs. 字符串拼接(有注入风险)。- 一级/二级缓存工作原理? ------ 会话级/命名空间级;更新语句清缓存;
cache-ref
。 - 动态 SQL 标签 ------
if/choose/where/set/trim/foreach/bind
场景与常见坑。 - 插件能拦截哪些点? ------
Executor/StatementHandler/ParameterHandler/ResultSetHandler
;谨慎使用。 - 分页实现方案? ------ LIMIT、RowBounds(慎用)、PageHelper(优点/缺点)。
- 批处理如何做? ------
ExecutorType.BATCH
vs. 多值 INSERT;差异与回滚策略。 - 如何避免 N+1? ------ JOIN 一次查全;必要时懒加载但注意一致性与事务边界。
- 事务为什么没生效? ------ AOP 代理、同类调用、异常类型、事务管理器配置。
- TypeHandler 的作用与自定义示例 ------ 枚举/JSON 映射。
15. 纯 MyBatis(非 Spring)最小可运行示例
15.1 mybatis-config.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/demo"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
15.2 启动代码
java
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User u = mapper.selectById(1L);
System.out.println(u);
}
16. 参考资料与学习路径
- 官方文档:熟悉 XML 标签、配置项、缓存与插件机制。
- 深入源码:
MapperProxy
、Executor
、Configuration
、MappedStatement
、BoundSql
。 - 实战演练:把线上一个复杂查询用 MyBatis 重写,做 Explain 分析 + 索引优化 + 压测。
- 扩展阅读:MyBatis-Plus、ShardingSphere(分库分表)、多租户/数据权限设计。
最后
MyBatis 的真正价值在于"可控性 + 可观测性 + 可维护性"。
掌握本文的路线与示例,配合你所在业务的真实 SQL 场景,基本可以实现从入门 → 熟练 → 精通。祝使用愉快!🚀