外观
MyBatis 笔记
约 2063 字大约 7 分钟
2025-08-16
一、MyBatis 简介
1.1 什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(普通老式 Java 对象)为数据库中的记录。
1.2 MyBatis 与传统 JDBC 和 Hibernate 的比较
| 对比项 | JDBC | Hibernate | MyBatis |
|---|---|---|---|
| SQL 控制 | 完全手动编写 | 完全自动生成 | 手动编写,灵活控制 |
| 学习曲线 | 简单 | 复杂 | 中等 |
| 开发效率 | 低 | 高 | 中高 |
| SQL 优化 | 完全可控 | 较难 | 完全可控 |
| 适用场景 | 简单应用 | 快速开发,标准 CRUD | 需要精细控制 SQL 的场景 |
1.3 MyBatis 的优势
- 轻量级:性能出色,无侵入性
- SQL 和 Java 编码分离:功能边界清晰,Java 代码专注业务逻辑,SQL 语句专注数据操作
- 灵活的 SQL 控制:可以精确控制每一条 SQL 语句
- 易于维护:SQL 集中管理,便于优化和修改
- 与 Spring 完美集成:成为 Spring Boot 默认的持久层框架
二、MyBatis 核心配置
2.1 核心配置文件 mybatis-config.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>
<!-- 引入外部配置文件 -->
<properties resource="jdbc.properties"/>
<!-- 设置 -->
<settings>
<!-- 将表中字段的下划线自动转换为驼峰 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
<!-- 类型别名 -->
<typeAliases>
<!-- 为单个类设置别名 -->
<!-- <typeAlias type="com.example.User" alias="User"/> -->
<!-- 为整个包下的类设置别名 -->
<package name="com.example.bean"/>
</typeAliases>
<!-- 环境配置 -->
<environments default="development">
<environment id="development">
<!-- 事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 数据源 -->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!-- 映射器 -->
<mappers>
<!-- 文件引入 -->
<!-- <mapper resource="com/example/mapper/UserMapper.xml"/> -->
<!-- 包名引入 -->
<package name="com.example.mapper"/>
</mappers>
</configuration>2.2 配置要点
- properties:用于引入外部配置文件,如数据库连接信息
- settings:全局配置,常用配置:
mapUnderscoreToCamelCase:自动将下划线命名转换为驼峰命名lazyLoadingEnabled:开启延迟加载
- typeAliases:为 Java 类型设置别名,简化映射文件中的类型引用
- environments:配置数据库环境,包括事务管理器和数据源
- mappers:指定映射文件的位置
三、Mapper 映射文件
3.1 映射文件结构
<?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.mapper.UserMapper">
<!-- 增删改查操作 -->
</mapper>关键点:
namespace必须与对应的 Mapper 接口全限定名一致- 一个映射文件对应一个实体类,对应一张表的操作
3.2 基本 CRUD 操作
<!-- 查询一个 -->
<select id="getUserById" resultType="User">
SELECT * FROM t_user WHERE id = #{id}
</select>
<!-- 查询集合 -->
<select id="getUserList" resultType="User">
SELECT * FROM t_user
</select>
<!-- 插入 -->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_user(username, password, age, sex, email)
VALUES(#{username}, #{password}, #{age}, #{sex}, #{email})
</insert>
<!-- 更新 -->
<update id="updateUser">
UPDATE t_user
SET username = #{username}, password = #{password}
WHERE id = #{id}
</update>
<!-- 删除 -->
<delete id="deleteUser">
DELETE FROM t_user WHERE id = #{id}
</delete>参数说明:
#{}:预编译处理,防止 SQL 注入useGeneratedKeys:是否使用自动生成的主键keyProperty:指定主键对应的属性名
3.3 结果映射
当数据库字段名与 Java 属性名不一致时,需要使用 resultMap:
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id"/>
<result property="username" column="user_name"/>
<result property="password" column="user_password"/>
<result property="age" column="user_age"/>
<result property="sex" column="user_sex"/>
<result property="email" column="user_email"/>
</resultMap>
<select id="getUserById" resultMap="userResultMap">
SELECT * FROM t_user WHERE user_id = #{id}
</select>四、动态 SQL
4.1 if 标签
<select id="findUserList" resultType="User">
SELECT * FROM t_user
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
</where>
</select>4.2 where 标签
where 标签会自动处理 SQL 语句中的 AND 或 OR 开头问题:
<select id="findUserList" resultType="User">
SELECT * FROM t_user
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
</where>
</select>4.3 foreach 标签
用于遍历集合或数组:
<!-- IN 查询 -->
<select id="selectByIds" resultType="User">
SELECT * FROM t_user
<where>
<if test="ids != null">
id IN
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
</select>
<!-- 批量插入 -->
<insert id="batchInsert">
INSERT INTO t_user(username, password)
VALUES
<foreach collection="users" item="user" separator=",">
(#{user.username}, #{user.password})
</foreach>
</insert>4.4 choose/when/otherwise 标签
相当于 Java 中的 switch 语句:
<select id="selectByIdOrUserName" resultType="User">
SELECT * FROM t_user
<where>
<choose>
<when test="id != null">
AND id = #{id}
</when>
<when test="username != null and username != ''">
AND username = #{username}
</when>
<otherwise>
AND 1 = 2
</otherwise>
</choose>
</where>
</select>五、MyBatis 缓存机制
5.1 一级缓存
- 作用范围:SqlSession 级别
- 默认开启:无需配置
- 生命周期:与 SqlSession 相同,SqlSession 关闭后缓存失效
- 工作原理:在同一个 SqlSession 中,多次执行相同的 SQL 查询,第一次查询数据库并将结果存入缓存,后续查询直接从缓存获取
注意事项:
- 执行 DML 操作(INSERT、UPDATE、DELETE)后,一级缓存会被清空
- 不同 SqlSession 之间的一级缓存是相互隔离的
5.2 二级缓存
- 作用范围:Mapper(namespace)级别
- 默认关闭:需要手动配置开启
- 生命周期:与 SqlSessionFactory 相同
配置步骤:
- 在核心配置文件中开启二级缓存:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>- 在 Mapper 映射文件中添加
<cache/>标签:
<mapper namespace="com.example.mapper.UserMapper">
<cache/>
<!-- 其他映射语句 -->
</mapper>- 实体类实现 Serializable 接口:
public class User implements Serializable {
// 属性和方法
}二级缓存配置参数:
<cache
eviction="LRU" <!-- 清除策略 -->
flushInterval="60000" <!-- 自动刷新时间(ms) -->
size="512" <!-- 最大缓存条目 -->
readOnly="true"/> <!-- 是否只读 -->缓存查询顺序:二级缓存 → 一级缓存 → 数据库
六、分页插件 PageHelper
6.1 集成与配置
- 添加 Maven 依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.0</version>
</dependency>- 在 mybatis-config.xml 中配置插件:
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 分页参数合理化 -->
<property name="reasonable" value="true"/>
</plugin>
</plugins>6.2 基本使用
// 开始分页,第1页,每页10条记录
PageHelper.startPage(1, 10);
// 执行查询,MyBatis 会自动进行分页
List<User> userList = userMapper.selectAll();
// 获取分页信息
PageInfo<User> pageInfo = new PageInfo<>(userList);
System.out.println("当前页: " + pageInfo.getPageNum());
System.out.println("每页条数: " + pageInfo.getPageSize());
System.out.println("总条数: " + pageInfo.getTotal());
System.out.println("总页数: " + pageInfo.getPages());分页参数说明:
PageHelper.startPage(pageNum, pageSize):设置分页参数PageInfo:封装分页结果,提供分页相关信息
七、MyBatis 与 Spring Boot 集成
7.1 依赖配置
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis Spring Boot Starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>7.2 配置文件
# application.properties
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# MyBatis 配置
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.mapper-locations=classpath:mapper/*.xml7.3 注解使用
Mapper 接口:
@Mapper
public interface UserMapper {
@Select("SELECT * FROM t_user WHERE id = #{id}")
User getUserById(Long id);
@Insert("INSERT INTO t_user(username, password) VALUES(#{username}, #{password})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);
@Update("UPDATE t_user SET username=#{username} WHERE id=#{id}")
int updateUser(User user);
@Delete("DELETE FROM t_user WHERE id=#{id}")
int deleteUser(Long id);
}Service 层:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User getUserById(Long id) {
return userMapper.getUserById(id);
}
public int createUser(User user) {
return userMapper.insertUser(user);
}
}八、最佳实践
8.1 常见问题解决
1. 字段名与属性名不匹配
- 解决方案:使用
mapUnderscoreToCamelCase=true配置,或使用resultMap映射
2. N+1 查询问题
- 问题:一对多查询时,每查一条主记录,就会执行一次关联查询
- 解决方案:使用
JOIN查询一次性获取所有数据,或使用延迟加载
3. 事务管理
- Spring Boot 中使用
@Transactional注解管理事务
@Service
public class UserService {
@Transactional
public void createUserAndLog(User user, Log log) {
userMapper.insertUser(user);
logMapper.insertLog(log);
// 两个操作在一个事务中
}
}8.2 性能优化建议
合理使用缓存:
- 对于查询频繁、更新较少的数据,开启二级缓存
- 设置合理的缓存大小和过期时间
SQL 优化:
- 避免使用
SELECT *,只查询需要的字段 - 复杂查询使用
JOIN代替多次查询 - 为常用查询条件添加索引
- 避免使用
批量操作:
- 使用
foreach标签实现批量插入/更新 - 对于大量数据,考虑使用 MyBatis 的批处理功能
- 使用
// 批量插入示例
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
for (User user : userList) {
mapper.insertUser(user);
}
sqlSession.commit();
} finally {
sqlSession.close();
}