Mybatis学习笔记
MyBatis 深入学习笔记
一、MyBatis 核心概念
1.1 什么是 MyBatis
MyBatis 是一款优秀的持久层半自动化 ORM 框架。
ORM (Object Relational Mapping) 对象关系映射:
- O:Object,Java 对象
- R:Relational,关系型数据库中的记录
- M:Mapping,映射关系
实现 Java 对象与数据库记录之间的双向自动映射,开发者无需手动转换数据。
1.2 半自动化特性
- 全自动框架(如 Hibernate):适合单表操作
- 半自动框架(如 MyBatis):适合复杂多表操作
MyBatis 的特点:
- SQL 语句需要手动编写,但参数映射和结果集映射自动完成
- 提供了强大的动态 SQL 功能
- 支持存储过程和高级映射
1.3 MyBatis 架构图
应用程序
↓
MyBatis API
↓
SqlSessionFactory
↓
SqlSession
↓
Executor(执行器)
↓
MappedStatement(映射语句)
↓
数据库
二、快速入门
2.1 环境搭建步骤
1. 创建 Maven/Gradle 项目
2. 添加 MyBatis 依赖
3. 创建核心配置文件 mybatis-config.xml
4. 创建实体类(POJO)
5. 创建 Mapper 接口
6. 创建 SQL 映射文件
7. 编写测试代码
2.2 Maven 依赖配置
<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>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2.3 核心配置文件示例
<?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>
<!-- 1. 引入外部属性文件 -->
<properties resource="jdbc.properties"/>
<!-- 2. 设置 -->
<settings>
<!-- 开启驼峰命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
<!-- 3. 类型别名 -->
<typeAliases>
<package name="com.example.entity"/>
</typeAliases>
<!-- 4. 环境配置 -->
<environments default="development">
<environment id="development">
<!-- 事务管理器 -->
<transactionManager type="JDBC"/>
<!-- 数据源 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- 5. 映射器配置 -->
<mappers>
<package name="com.example.mapper"/>
</mappers>
</configuration>
2.4 基础使用示例
// 1. 加载配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 2. 创建 SqlSessionFactory
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
// 3. 获取 SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
// 4. 获取 Mapper 代理对象
UserMapper userMapper = session.getMapper(UserMapper.class);
// 5. 执行查询
User user = userMapper.selectById(1);
System.out.println(user);
// 6. 提交事务(增删改需要)
session.commit();
}
三、SQL 映射文件详解
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">
<!-- SQL 语句定义 -->
</mapper>
3.2 参数传递的三种方式
方式一:使用 Map 传递参数
// Mapper 接口
List<User> selectByMap(Map<String, Object> params);
// 映射文件
<select id="selectByMap" parameterType="map" resultType="User">
SELECT * FROM users
WHERE name LIKE #{name}
AND age = #{age}
</select>
// 使用
Map<String, Object> params = new HashMap<>();
params.put("name", "%张%");
params.put("age", 20);
List<User> users = userMapper.selectByMap(params);
方式二:使用 @Param 注解
// Mapper 接口
List<User> selectByCondition(
@Param("name") String name,
@Param("age") Integer age
);
// 映射文件
<select id="selectByCondition" resultType="User">
SELECT * FROM users
WHERE name LIKE #{name}
AND age = #{age}
</select>
方式三:使用默认参数名
// Mapper 接口
List<User> selectByCondition(String name, Integer age);
// 映射文件
<select id="selectByCondition" resultType="User">
SELECT * FROM users
WHERE name LIKE #{arg0} <!-- 或 #{param1} -->
AND age = #{arg1} <!-- 或 #{param2} -->
</select>
3.3 #{} 和 ${} 的区别
| 特性 | #{} |
${} |
|---|---|---|
| 安全性 | 预编译,防止 SQL 注入 | 直接拼接,有 SQL 注入风险 |
| 使用场景 | 参数值传递 | 动态表名、列名等 |
| 处理方式 | 添加引号,类型安全 | 直接替换字符串 |
| 性能 | 一次编译,多次执行 | 每次重新编译 |
<!-- 安全的使用方式 -->
SELECT * FROM #{tableName} WHERE id = #{id}
<!-- 危险的使用方式(有SQL注入风险) -->
SELECT * FROM users WHERE name = '${name}'
四、结果集映射
4.1 自动映射
<select id="selectAll" resultType="User">
SELECT user_id, user_name, user_email FROM users
</select>
4.2 resultMap 手动映射
<resultMap id="userResultMap" type="User">
<!-- 主键映射 -->
<id property="id" column="user_id"/>
<!-- 普通字段映射 -->
<result property="username" column="user_name"/>
<result property="email" column="user_email"/>
<result property="createTime" column="create_time"/>
<!-- 一对一关联映射 -->
<association property="department" javaType="Department">
<id property="id" column="dept_id"/>
<result property="deptName" column="dept_name"/>
</association>
<!-- 一对多关联映射 -->
<collection property="roles" ofType="Role">
<id property="id" column="role_id"/>
<result property="roleName" column="role_name"/>
</collection>
</resultMap>
<select id="selectUserWithDetails" resultMap="userResultMap">
SELECT u.*, d.*, r.*
FROM users u
LEFT JOIN department d ON u.dept_id = d.dept_id
LEFT JOIN user_role ur ON u.user_id = ur.user_id
LEFT JOIN roles r ON ur.role_id = r.role_id
</select>
4.3 驼峰命名映射
<!-- 在配置文件中开启 -->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- 使用示例 -->
<select id="selectUser" resultType="User">
SELECT user_name, user_email, create_time
FROM users
<!-- 自动映射到 userName, userEmail, createTime -->
</select>
五、动态 SQL
5.1 if 条件判断
<select id="findUsers" parameterType="map" resultType="User">
SELECT * FROM users
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
<if test="email != null">
AND email = #{email}
</if>
</where>
ORDER BY id DESC
</select>
5.2 choose-when-otherwise
<select id="findActiveUsers" parameterType="map" resultType="User">
SELECT * FROM users
<where>
<choose>
<when test="status == 'active'">
AND status = 1
</when>
<when test="status == 'inactive'">
AND status = 0
</when>
<otherwise>
AND status IN (0, 1)
</otherwise>
</choose>
</where>
</select>
5.3 foreach 循环
<!-- 批量查询 -->
<select id="selectByIds" parameterType="list" resultType="User">
SELECT * FROM users
WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 批量插入 -->
<insert id="batchInsert" parameterType="list">
INSERT INTO users(name, age, email) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age}, #{user.email})
</foreach>
</insert>
5.4 set 更新
<update id="updateUser" parameterType="User">
UPDATE users
<set>
<if test="name != null">name = #{name},</if>
<if test="age != null">age = #{age},</if>
<if test="email != null">email = #{email},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</set>
WHERE id = #{id}
</update>
5.5 trim 自定义修剪
<select id="dynamicQuery" parameterType="map" resultType="User">
SELECT * FROM users
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null">
AND name = #{name}
</if>
<if test="age != null">
OR age = #{age}
</if>
</trim>
</select>
5.6 bind 创建变量
<select id="searchUsers" parameterType="string" resultType="User">
<bind name="pattern" value="'%' + keyword + '%'"/>
SELECT * FROM users
WHERE name LIKE #{pattern}
OR email LIKE #{pattern}
</select>
5.7 SQL 片段重用
<!-- 定义 SQL 片段 -->
<sql id="baseColumns">
id, name, age, email, create_time, update_time
</sql>
<sql id="whereConditions">
<where>
<if test="name != null">AND name = #{name}</if>
<if test="age != null">AND age = #{age}</if>
<if test="email != null">AND email = #{email}</if>
</where>
</sql>
<!-- 使用 SQL 片段 -->
<select id="selectUsers" parameterType="map" resultType="User">
SELECT
<include refid="baseColumns"/>
FROM users
<include refid="whereConditions"/>
ORDER BY id DESC
</select>
六、高级特性
6.1 主键回填
<!-- 方式一:useGeneratedKeys -->
<insert id="insertUser" parameterType="User"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO users(name, age, email)
VALUES(#{name}, #{age}, #{email})
</insert>
<!-- 方式二:selectKey -->
<insert id="insertUser" parameterType="User">
<selectKey keyProperty="id" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO users(name, age, email)
VALUES(#{name}, #{age}, #{email})
</insert>
6.2 延迟加载(懒加载)
<!-- 1. 在配置文件中开启 -->
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
<!-- 2. 在映射文件中配置 -->
<resultMap id="userWithOrdersMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- 延迟加载订单信息 -->
<collection property="orders" ofType="Order"
select="com.example.mapper.OrderMapper.selectByUserId"
column="id" fetchType="lazy"/>
</resultMap>
<select id="selectUserWithOrders" resultMap="userWithOrdersMap">
SELECT id, name FROM users WHERE id = #{id}
</select>
6.3 分页查询
// 使用 RowBounds(逻辑分页)
public List<User> selectUsersByPage(int offset, int limit) {
try (SqlSession session = sqlSessionFactory.openSession()) {
RowBounds rowBounds = new RowBounds(offset, limit);
return session.selectList("com.example.mapper.UserMapper.selectAll",
null, rowBounds);
}
}
// 使用 PageHelper(物理分页,推荐)
public PageInfo<User> selectUsersByPage(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll();
return new PageInfo<>(users);
}
七、缓存机制
7.1 一级缓存(本地缓存)
- 作用域:SqlSession 级别
- 生命周期:与 SqlSession 相同
- 默认开启:无需配置
- 失效场景:
- 执行了增删改操作
- 调用了 clearCache() 方法
- 执行了 commit() 或 rollback()
- 不同的 SqlSession 之间不共享
7.2 二级缓存(全局缓存)
7.2.1 配置步骤
<!-- 1. 在配置文件中开启 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 2. 在映射文件中启用 -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 使用默认缓存 -->
<cache/>
<!-- 或使用第三方缓存(如 EhCache) -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
</mapper>
<!-- 3. 实体类需要实现 Serializable 接口 -->
public class User implements Serializable {
// ...
}
7.2.2 缓存配置属性
<cache
eviction="FIFO" <!-- 回收策略:LRU, FIFO, SOFT, WEAK -->
flushInterval="60000" <!-- 刷新间隔(毫秒) -->
size="512" <!-- 最大缓存对象数量 -->
readOnly="true" <!-- 是否只读 -->
/>
7.2.3 控制缓存使用
<!-- 禁用二级缓存 -->
<select id="selectById" resultType="User" useCache="false">
SELECT * FROM users WHERE id = #{id}
</select>
<!-- 刷新缓存 -->
<insert id="insertUser" parameterType="User" flushCache="true">
INSERT INTO users(name, age) VALUES(#{name}, #{age})
</insert>
八、注解开发
8.1 基础 CRUD 注解
@Mapper
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User selectById(@Param("id") Integer id);
@Insert("INSERT INTO users(name, age) VALUES(#{name}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
@Update("UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id}")
int update(User user);
@Delete("DELETE FROM users WHERE id = #{id}")
int deleteById(@Param("id") Integer id);
}
8.2 结果集映射注解
@Results(id = "userResultMap", value = {
@Result(property = "id", column = "user_id", id = true),
@Result(property = "username", column = "user_name"),
@Result(property = "email", column = "user_email"),
@Result(property = "createTime", column = "create_time")
})
@Select("SELECT * FROM users WHERE id = #{id}")
User selectUserWithMap(@Param("id") Integer id);
// 复用结果集映射
@ResultMap("userResultMap")
@Select("SELECT * FROM users WHERE name = #{name}")
List<User> selectUsersByName(@Param("name") String name);
8.3 动态 SQL 注解
// 使用 @SelectProvider
@SelectProvider(type = UserSqlProvider.class, method = "buildSelectSql")
List<User> selectByCondition(UserCondition condition);
// SQL 提供者类
public class UserSqlProvider {
public String buildSelectSql(UserCondition condition) {
return new SQL() {{
SELECT("*");
FROM("users");
if (condition.getName() != null) {
WHERE("name LIKE CONCAT('%', #{name}, '%')");
}
if (condition.getAge() != null) {
WHERE("age = #{age}");
}
ORDER_BY("id DESC");
}}.toString();
}
}
九、执行流程与原理
9.1 MyBatis 执行流程
graph TD
A[加载配置文件] --> B[创建SqlSessionFactory]
B --> C[创建SqlSession]
C --> D[获取Mapper接口代理]
D --> E[执行SQL]
E --> F[返回结果]
9.2 四大核心接口
-
Executor(执行器)
- SimpleExecutor:简单执行器,每次执行都创建 Statement
- ReuseExecutor:重用执行器,重用 Statement
- BatchExecutor:批处理执行器,用于批量操作
-
StatementHandler(语句处理器)
- PreparedStatementHandler:预处理语句处理器
- SimpleStatementHandler:简单语句处理器
- CallableStatementHandler:存储过程处理器
-
ParameterHandler(参数处理器)
- 负责将 Java 对象转换为 JDBC 参数
-
ResultSetHandler(结果集处理器)
- 负责将 JDBC 结果集转换为 Java 对象
9.3 插件机制
// 自定义插件示例
@Intercepts({
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class})
})
public class MyPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 执行前处理
System.out.println("Before query...");
// 执行原方法
Object result = invocation.proceed();
// 执行后处理
System.out.println("After query...");
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 设置插件属性
}
}
十、最佳实践与优化
10.1 SQL 优化建议
- **避免使用 SELECT ***:明确指定需要的列
- 合理使用索引:为查询条件创建索引
- 批量操作:使用 foreach 进行批量插入/更新
- 分页查询:使用物理分页而非逻辑分页
- 避免 N+1 查询:使用关联查询或延迟加载
10.2 配置优化
<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 开启懒加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 开启驼峰命名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 设置默认执行器 -->
<setting name="defaultExecutorType" value="REUSE"/>
<!-- 设置超时时间 -->
<setting name="defaultStatementTimeout" value="30"/>
</settings>
10.3 事务管理
// 编程式事务管理
SqlSession session = sqlSessionFactory.openSession();
try {
UserMapper mapper = session.getMapper(UserMapper.class);
// 执行多个操作
mapper.insert(user1);
mapper.insert(user2);
// 提交事务
session.commit();
} catch (Exception e) {
// 回滚事务
session.rollback();
throw e;
} finally {
// 关闭会话
session.close();
}
十一、常见问题与解决方案
11.1 常见异常及解决
-
BindingException
// 原因:Mapper接口与XML映射文件不匹配 // 解决:检查namespace、id、parameterType、resultType是否一致 -
TooManyResultsException
// 原因:查询结果多于一个,但期望单个结果 // 解决:使用selectOne或修改查询条件 -
TypeException
// 原因:类型转换失败 // 解决:检查数据库字段与Java属性类型是否匹配
11.2 性能优化技巧
- 使用连接池:配置合适的连接池参数
- 缓存策略:根据业务需求选择合适的缓存策略
- SQL 监控:使用日志或监控工具分析SQL性能
- 批量处理:大量数据操作时使用批量模式
- 索引优化:为频繁查询的字段创建索引
总结
MyBatis 是一个功能强大且灵活的持久层框架,通过合理的配置和使用,可以大大提高开发效率和系统性能。掌握 MyBatis 的核心概念、动态 SQL、缓存机制和高级特性,对于构建高性能的 Java 应用至关重要。
关键要点回顾:
- MyBatis 是半自动化 ORM 框架,平衡了灵活性和开发效率
- 动态 SQL 是 MyBatis 的核心特性,支持复杂查询条件
- 合理的缓存配置可以显著提升系统性能
- 注解和 XML 配置各有优势,可根据项目需求选择
- 理解 MyBatis 的执行流程和原理有助于排查问题和优化性能
持续关注 MyBatis 的更新和最佳实践,结合实际项目需求进行灵活应用,才能充分发挥 MyBatis 的优势。

浙公网安备 33010602011771号