目录
[一、MyBatis 核心原理](#一、MyBatis 核心原理)
[1. 核心架构](#1. 核心架构)
[2. 核心特性](#2. 核心特性)
[二、Spring Boot 集成 MyBatis 实战](#二、Spring Boot 集成 MyBatis 实战)
[1. 环境准备](#1. 环境准备)
[2. 代码实现](#2. 代码实现)
[(3)Mapper 接口](#(3)Mapper 接口)
[(4)Mapper XML 文件(resources/mapper/UserMapper.xml)](#(4)Mapper XML 文件(resources/mapper/UserMapper.xml))
[(5)Service 层(业务逻辑)](#(5)Service 层(业务逻辑))
[3. 测试验证](#3. 测试验证)
[三、MyBatis 高级用法](#三、MyBatis 高级用法)
[1. 分页插件(PageHelper)](#1. 分页插件(PageHelper))
[2. 二级缓存](#2. 二级缓存)
[(2)在 Mapper XML 中配置缓存](#(2)在 Mapper XML 中配置缓存)
[3. 自定义类型处理器](#3. 自定义类型处理器)
[1. 常见问题](#1. 常见问题)
[2. 最佳实践](#2. 最佳实践)
MyBatis 是一款轻量级、高性能的持久层框架,核心优势是灵活控制 SQL + 简化 JDBC 操作,既保留了手写 SQL 的灵活性,又解决了传统 JDBC 繁琐的资源管理、参数设置和结果映射问题。本文将从核心原理、核心特性、Spring Boot 集成实战、高级用法四个维度全面解析 MyBatis。
一、MyBatis 核心原理
1. 核心架构
MyBatis 的核心执行流程可概括为:
plaintext
配置加载 → SqlSessionFactory 创建 → SqlSession 获取 → Mapper 代理生成 → SQL 执行 → 结果映射
关键组件说明:
| 组件 | 作用 |
|---|---|
| SqlSessionFactory | 会话工厂(单例),负责创建 SqlSession,通过 SqlSessionFactoryBuilder 构建 |
| SqlSession | 数据库会话(非线程安全),封装增删改查 API,每次请求 / 事务独立创建 |
| Mapper 接口 | 数据操作接口,MyBatis 通过动态代理生成实现类,接口方法与 SQL 绑定 |
| Mapper XML / 注解 | 存储 SQL 语句、参数映射、结果映射规则 |
| Configuration | 核心配置类,存储全局配置(数据源、事务、别名、映射器等) |
| Executor | 执行器,负责 SQL 执行(SimpleExecutor/BatchExecutor/ReuseExecutor) |
| StatementHandler | 处理 Statement(PreparedStatement/Statement),设置参数、执行 SQL |
| ResultSetHandler | 将 ResultSet 映射为 Java 对象(结果映射核心) |
2. 核心特性
- 动态 SQL :通过
<if>/<where>/<foreach>等标签动态拼接 SQL,适配复杂条件 - 结果映射:支持一对一、一对多、多对多关联映射,解决字段名与属性名不一致问题
- 参数解析:支持多种参数类型(基本类型、实体、Map、注解参数),防止 SQL 注入
- 缓存机制:一级缓存(SqlSession 级别)+ 二级缓存(Mapper 级别),提升查询性能
- 插件扩展:支持自定义插件(如分页、拦截 SQL),扩展执行流程
二、Spring Boot 集成 MyBatis 实战
1. 环境准备
(1)依赖引入(Maven)
xml
<!-- Spring Boot 父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<dependencies>
<!-- MyBatis + Spring Boot 整合启动器 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Boot 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Lombok(简化实体类) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
(2)核心配置(application.yml)
yaml
# 数据源配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: 123456
# 连接池配置(默认 HikariCP)
hikari:
maximum-pool-size: 10 # 最大连接数
minimum-idle: 5 # 最小空闲连接
idle-timeout: 300000 # 空闲连接超时时间
# MyBatis 配置
mybatis:
# Mapper XML 文件位置
mapper-locations: classpath:mapper/**/*.xml
# 实体类别名包(简化 XML 中 type 配置)
type-aliases-package: com.example.mybatis.entity
# 全局配置
configuration:
map-underscore-to-camel-case: true # 自动下划线转驼峰(如 user_name → userName)
cache-enabled: false # 关闭二级缓存(默认开启,按需开启)
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印 SQL 日志
default-statement-timeout: 30 # SQL 执行超时时间
2. 代码实现
(1)数据库表设计
sql
CREATE DATABASE IF NOT EXISTS mybatis_demo;
USE mybatis_demo;
-- 用户表
CREATE TABLE `user` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
`user_name` VARCHAR(50) NOT NULL COMMENT '用户名',
`age` INT COMMENT '年龄',
`email` VARCHAR(100) COMMENT '邮箱',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) COMMENT '用户表';
-- 订单表(一对多关联:一个用户多个订单)
CREATE TABLE `order` (
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
`order_no` VARCHAR(32) NOT NULL COMMENT '订单号',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`amount` DECIMAL(10,2) NOT NULL COMMENT '订单金额',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`)
) COMMENT '订单表';
(2)实体类
java
运行
// User.java
package com.example.mybatis.entity;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class User {
private Long id;
private String userName; // 对应数据库 user_name(下划线转驼峰)
private Integer age;
private String email;
private LocalDateTime createTime;
// 一对多关联:用户的订单列表
private List<Order> orderList;
}
// Order.java
package com.example.mybatis.entity;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
public class Order {
private Long id;
private String orderNo;
private Long userId;
private BigDecimal amount;
private LocalDateTime createTime;
}
(3)Mapper 接口
java
运行
// UserMapper.java
package com.example.mybatis.mapper;
import com.example.mybatis.entity.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
// 方式1:注解扫描(推荐),也可在启动类加 @MapperScan("com.example.mybatis.mapper")
@Mapper
public interface UserMapper {
/**
* 根据ID查询用户(含订单列表)
*/
User selectById(Long id);
/**
* 条件查询用户(动态SQL)
*/
List<User> selectByCondition(@Param("userName") String userName, @Param("age") Integer age);
/**
* 新增用户(主键回填)
*/
int insert(User user);
/**
* 更新用户(动态更新)
*/
int update(User user);
/**
* 删除用户
*/
int delete(Long id);
}
(4)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">
<!-- namespace 必须与 Mapper 接口全限定名一致 -->
<mapper namespace="com.example.mybatis.mapper.UserMapper">
<!-- 基础结果映射:解决字段名与属性名映射 -->
<resultMap id="BaseResultMap" type="User">
<id column="id" property="id"/> <!-- 主键映射 -->
<result column="user_name" property="userName"/>
<result column="age" property="age"/>
<result column="email" property="email"/>
<result column="create_time" property="createTime"/>
</resultMap>
<!-- 关联结果映射:用户 + 订单列表(一对多) -->
<resultMap id="UserWithOrderResultMap" type="User" extends="BaseResultMap">
<!-- collection 映射一对多关系 -->
<collection property="orderList" ofType="Order" column="id"
select="com.example.mybatis.mapper.OrderMapper.selectByUserId"/>
</resultMap>
<!-- 根据ID查询用户(关联订单) -->
<select id="selectById" resultMap="UserWithOrderResultMap">
SELECT id, user_name, age, email, create_time
FROM user
WHERE id = #{id}
</select>
<!-- 条件查询(动态SQL) -->
<select id="selectByCondition" resultMap="BaseResultMap">
SELECT id, user_name, age, email, create_time
FROM user
<where>
<if test="userName != null and userName != ''">
AND user_name LIKE CONCAT('%', #{userName}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</where>
</select>
<!-- 新增用户:useGeneratedKeys 开启主键回填,keyProperty 绑定实体主键 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (user_name, age, email)
VALUES (#{userName}, #{age}, #{email})
</insert>
<!-- 动态更新:set 标签自动处理逗号 -->
<update id="update">
UPDATE user
<set>
<if test="userName != null and userName != ''">
user_name = #{userName},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="email != null and email != ''">
email = #{email}
</if>
</set>
WHERE id = #{id}
</update>
<!-- 删除用户 -->
<delete id="delete">
DELETE FROM user WHERE id = #{id}
</delete>
</mapper>
xml
<!-- OrderMapper.xml(resources/mapper/OrderMapper.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.mybatis.mapper.OrderMapper">
<resultMap id="BaseResultMap" type="Order">
<id column="id" property="id"/>
<result column="order_no" property="orderNo"/>
<result column="user_id" property="userId"/>
<result column="amount" property="amount"/>
<result column="create_time" property="createTime"/>
</resultMap>
<select id="selectByUserId" resultMap="BaseResultMap">
SELECT id, order_no, user_id, amount, create_time
FROM `order`
WHERE user_id = #{userId}
</select>
</mapper>
(5)Service 层(业务逻辑)
java
运行
// UserService.java
package com.example.mybatis.service;
import com.example.mybatis.entity.User;
import com.example.mybatis.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class UserService {
@Resource
private UserMapper userMapper;
/**
* 根据ID查询用户
*/
public User selectById(Long id) {
return userMapper.selectById(id);
}
/**
* 条件查询
*/
public List<User> selectByCondition(String userName, Integer age) {
return userMapper.selectByCondition(userName, age);
}
/**
* 新增用户(事务控制)
*/
@Transactional(rollbackFor = Exception.class)
public int insert(User user) {
return userMapper.insert(user);
}
/**
* 更新用户
*/
@Transactional(rollbackFor = Exception.class)
public int update(User user) {
return userMapper.update(user);
}
/**
* 删除用户
*/
@Transactional(rollbackFor = Exception.class)
public int delete(Long id) {
return userMapper.delete(id);
}
}
(6)启动类
java
运行
package com.example.mybatis;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// 方式2:批量扫描 Mapper 接口(替代每个接口加 @Mapper)
// @MapperScan("com.example.mybatis.mapper")
@SpringBootApplication
public class MybatisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisDemoApplication.class, args);
}
}
3. 测试验证
java
运行
package com.example.mybatis;
import com.example.mybatis.entity.User;
import com.example.mybatis.service.UserService;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
public class UserServiceTest {
@Resource
private UserService userService;
@Test
public void testInsert() {
User user = new User();
user.setUserName("张三");
user.setAge(25);
user.setEmail("zhangsan@example.com");
int rows = userService.insert(user);
System.out.println("新增行数:" + rows + ",用户ID:" + user.getId());
}
@Test
public void testSelectById() {
User user = userService.selectById(1L);
System.out.println("用户信息:" + user);
System.out.println("用户订单:" + user.getOrderList());
}
@Test
public void testSelectByCondition() {
List<User> userList = userService.selectByCondition("张", 25);
System.out.println("条件查询结果:" + userList);
}
@Test
public void testUpdate() {
User user = new User();
user.setId(1L);
user.setAge(26);
int rows = userService.update(user);
System.out.println("更新行数:" + rows);
}
@Test
public void testDelete() {
int rows = userService.delete(1L);
System.out.println("删除行数:" + rows);
}
}
三、MyBatis 高级用法
1. 分页插件(PageHelper)
(1)引入依赖
xml
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.7</version>
</dependency>
(2)配置(application.yml)
yaml
pagehelper:
helper-dialect: mysql # 数据库方言
reasonable: true # 页码合理化(页码<=0 查第1页,页码>总页数查最后一页)
support-methods-arguments: true # 支持通过参数传递分页参数
(3)使用示例
java
运行
@Test
public void testPage() {
// 第1页,每页10条
PageHelper.startPage(1, 10);
List<User> userList = userService.selectByCondition(null, null);
// 分页结果封装
PageInfo<User> pageInfo = new PageInfo<>(userList);
System.out.println("总条数:" + pageInfo.getTotal());
System.out.println("总页数:" + pageInfo.getPages());
System.out.println("当前页数据:" + pageInfo.getList());
}
2. 二级缓存
(1)开启全局缓存(application.yml)
yaml
mybatis:
configuration:
cache-enabled: true # 开启二级缓存(默认 true)
(2)在 Mapper XML 中配置缓存
xml
<!-- UserMapper.xml 中添加 -->
<cache
eviction="LRU" # 缓存淘汰策略(LRU/FIFO/SOFT/WEAK)
flushInterval="60000" # 自动刷新时间(毫秒)
size="1024" # 缓存最大条目数
readOnly="true"/> # 只读缓存(性能更高)
注意 :二级缓存基于 Mapper 级别,仅适用于查询频繁、修改较少的场景;实体类需实现 Serializable 接口。
3. 自定义类型处理器
解决特殊类型映射(如 JSON 字段、枚举),示例:将 List<String> 映射为数据库 JSON 字段。
(1)自定义类型处理器
java
运行
package com.example.mybatis.typehandler;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import com.alibaba.fastjson2.JSON;
import java.sql.*;
import java.util.List;
public class ListTypeHandler extends BaseTypeHandler<List<String>> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, JSON.toJSONString(parameter));
}
@Override
public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {
return JSON.parseArray(rs.getString(columnName), String.class);
}
@Override
public List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return JSON.parseArray(rs.getString(columnIndex), String.class);
}
@Override
public List<String> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return JSON.parseArray(cs.getString(columnIndex), String.class);
}
}
(2)配置类型处理器
yaml
mybatis:
type-handlers-package: com.example.mybatis.typehandler # 扫描自定义类型处理器
(3)使用示例(XML)
xml
<resultMap id="BaseResultMap" type="User">
<result column="tags" property="tags" typeHandler="com.example.mybatis.typehandler.ListTypeHandler"/>
</resultMap>
四、常见问题与最佳实践
1. 常见问题
| 问题场景 | 解决方案 |
|---|---|
| 字段名与属性名不一致 | 开启 map-underscore-to-camel-case 或自定义 resultMap |
| SQL 注入风险 | 使用 #{}(预编译),避免 ${}(直接拼接);参数校验 |
| 关联查询性能差 | 按需使用 association/collection,避免 N+1 查询(可通过 fetchType="lazy" 懒加载) |
| 分页查询繁琐 | 集成 PageHelper 插件 |
| 主键回填失败 | 确保 useGeneratedKeys="true" + keyProperty 绑定正确,数据库主键自增 |
2. 最佳实践
- SQL 管理 :复杂 SQL 写 XML,简单 SQL 用注解(如
@Select) - 参数传递 :多参数使用
@Param注解,避免 Map 传递(可读性差) - 结果映射 :统一使用
resultMap,避免重复配置 - 事务控制 :在 Service 层加
@Transactional,指定rollbackFor = Exception.class - 日志调试:开启 MyBatis SQL 日志,便于定位问题
- 性能优化 :
- 避免全表扫描,给查询字段加索引
- 合理使用缓存(一级缓存默认开启,二级缓存按需开启)
- 批量操作使用
foreach+BatchExecutor
五、总结
MyBatis 与 Spring Boot 的整合核心是自动配置 + 简化开发:
- Spring Boot 自动创建
SqlSessionFactory、SqlSession,无需手动管理 - 通过
@Mapper或@MapperScan扫描 Mapper 接口,动态代理生成实现类 - 结合 XML / 注解灵活编写 SQL,适配各类业务场景
掌握 MyBatis 的核心是理解映射规则 和动态 SQL,而 Spring Boot 集成则重点关注配置简化、插件扩展和事务管理。在实际开发中,需结合业务场景选择合适的用法(XML / 注解、缓存策略、分页方式),兼顾灵活性和性能。