1. MyBatis框架概述
MyBatis是一款优秀的持久层框架,它消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。作为一个半自动化的ORM框架,MyBatis在SQL语句和Java对象之间建立了灵活的映射关系,让开发者能够专注于SQL本身,同时又享受到对象关系映射带来的便利。
我第一次接触MyBatis是在2012年参与一个电商项目时。当时团队从Hibernate切换到MyBatis,最大的感受就是SQL的可控性大幅提升,特别是面对复杂查询和性能优化场景时。MyBatis不会像全自动ORM那样生成大量难以优化的SQL,而是让开发者完全掌控SQL语句的编写。
2. MyBatis核心架构解析
2.1 核心组件构成
MyBatis的核心架构由以下几个关键组件组成:
-
SqlSessionFactory:这是MyBatis的入口点,通过它创建SqlSession实例。通常一个应用只需要一个SqlSessionFactory实例,建议使用单例模式管理。
-
SqlSession:这是执行持久化操作的核心接口,每个线程都应该有自己的SqlSession实例。它提供了select/insert/update/delete等CRUD方法。
-
Executor:执行器接口,负责SQL语句的执行和缓存的维护。MyBatis有三种执行器实现:
- SimpleExecutor:每次执行都会创建新的Statement对象
- ReuseExecutor:重用预处理语句
- BatchExecutor:批量执行所有更新语句
-
MappedStatement:封装了SQL语句的详细信息,包括SQL文本、参数映射、结果映射等。
-
Configuration:MyBatis的所有配置信息都保存在这个对象中,包括数据源、事务管理器、映射文件等。
2.2 工作流程详解
MyBatis的工作流程可以分为以下几个阶段:
-
配置加载阶段:
- 解析mybatis-config.xml全局配置文件
- 加载所有Mapper XML文件
- 构建Configuration对象
-
SQLSession创建阶段:
- 通过SqlSessionFactoryBuilder构建SqlSessionFactory
- SqlSessionFactory创建SqlSession
-
SQL执行阶段:
- SqlSession获取Mapper接口的代理对象
- 代理对象将方法调用转换为对应的SQL操作
- Executor执行SQL并处理结果
-
结果映射阶段:
- ResultSetHandler处理结果集
- 根据映射配置将结果转换为Java对象
3. MyBatis核心特性深度剖析
3.1 动态SQL能力
MyBatis提供了强大的动态SQL功能,这是它最受欢迎的特性之一。通过OGNL表达式和内置标签,可以构建灵活的SQL语句:
xml复制<select id="findActiveBlogWithTitleLike" resultType="Blog">
SELECT * FROM BLOG
WHERE state = 'ACTIVE'
<if test="title != null">
AND title like #{title}
</if>
<choose>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
实际项目中,我经常使用动态SQL处理复杂的查询条件组合。一个经验是:对于条件特别多的查询,可以考虑使用<where>标签自动处理AND/OR连接问题,避免SQL语法错误。
3.2 结果集映射
MyBatis的结果集映射非常灵活,支持简单映射、复杂对象关联和集合处理:
xml复制<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
</association>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
</collection>
</resultMap>
在处理一对多、多对多关系时,我建议使用嵌套查询(通过select属性)而不是嵌套结果,虽然会带来N+1查询问题,但在数据量大时性能更好。
3.3 缓存机制
MyBatis提供了一级缓存和二级缓存:
-
一级缓存:SqlSession级别的缓存,默认开启。在同一个SqlSession中执行相同的查询,MyBatis会直接从缓存返回结果。需要注意的是,任何insert/update/delete操作都会清空一级缓存。
-
二级缓存:Mapper级别的缓存,需要显式配置开启。多个SqlSession可以共享二级缓存。配置方式如下:
xml复制<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
在实际项目中,我通常会对查询频繁但更新少的表启用二级缓存。但要注意缓存一致性问题,建议设置合理的flushInterval和适当的缓存淘汰策略。
4. MyBatis高级特性与应用
4.1 插件开发
MyBatis提供了强大的插件机制,可以拦截以下四大对象的方法调用:
- Executor
- ParameterHandler
- ResultSetHandler
- StatementHandler
开发插件的步骤:
- 实现Interceptor接口
- 使用@Intercepts注解指定要拦截的方法
- 在配置文件中注册插件
例如,下面是一个简单的SQL执行时间统计插件:
java复制@Intercepts({
@Signature(type= Executor.class,
method="query",
args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})
})
public class SqlCostInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long end = System.currentTimeMillis();
System.out.println("SQL执行耗时:" + (end - start) + "ms");
return result;
}
}
4.2 批量操作优化
MyBatis提供了几种批量操作的方式:
- 使用BatchExecutor:
java复制SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : users) {
mapper.insertUser(user);
}
session.commit();
} finally {
session.close();
}
- 使用foreach标签:
xml复制<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO user(name, age) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.name}, #{item.age})
</foreach>
</insert>
在实际项目中,我通常对小批量数据使用foreach方式,对大批量数据使用BatchExecutor。需要注意的是,MySQL的JDBC驱动默认情况下会缓存所有批量操作的SQL语句,可能导致内存溢出,可以通过在JDBC URL中添加rewriteBatchedStatements=true参数来优化。
4.3 多数据源支持
在大型应用中,经常需要访问多个数据源。MyBatis可以通过以下方式实现多数据源:
- 配置多个SqlSessionFactory
- 使用@Qualifier区分Mapper接口
- 使用AOP在运行时动态切换数据源
Spring Boot项目中可以这样配置:
java复制@Configuration
@MapperScan(basePackages = "com.example.mapper.primary", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
return sessionFactory.getObject();
}
}
5. MyBatis最佳实践与性能优化
5.1 SQL编写规范
- **避免使用SELECT ***:明确列出需要的字段,减少网络传输和内存消耗。
- 合理使用索引:WHERE条件中的字段应该建立合适的索引。
- 注意LIKE查询:前导通配符(如'%xxx')会导致索引失效。
- 分页优化:大数据量分页避免使用LIMIT offset, size,建议使用"上一页最大ID"方式:
sql复制SELECT * FROM table WHERE id > #{lastMaxId} ORDER BY id LIMIT #{size}
5.2 事务管理
MyBatis本身不管理事务,它依赖于底层的数据源事务管理。在Spring环境中,可以通过@Transactional注解管理事务。一些实践经验:
- 事务方法不要太大,保持短小精悍
- 只读操作添加@Transactional(readOnly = true)
- 注意事务传播行为的设置
- 避免在事务中进行远程调用和耗时操作
5.3 性能监控与调优
- 启用慢SQL日志:配置拦截器记录执行时间过长的SQL
- 使用连接池:推荐HikariCP,性能优于传统的DBCP和C3P0
- 合理设置fetchSize:对于大数据量查询,适当增大fetchSize
- 使用延迟加载:对于关联对象,可以配置lazyLoadingEnabled=true
6. MyBatis与Spring/Spring Boot集成
6.1 基本集成配置
Spring Boot中集成MyBatis非常简单,只需添加依赖:
xml复制<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
然后在application.properties中配置:
properties复制# 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# MyBatis配置
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.example.model
6.2 高级集成特性
- 多数据源支持:如前面所述,可以配置多个SqlSessionFactory
- MyBatis Generator:自动生成Mapper接口和XML文件
- PageHelper分页插件:简化分页查询
- 动态数据源路由:基于AOP实现读写分离
6.3 常见问题解决
-
Mapper接口无法注入:
- 检查@MapperScan配置的包路径是否正确
- 确保Mapper接口有@Mapper注解或XML文件存在
-
XML文件未加载:
- 检查mybatis.mapper-locations配置
- 确保XML文件在resources目录下正确位置
-
属性名和列名不匹配:
- 使用@Results注解或resultMap显式映射
- 配置mapUnderscoreToCamelCase=true自动转换下划线命名
7. MyBatis 3.5+新特性
7.1 新增功能
- Kotlin DSL支持:可以使用Kotlin代码构建动态SQL
- 增强的注解配置:@SelectProvider等注解功能增强
- 新的缓存实现:支持Caffeine等现代缓存库
- 改进的类型处理器:更好的Java 8日期时间支持
7.2 性能改进
- 延迟加载优化:减少了不必要的代理对象创建
- XML解析性能提升:使用StAX代替DOM解析XML
- 批处理改进:更高效的批量操作实现
7.3 向后兼容性
MyBatis 3.5+保持了良好的向后兼容性,但需要注意:
- 移除了部分过时的API
- 修改了一些默认配置值
- 增强了对Java 8+特性的支持
在实际升级过程中,我建议先在测试环境充分验证,特别是检查自定义插件和类型处理器是否兼容新版本。
