一、MyBatis 核心架构与设计哲学
MyBatis 作为半自动 ORM 框架,核心设计目标是在灵活性与开发效率之间取得平衡。与 Hibernate 等全自动 ORM 框架不同,MyBatis 允许开发者完全控制 SQL 编写,同时通过映射机制减少重复代码,特别适合复杂业务场景和性能敏感的系统。
1.1 核心优势对比
特性 | 传统 JDBC | 全自动 ORM(如 Hibernate) | MyBatis |
---|---|---|---|
SQL 控制粒度 | 细粒度(完全手动) | 粗粒度(自动生成 SQL) | 中细粒度(手动编写 + 映射) |
学习成本 | 高(需处理连接 / 结果集) | 高(需掌握 ORM 规则) | 中(聚焦 SQL 与映射) |
性能优化空间 | 高 | 低(依赖框架优化) | 高(可针对性优化 SQL) |
适用场景 | 底层工具开发 | 简单 CRUD 业务 | 复杂查询 / 性能敏感系统 |
1.2 架构核心组件
MyBatis 的核心流程围绕以下组件展开:
- SqlSessionFactory:通过
mybatis-config.xml
加载配置,创建SqlSession
实例。- 包含数据源(
DataSource
)、事务管理器(TransactionManager
)、插件(Plugins
)等全局配置。
- 包含数据源(
- SqlSession:提供操作数据库的接口(如
selectOne
、insert
),维护一级缓存。 - Executor:执行 SQL 的核心引擎,支持三种模式:
- Simple:默认模式,每次执行 SQL 创建新 Statement。
- Reuse:重用 Statement 对象(适用于相同 SQL 多次执行)。
- Batch:批量执行 SQL(适用于批量插入 / 更新)。
- MappedStatement:封装单个 SQL 语句的映射信息(如
id
、SQL语句
、参数类型
、结果映射
)。
二、基础配置与快速入门
2.1 依赖管理最佳实践
Maven 标准配置
<dependencies><!-- MyBatis核心 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.10</version></dependency><!-- 数据库驱动(建议与数据库版本严格匹配) --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><!-- 连接池(推荐HikariCP或Druid) --><dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId><version>5.0.1</version></dependency><!-- 与Spring整合时添加 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>2.0.7</version></dependency>
</dependencies>
2.2 核心配置文件详解
<!-- mybatis-config.xml -->
<configuration><!-- 全局设置 --><settings><setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 驼峰命名自动映射 --><setting name="lazyLoadingEnabled" value="true"/> <!-- 开启延迟加载 --><setting name="multipleResultSetsEnabled" value="false"/> <!-- 禁止多结果集(安全考虑) --></settings><!-- 环境配置(支持多环境切换) --><environments default="dev"><environment id="dev"><transactionManager type="JDBC"/> <!-- 使用JDBC事务 --><dataSource type="POOLED"> <!-- 池化数据源 --><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC"/><property name="username" value="root"/><property name="password" value="123456"/><!-- HikariCP特有配置(池化数据源建议显式配置) --><property name="maxPoolSize" value="10"/><property name="minIdle" value="2"/></dataSource></environment></environments><!-- 映射器注册(推荐使用classpath*:mapper/**Mapper.xml通配符) --><mappers><mapper resource="mapper/UserMapper.xml"/><mapper class="com.example.mapper.OrderMapper"/> <!-- 注解映射器注册方式 --></mappers>
</configuration>
2.3 初始化与核心操作
// 初始化SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 获取SqlSession(默认自动提交为false)
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {UserMapper mapper = sqlSession.getMapper(UserMapper.class);// 单条查询User user = mapper.selectUserById(1L);// 插入数据(需手动提交事务)User newUser = new User("Alice", 25);mapper.insertUser(newUser);sqlSession.commit(); // 提交事务
}
三、SQL 映射深度实践
3.1 XML 映射与动态 SQL
基础 CRUD 映射
<mapper namespace="com.example.mapper.UserMapper"><!-- 查询(使用resultType自动映射简单对象) --><select id="selectUserById" resultType="User">SELECT id, user_name AS name, user_age AS age FROM t_user WHERE id = #{id, jdbcType=BIGINT}</select><!-- 插入(使用useGeneratedKeys获取自增主键) --><insert id="insertUser" useGeneratedKeys="true" keyProperty="id">INSERT INTO t_user (user_name, user_age)VALUES (#{name, jdbcType=VARCHAR}, #{age, jdbcType=INTEGER})</insert><!-- 更新(动态SET子句) --><update id="updateUser">UPDATE t_user<set><if test="name != null">user_name = #{name},</if><if test="age != null">user_age = #{age},</if>update_time = NOW()</set>WHERE id = #{id}</update>
</mapper>
复杂动态 SQL 场景
<select id="searchUsers" resultType="User">SELECT * FROM t_user<where><!-- 模糊查询(注意%的拼接方式) --><if test="name != null and name != ''">user_name LIKE CONCAT('%', #{name}, '%')</if><!-- 范围查询 --><if test="minAge != null">AND user_age >= #{minAge}</if><!-- 排序(通过OGNL表达式安全拼接字段) --><if test="orderByColumn in {'name', 'age', 'id'}">ORDER BY ${orderByColumn} ${orderByDirection}</if></where><!-- 分页(建议使用PageHelper插件) --><if test="pageable != null">LIMIT #{pageable.offset}, #{pageable.pageSize}</if>
</select>
3.2 注解映射高级用法
public interface UserMapper {// 单参数查询(自动识别参数名)@Select("SELECT * FROM t_user WHERE id = #{id}")User selectById(Long id);// 多参数查询(必须使用@Param注解)@Select("SELECT * FROM t_user WHERE user_name = #{name} AND user_age = #{age}")User selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);// 插入返回主键(通过@Options配置)@Insert("INSERT INTO t_user (user_name, user_age) VALUES (#{name}, #{age})")@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")int insert(@Param("user") User user);// 动态SQL注解(结合SqlProvider)@SelectProvider(type = UserSqlProvider.class, method = "buildSearchSql")List<User> search(@Param("query") UserQuery query);
}// SQLProvider类(动态拼接SQL)
public class UserSqlProvider {public String buildSearchSql(UserQuery query) {SQL sql = new SQL();sql.SELECT("id, user_name, user_age");sql.FROM("t_user");if (StringUtils.isNotBlank(query.getName())) {sql.WHERE("user_name LIKE CONCAT('%', #{query.name}, '%')");}if (query.getMinAge() != null) {sql.WHERE("user_age >= #{query.minAge}");}if (StringUtils.isNotBlank(query.getOrderBy())) {sql.ORDER_BY(query.getOrderBy());}return sql.toString();}
}
四、结果集映射与关联查询
4.1 基础结果映射
<!-- 使用resultMap显式映射(解决字段名与属性名不匹配) -->
<resultMap id="userResultMap" type="User"><id column="user_id" property="id" jdbcType="BIGINT"/> <!-- 主键映射 --><result column="user_name" property="name" jdbcType="VARCHAR"/><result column="user_age" property="age" jdbcType="INTEGER"/><result column="create_time" property="createTime" jdbcType="TIMESTAMP"/> <!-- 驼峰映射示例 -->
</resultMap><select id="selectUserWithMap" resultMap="userResultMap">SELECT user_id, user_name, user_age, create_time FROM t_user WHERE id = #{id}
</select>
4.2 关联对象映射
一对一关联(使用 association)
<resultMap id="userWithDeptResultMap" type="User"><id column="user_id" property="id"/><result column="user_name" property="name"/><!-- 关联部门对象(使用嵌套查询) --><association property="department" column="dept_id" javaType="Department" select="selectDepartmentById"/>
</resultMap><select id="selectUserWithDept" resultMap="userWithDeptResultMap">SELECT user_id, user_name, dept_id FROM t_user WHERE id = #{id}
</select><select id="selectDepartmentById" resultType="Department">SELECT dept_id, dept_name FROM t_department WHERE dept_id = #{deptId}
</select>
一对多关联(使用 collection)
<resultMap id="userWithRolesResultMap" type="User"><id column="user_id" property="id"/><result column="user_name" property="name"/><!-- 关联角色集合(使用嵌套结果) --><collection property="roles" ofType="Role" columnPrefix="role_"><id column="role_id" property="id"/><result column="role_name" property="name"/></collection>
</resultMap><select id="selectUserWithRoles" resultMap="userWithRolesResultMap">SELECT u.user_id, u.user_name, r.role_id, r.role_name FROM t_user u LEFT JOIN t_user_role ur ON u.user_id = ur.user_id LEFT JOIN t_role r ON ur.role_id = r.role_id WHERE u.id = #{id}
</select>
五、高级特性与性能优化
5.1 缓存机制深度解析
一级缓存(SqlSession 级别)
- 作用范围:同一
SqlSession
内,默认开启,无法关闭。 - 失效场景:
- 调用
sqlSession.clearCache()
手动清空缓存。 - 执行
insert
/update
/delete
操作(自动清空缓存)。
- 调用
二级缓存(Mapper 级别)
<!-- 在Mapper中开启二级缓存 -->
<cache eviction="LRU" <!-- 淘汰策略:LRU(最近最少使用) -->flushInterval="60000" <!-- 刷新间隔:60秒 -->size="512" <!-- 缓存容量:512个对象 -->readOnly="true"/> <!-- 是否只读:true(适合只读场景,性能更高) --><!-- 使用二级缓存(需在select语句中声明useCache="true") -->
<select id="selectUserById" resultType="User" useCache="true">SELECT * FROM t_user WHERE id = #{id}
</select>
注意事项:
- 二级缓存跨
SqlSession
共享,需确保 POJO 可序列化(实现Serializable
接口)。 - 建议仅在读多写少的场景使用二级缓存,频繁更新的表禁用缓存。
5.2 插件开发实战
自定义 SQL 执行监控插件
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class PerformanceInterceptor implements Interceptor {private static final Logger log = LoggerFactory.getLogger(PerformanceInterceptor.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {long start = System.currentTimeMillis();Object result = invocation.proceed();long cost = System.currentTimeMillis() - start;MappedStatement ms = (MappedStatement) invocation.getArgs()[0];String statementId = ms.getId();String sql = ms.getBoundSql(invocation.getArgs()[1]).getSql();if (cost > 1000) { // 记录耗时超过1秒的SQLlog.warn("Slow SQL executed: {} Cost: {}ms\n{}", statementId, cost, sql);}return result;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}
}<!-- 在mybatis-config.xml中注册插件 -->
<plugins><plugin interceptor="com.example.performance.PerformanceInterceptor"><!-- 插件参数配置 --><property name="slowSqlThreshold" value="500"/> <!-- 慢SQL阈值(毫秒) --></plugin>
</plugins>
5.3 批量操作优化
批量插入(Batch 模式)
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {UserMapper mapper = sqlSession.getMapper(UserMapper.class);for (int i = 0; i < 1000; i++) {User user = new User("user_" + i, 20 + i);mapper.insertUser(user);if (i % 200 == 0) { // 每200条提交一次,避免内存溢出sqlSession.flushStatements();}}sqlSession.commit(); // 最终提交事务
}
批量更新(使用<foreach>标签)
<update id="batchUpdateStatus">UPDATE t_userSET status = #{status}WHERE id IN<foreach collection="idList" item="id" open="(" separator="," close=")">#{id}</foreach>
</update>
六、与 Spring 整合最佳实践
6.1 基于注解的整合配置
1. 配置类(替代 XML 配置)
@Configuration
@MapperScan(basePackages = "com.example.mapper") // 扫描Mapper接口
public class MyBatisConfig {@Bean@ConfigurationProperties(prefix = "spring.datasource") // 读取application.yml中的数据源配置public DataSource dataSource() {return new HikariDataSource();}@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();factoryBean.setDataSource(dataSource);factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/*.xml")); // 加载Mapper XML文件// 配置MyBatis全局属性Configuration configuration = factoryBean.getObject().getConfiguration();configuration.setMapUnderscoreToCamelCase(true); // 开启驼峰映射configuration.setLogImpl(StdOutImpl.class); // 开发环境打印SQL日志factoryBean.setConfiguration(configuration);return factoryBean.getObject();}@Beanpublic DataSourceTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource); // 配置事务管理器}
}
6.2 事务管理与 Spring 整合
声明式事务配置
@Service
public class UserService {private final UserMapper userMapper;private final OrderMapper orderMapper;@Autowiredpublic UserService(UserMapper userMapper, OrderMapper orderMapper) {this.userMapper = userMapper;this.orderMapper = orderMapper;}// 使用@Transactional声明事务(默认传播行为:REQUIRED)@Transactionalpublic void createUserAndOrder(User user, Order order) {userMapper.insertUser(user); // 插入用户order.setUserId(user.getId());orderMapper.insertOrder(order); // 插入订单(与用户操作在同一事务中)// 模拟异常回滚if (order.getAmount() <= 0) {throw new BusinessException("订单金额不能为负数");}}
}// 全局异常处理器(回滚事务)
@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(BusinessException.class)public ResponseEntity<String> handleBusinessException(BusinessException ex) {return ResponseEntity.badRequest().body(ex.getMessage());}
}
事务传播行为示例
@Service
public class TransactionService {@Autowiredprivate UserService userService;@Autowiredprivate OrderService orderService;// 外层事务(REQUIRED)@Transactionalpublic void outerTransaction() {userService.updateUser(new User(1L, "Updated")); // 新增事务?取决于内层配置try {orderService.createOrder(new Order(1L, -100)); // 内层事务抛出异常} catch (Exception e) {// 外层可捕获异常并决定是否回滚System.out.println("捕获内层异常,手动回滚");TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}}// 内层事务(REQUIRES_NEW:新建独立事务)@Transactional(propagation = Propagation.REQUIRES_NEW)public void createOrder(Order order) {orderMapper.insertOrder(order);if (order.getAmount() < 0) {throw new IllegalArgumentException("无效金额");}}
}
七、MyBatis 最佳实践
7.1 SQL 编写规范
避免的反模式
-- 反模式:使用SELECT *(性能差、耦合度高)
SELECT * FROM t_user-- 推荐:显式指定字段
SELECT id, user_name, user_age FROM t_user-- 反模式:子查询性能差
SELECT * FROM t_user WHERE id IN (SELECT user_id FROM t_order)-- 推荐:JOIN优化
SELECT u.* FROM t_user u JOIN t_order o ON u.id = o.user_id
动态 SQL 安全原则
<!-- 安全写法:使用预编译防止SQL注入 -->
<select id="findUserByName">SELECT * FROM t_user WHERE user_name = #{name}
</select><!-- 危险写法:直接拼接用户输入(仅用于可信场景) -->
<select id="sortUsers">SELECT * FROM t_user ORDER BY ${sortColumn} ${sortDirection}<!-- 必须确保sortColumn和sortDirection为预定义值 -->
</select>
7.2 性能优化策略
分页查询优化
<!-- 物理分页(推荐使用PageHelper插件) -->
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.3.2</version>
</dependency>// 使用方式
PageHelper.startPage(pageNum, pageSize); // 开启分页
List<User> userList = userMapper.searchUsers(query);
PageInfo<User> pageInfo = new PageInfo<>(userList); // 获取分页信息
延迟加载与懒加载
<!-- 全局开启延迟加载 -->
<settings><setting name="lazyLoadingEnabled" value="true"/><setting name="aggressiveLazyLoading" value="false"/> <!-- 禁止激进加载(默认false) -->
</settings><!-- 关联查询使用延迟加载 -->
<association property="department" column="dept_id" javaType="Department" select="selectDepartmentById" lazy="true"/>
批量操作性能对比
操作方式 | 单次插入 1000 条数据耗时 | 优点 | 缺点 |
---|---|---|---|
单条插入 | ~2000ms | 简单直接 | 网络 IO 次数多 |
批量插入(XML) | ~200ms | 性能提升明显 | 需要拼接 SQL |
批量插入(Batch) | ~150ms | 最优性能 | 需手动管理事务批次 |
八、常见问题与解决方案
8.1 参数绑定失效问题
现象:
org.apache.ibatis.exceptions.PersistenceException:
Error querying database. Cause: org.apache.ibatis.binding.BindingException:
Parameter 'name' not found. Available parameters are [0, 1, param1, param2]
原因:多参数未使用@Param
注解标识。
解决:
// 错误写法(MyBatis无法识别参数名)
User selectUser(String name, Integer age);// 正确写法(显式指定参数名)
User selectUser(@Param("name") String name, @Param("age") Integer age);
8.2 结果映射类型不匹配
现象:
org.apache.ibatis.type.TypeException:
Could not set parameters for mapping: ParameterMapping{property='createTime', ...}
Cause: java.sql.SQLException: Invalid value for getLong() - '2023-10-01 12:00:00'
原因:数据库字段类型(如 TIMESTAMP)与 Java 属性类型(如String
)不匹配。
解决:
<!-- 显式指定JDBC类型 -->
<result column="create_time" property="createTime" jdbcType="TIMESTAMP" javaType="java.time.LocalDateTime"/>
8.3 二级缓存不生效
排查步骤:
- 检查 Mapper 是否开启缓存:
<cache/> <!-- 确保存在缓存声明 -->
- 确认
select
语句是否启用缓存:<select ... useCache="true"/> <!-- 默认为true,但需显式声明 -->
- 检查 POJO 是否实现
Serializable
接口:public class User implements Serializable { ... }
- 确认是否执行了
insert/update/delete
操作(会清空二级缓存)。
九、MyBatis 3.5 + 新特性
9.1 注解式动态 SQL 增强
// 使用@SqlSource声明动态SQL
public interface UserMapper {@Select({"<script>","SELECT * FROM t_user","<where>","<if test='name != null'>user_name LIKE CONCAT('%', #{name}, '%')</if>","<if test='age != null'>AND user_age >= #{age}</if>","</where>","</script>"})List<User> search(@Param("name") String name, @Param("age") Integer age);
}
9.2 自动映射枚举类型
<!-- 全局配置枚举类型处理器 -->
<typeHandlers><typeHandler handler="org.apache.ibatis.type.EnumTypeHandler"/>
</typeHandlers>// 枚举类
public enum UserStatus {ACTIVE(1, "活跃"),INACTIVE(0, "禁用");private final int code;private final String desc;// 构造方法与getter
}// 映射使用
<result column="status" property="status" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
十、总结与学习资源
10.1 核心价值
MyBatis 的核心竞争力在于可控性与灵活性:
- 适合复杂业务场景(如多表关联查询、动态报表)。
- 便于优化 SQL 性能(如索引优化、分页策略)。
- 轻量级依赖(仅需 MyBatis 核心库,无额外框架侵入)。
10.2 学习路径建议
- 基础阶段:掌握 XML 映射、动态 SQL、结果集映射。
- 进阶阶段:深入缓存机制、插件开发、批量操作优化。
- 实战阶段:结合 Spring Boot/Spring Cloud 构建微服务,处理分布式事务场景。
- 源码阶段:阅读 MyBatis 核心类(如
Executor
、StatementHandler
),理解底层执行逻辑。
10.3 推荐资源
- 官方文档:MyBatis 3 中文文档
- 书籍:《MyBatis 从入门到精通》《Java 持久层技术实战》
- 开源项目:MyBatis-Plus(增强工具包,简化 CRUD)、TkMapper(通用 Mapper 工具)。
结语
MyBatis 通过将 SQL 编写与业务逻辑解耦,在保持开发效率的同时提供了极高的性能优化空间,是企业级应用中持久层的首选方案。开发者需在实际项目中积累 SQL 优化经验,结合业务场景合理使用缓存、批量操作和关联映射,以充分发挥 MyBatis 的优势。未来可进一步关注 MyBatis 与响应式编程(如 Reactive SQL)的整合,以及云原生场景下的持久层解决方案。