MyBatis / MyBatis-Plus 原理
MyBatis 核心组件
| 组件 | 作用 | 说明 |
|---|---|---|
| SqlSessionFactoryBuilder | 构建工厂 | 解析 XML 构建 SqlSessionFactory(一次性) |
| SqlSessionFactory | 会话工厂 | 生产 SqlSession(应用级别单例) |
| SqlSession | 会话 | 执行 SQL、管理事务、获取 Mapper(线程非安全) |
| Executor | 执行器 | 真正执行 JDBC 操作(Simple / Reuse / Batch) |
| MappedStatement | SQL 封装 | 封装 XML/注解中的 SQL 定义 |
| StatementHandler | 语句处理器 | 处理 JDBC Statement(SQL 预编译、参数设置、结果映射) |
| ParameterHandler | 参数处理器 | 将 Java 参数转换为 JDBC 参数 |
| ResultSetHandler | 结果集处理器 | 将 JDBC ResultSet 映射为 Java 对象 |
组件协作流程
text
应用程序
│
▼
SqlSessionFactoryBuilder
│ parse() — 解析 mybatis-config.xml + Mapper XML
▼
SqlSessionFactory
│ openSession()
▼
SqlSession
│ getMapper(UserMapper.class)
▼ 或
UserMapper proxy.invoke()
│ │
└─────────────────────┘
│
▼
DefaultSqlSession.selectList()
│
▼
CachingExecutor (二级缓存)
│
▼
BaseExecutor (一级缓存)
│
├─ 查询缓存 → 直接返回
└─ 未命中 → doQuery()
│
▼
SimpleExecutor.doQuery()
│
▼
StatementHandler
│
├─ prepare() → 创建 Statement
├─ parameterize() → ParameterHandler 设置参数
└─ query() → ResultSetHandler 映射结果
│
▼
返回结果MyBatis 执行流程详解
1. 配置文件解析
text
SqlSessionFactoryBuilder.build(inputStream)
→ XMLConfigBuilder.parse()
→ 解析 mybatis-config.xml 的 <configuration> 节点
├─ <properties> → 加载属性
├─ <settings> → 全局配置
├─ <typeAliases> → 类型别名
├─ <typeHandlers> → 类型处理器
├─ <environments> → 环境配置(数据源、事务管理器)
└─ <mappers> → 映射器(Mapper XML/注解类)
└─ XMLMapperBuilder.parse()
└─ 解析 <mapper> 节点
├─ <resultMap> → 结果映射规则
├─ <sql> → SQL 片段
└─ <select|insert|update|delete>
└─ 构建 MappedStatement2. Mapper 代理
MyBatis 通过 JDK 动态代理 为 Mapper 接口创建代理对象:
java
// SqlSession.getMapper()
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
// MapperRegistry.getMapper()
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 从 knownMappers 中获取 MapperProxyFactory
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
return mapperProxyFactory.newInstance(sqlSession);
}
// MapperProxyFactory.newInstance()
protected T newInstance(MapperProxy<T> mapperProxy) {
// 使用 JDK 动态代理创建代理对象
return (T) Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[] { mapperInterface },
mapperProxy
);
}
// MapperProxy.invoke() — 拦截 Mapper 方法调用
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 过滤 Object 类的方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 创建 MapperMethodInvoker,执行 SQL
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}3. SQL 执行
java
// MapperMethod.execute()
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: result = sqlSession.insert(...); break;
case UPDATE: result = sqlSession.update(...); break;
case DELETE: result = sqlSession.delete(...); break;
case SELECT:
if (returnsVoid) {
sqlSession.select(...);
} else if (returnsMany) {
result = sqlSession.selectList(...);
} else {
result = sqlSession.selectOne(...);
}
break;
case FLUSH: result = sqlSession.flushStatements(); break;
}
return result;
}4. 参数处理
java
// DefaultParameterHandler.setParameters()
public void setParameters(PreparedStatement ps) {
// 遍历 MappedStatement 中的 parameterMappings
for (int i = 0; i < parameterMappings.size(); i++) {
PropertyMapping mapping = parameterMappings.get(i);
Object value = reflectionFactory.getProperty(paramObject, mapping.getProperty());
TypeHandler typeHandler = mapping.getTypeHandler();
// 使用 TypeHandler 设置 JDBC 参数
typeHandler.setParameter(ps, i + 1, value, mapping.getJdbcType());
}
}5. 结果集映射
java
// DefaultResultSetHandler.handleResultSets()
public List<Object> handleResultSets(Statement stmt) throws SQLException {
List<Object> multipleResults = new ArrayList<>();
ResultSet rs = stmt.getResultSet();
ResultMap resultMap = mappedStatement.getResultMaps().get(0);
// 创建结果对象,逐行映射
while (rs.next()) {
Object row = createResultObject(rs, resultMap);
// 自动映射或 ResultMap 映射
applyAutomaticMappings(row, rs, resultMap);
applyPropertyMappings(row, rs, resultMap);
multipleResults.add(row);
}
return multipleResults;
}6. 缓存机制
一级缓存(SqlSession 级别):
text
默认开启,同一 SqlSession 中查询相同 SQL:
SELECT * FROM user WHERE id = 1 → 查数据库 → 缓存
SELECT * FROM user WHERE id = 1 → 命中缓存 → 不查库
失效条件:
- SqlSession close()
- 执行 INSERT / UPDATE / DELETE(清空当前 Session 缓存)
- 手动调用 SqlSession.clearCache()二级缓存(namespace 级别,跨 SqlSession):
text
需要手动开启:
1. mybatis-config: <setting name="cacheEnabled" value="true"/>
2. Mapper XML: <cache/>
3. POJO 实现 Serializable
脏读问题:
二级缓存默认不刷新,多表查询可能读到旧数据
→ 使用 <cache-ref namespace="..."/> 统一管理关联表7. 插件机制(Interceptor)
MyBatis 支持对四大核心组件进行拦截:
java
// 定义插件
@Intercepts({
@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
)
})
public class SqlLogPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler handler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = handler.getBoundSql();
// 打印 SQL
System.out.println("SQL: " + boundSql.getSql());
System.out.println("Params: " + boundSql.getParameterObject());
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long end = System.currentTimeMillis();
System.out.println("Time: " + (end - start) + "ms");
return result;
}
@Override
public Object plugin(Object target) {
// 使用 JDK 动态代理包装目标对象
return Plugin.wrap(target, this);
}
}
// 注册插件(mybatis-config.xml)
// <plugins>
// <plugin interceptor="com.example.SqlLogPlugin"/>
// </plugins>可拦截的四大对象:
| 组件 | 拦截点(常用方法) |
|---|---|
| Executor | update, query, commit, rollback, flushStatements |
| StatementHandler | prepare, parameterize, batch, update, query |
| ParameterHandler | getParameterObject, setParameters |
| ResultSetHandler | handleResultSets, handleOutputParameters |
MyBatis-Plus 增强特性
MyBatis-Plus(MP)是在 MyBatis 基础上不做任何修改的增强工具。
1. BaseMapper
java
public interface UserMapper extends BaseMapper<User> {
// 无需写任何 SQL!继承以下方法:
// insert(), deleteById(), updateById(), selectById()
// selectList(), selectPage(), selectCount() ...
}
// 使用
userMapper.selectById(1L);
userMapper.selectList(new LambdaQueryWrapper<User>()
.eq(User::getName, "Alice")
.ge(User::getAge, 18)
.orderByDesc(User::getCreateTime));实现原理:MP 在启动时通过 TableInfoHelper 解析实体类注解,自动生成 MappedStatement。
java
// 实体类注解
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("user_name")
private String name;
@TableLogic // 逻辑删除
private Integer deleted;
@Version // 乐观锁
private Integer version;
@TableField(fill = FieldFill.INSERT) // 自动填充
private LocalDateTime createTime;
}2. LambdaQueryWrapper / LambdaUpdateWrapper
java
// 链式查询(Lambda 形式,编译期校验字段名)
List<User> users = userMapper.selectList(
Wrappers.<User>lambdaQuery()
.eq(User::getName, "Alice")
.like(User::getEmail, "@example.com")
.in(User::getRole, "admin", "user")
.page(new Page<>(1, 10))
);
// 条件构造器等价 SQL:
// SELECT * FROM user
// WHERE name = 'Alice'
// AND email LIKE '%@example.com%'
// AND role IN ('admin', 'user')
// LIMIT 103. 分页插件
java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
// 使用分页
Page<User> page = userMapper.selectPage(
new Page<>(1, 10), // 第1页,每页10条
Wrappers.<User>lambdaQuery().eq(User::getStatus, 1)
);
// page.getRecords() → 数据列表
// page.getTotal() → 总记录数
// page.getPages() → 总页数4. 自动填充
java
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime::now, LocalDateTime.class);
this.strictInsertFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class);
}
}5. 代码生成器
java
FastAutoGenerator.create("jdbc:mysql://localhost:3306/db", "root", "password")
.globalConfig(builder -> builder.author("Alice").outputDir("src/main/java"))
.packageConfig(builder -> builder.parent("com.example").entity("entity").mapper("mapper"))
.strategyConfig(builder -> builder.addInclude("user", "order").entityBuilder().enableLombok())
.execute();6. 性能分析插件
properties
# application.yml
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # SQL 日志
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除字段
logic-delete-value: 1 # 逻辑已删除值
logic-not-delete-value: 0 # 逻辑未删除值MyBatis 与 MyBatis-Plus 对比
| 特性 | MyBatis | MyBatis-Plus |
|---|---|---|
| SQL 编写 | 需手动编写 | 基础 CRUD 自动生成 |
| 分页 | 手动 limit + 插件 | 内置分页插件 |
| 条件构造 | 手动拼接 SQL | LambdaQueryWrapper |
| 逻辑删除 | 需手动实现 | 注解 + 自动拦截 |
| 乐观锁 | 需手动实现 | @Version + 插件 |
| 自动填充 | 需手动实现 | MetaObjectHandler |
| 代码生成 | 需第三方工具 | FastAutoGenerator |
| 多数据源 | 需手动配置 | @DS 注解 |
| 性能分析 | 需手动实现 | 内置性能分析插件 |