1、什么是 MyBatis
一、MyBatis 核心定义
MyBatis 是一款优秀的持久层(ORM)框架 ,前身是 Apache 的 iBatis,2010 年迁移到 Google Code 并更名为 MyBatis,现属于 Apache 顶级项目。它彻底简化了 JDBC 冗余代码,通过 XML 或注解的方式,将 SQL 语句与 Java 对象做映射,让开发者可以专注于 SQL 本身,同时实现数据库的灵活操作。
二、核心特点
✅ SQL 与代码解耦 :SQL 写在 XML 中,与 Java 业务代码分离,便于维护、调优和权限控制✅ 灵活的 SQL 支持 :支持自定义 SQL、存储过程、多表关联、动态 SQL,适配复杂业务场景✅ 简化 JDBC 操作 :自动处理连接、Statement、ResultSet 封装,无需手动编写冗余代码✅ ORM 映射能力 :通过 ResultMap 实现数据库字段与 Java 对象属性的自动映射✅ 轻量无侵入 :无第三方依赖,学习成本低,完美适配 SSM、SpringBoot 等主流框架✅ 性能优异:原生 JDBC 封装,无额外性能损耗,支持连接池、缓存优化
三、MyBatis 与其他持久层技术对比
| 技术 | 核心优势 | 核心劣势 | 适用场景 |
|---|---|---|---|
| 原生 JDBC | 性能最高、无依赖 | 代码冗余、SQL 耦合、手动处理连接 | 简单工具类、性能极致要求场景 |
| MyBatis | SQL 灵活、易优化、轻量 | 需手动编写 SQL(部分场景) | 企业级项目、复杂查询、SSM 架构 |
| JPA/Hibernate | 全自动 ORM、无需写 SQL | SQL 不灵活、调优困难、学习成本高 | 快速开发、简单 CRUD 项目 |
| JdbcTemplate | Spring 内置、简单轻量 | 动态 SQL 支持弱、复杂查询不便 | Spring 项目简单数据库操作 |
四、核心架构
┌─────────────────────────────────────────────────────────┐
│ 应用层(Java代码):调用 MyBatis API,执行业务逻辑 │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ MyBatis 核心层:SqlSessionFactory、SqlSession、Executor │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ 映射层:Mapper 接口 + XML/注解,定义 SQL 与映射规则 │
└───────────────────────────┬─────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────┐
│ 数据库层:MySQL/Oracle 等,执行 SQL 语句 │
└─────────────────────────────────────────────────────────┘
2、第一个 MyBatis 程序
一、环境准备
- 开发工具:IDEA、Maven 3.8+、MySQL 8.0+
- 核心依赖 (
pom.xml):
xml
<dependencies>
<!-- MyBatis 核心依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<scope>runtime</scope>
</dependency>
<!-- 日志依赖(调试必备) -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- JUnit 测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 解决 Maven 资源过滤问题(XML 配置文件无法加载) -->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
二、步骤 1:创建数据库与表
sql
CREATE DATABASE IF NOT EXISTS mybatis_demo CHARACTER SET utf8mb4;
USE mybatis_demo;
CREATE TABLE `user` (
`id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
`username` VARCHAR(50) NOT NULL COMMENT '用户名',
`password` VARCHAR(50) NOT NULL COMMENT '密码',
`email` VARCHAR(50) COMMENT '邮箱',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 插入测试数据
INSERT INTO `user` (username, password, email) VALUES
('admin', '123456', 'admin@example.com'),
('test', '654321', 'test@example.com');
三、步骤 2:编写实体类(POJO)
package com.example.pojo;
import java.util.Date;
// 实体类:对应数据库 user 表
public class User {
// 属性名建议与数据库字段名一致(不一致需做映射)
private Integer id;
private String username;
private String password;
private String email;
private Date createTime;
// 必须有无参构造(MyBatis 反射创建对象使用)
public User() {}
// 有参构造(可选)
public User(Integer id, String username, String password, String email, Date createTime) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.createTime = createTime;
}
// getter/setter 方法(必须)
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Date getCreateTime() { return createTime; }
public void setCreateTime(Date createTime) { this.createTime = createTime; }
// toString 方法(方便打印)
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
", createTime=" + createTime +
'}';
}
}
四、步骤 3:编写 MyBatis 核心配置文件
在 src/main/resources 下创建 mybatis-config.xml:
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 环境配置:可配置多套环境(开发/测试/生产) -->
<environments default="development">
<environment id="development">
<!-- 事务管理器:JDBC 表示手动管理事务 -->
<transactionManager type="JDBC"/>
<!-- 数据源:POOLED 表示使用 MyBatis 内置连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 映射器:注册 SQL 映射文件 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
五、步骤 4:编写 Mapper 映射文件
在 src/main/resources/mapper 下创建 UserMapper.xml:
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace:命名空间,必须唯一,建议写 Mapper 接口全类名 -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 查询单个用户:id 为唯一标识,resultType 为返回值类型 -->
<select id="getUserById" resultType="com.example.pojo.User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
六、步骤 5:编写工具类(SqlSession 工厂)
package com.example.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
// MyBatis 工具类:获取 SqlSession
public class MyBatisUtils {
// 静态工厂:全局唯一
private static SqlSessionFactory sqlSessionFactory;
static {
try {
// 1. 加载核心配置文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 2. 构建 SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
// 抛出异常,避免工厂创建失败
throw new RuntimeException("初始化 SqlSessionFactory 失败!", e);
}
}
/**
* 获取 SqlSession(默认手动提交事务)
*/
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
/**
* 获取 SqlSession(自动提交事务)
*/
public static SqlSession getSqlSession(boolean autoCommit) {
return sqlSessionFactory.openSession(autoCommit);
}
}
七、步骤 6:编写测试类
package com.example.test;
import com.example.pojo.User;
import com.example.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class MyBatisTest {
@Test
public void testGetUserById() {
// 1. 获取 SqlSession
SqlSession sqlSession = MyBatisUtils.getSqlSession();
try {
// 2. 执行 SQL:namespace + id
User user = sqlSession.selectOne("com.example.mapper.UserMapper.getUserById", 1);
// 3. 打印结果
System.out.println(user);
} finally {
// 4. 关闭资源(必须)
sqlSession.close();
}
}
}
八、运行结果
text
User{id=1, username='admin', password='123456', email='admin@example.com', createTime=...}
✅ 第一个 MyBatis 程序运行成功!
3、增删改查实现(CRUD)
一、核心 API 说明
| 操作 | 核心方法 | 标签 | 说明 |
|---|---|---|---|
| 查询单个 | selectOne() |
<select> |
返回单个对象 |
| 查询列表 | selectList() |
<select> |
返回集合 |
| 新增 | insert() |
<insert> |
执行 INSERT 语句 |
| 修改 | update() |
<update> |
执行 UPDATE 语句 |
| 删除 | delete() |
<delete> |
执行 DELETE 语句 |
二、Mapper 映射文件(UserMapper.xml 完整 CRUD)
xml
<mapper namespace="com.example.mapper.UserMapper">
<!-- 1. 查询单个用户 -->
<select id="getUserById" resultType="com.example.pojo.User">
SELECT * FROM user WHERE id = #{id}
</select>
<!-- 2. 查询所有用户 -->
<select id="getUserList" resultType="com.example.pojo.User">
SELECT * FROM user
</select>
<!-- 3. 新增用户 -->
<insert id="addUser" parameterType="com.example.pojo.User">
INSERT INTO user (username, password, email)
VALUES (#{username}, #{password}, #{email})
</insert>
<!-- 4. 修改用户 -->
<update id="updateUser" parameterType="com.example.pojo.User">
UPDATE user
SET username=#{username}, password=#{password}, email=#{email}
WHERE id=#{id}
</update>
<!-- 5. 删除用户 -->
<delete id="deleteUser" parameterType="int">
DELETE FROM user WHERE id=#{id}
</delete>
</mapper>
三、CRUD 测试类
package com.example.test;
import com.example.pojo.User;
import com.example.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class CRUDTest {
// 1. 查询所有用户
@Test
public void testGetUserList() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
try {
List<User> userList = sqlSession.selectList("com.example.mapper.UserMapper.getUserList");
userList.forEach(System.out::println);
} finally {
sqlSession.close();
}
}
// 2. 新增用户
@Test
public void testAddUser() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
try {
User user = new User(null, "zhangsan", "123456", "zhangsan@example.com", null);
int rows = sqlSession.insert("com.example.mapper.UserMapper.addUser", user);
// 增删改必须手动提交事务!
sqlSession.commit();
System.out.println("新增成功,影响行数:" + rows);
} finally {
sqlSession.close();
}
}
// 3. 修改用户
@Test
public void testUpdateUser() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
try {
User user = new User(3, "zhangsan_update", "654321", "zhangsan_update@example.com", null);
int rows = sqlSession.update("com.example.mapper.UserMapper.updateUser", user);
sqlSession.commit();
System.out.println("修改成功,影响行数:" + rows);
} finally {
sqlSession.close();
}
}
// 4. 删除用户
@Test
public void testDeleteUser() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
try {
int rows = sqlSession.delete("com.example.mapper.UserMapper.deleteUser", 3);
sqlSession.commit();
System.out.println("删除成功,影响行数:" + rows);
} finally {
sqlSession.close();
}
}
}
四、核心注意事项
⚠️ 增删改必须提交事务 :MyBatis 默认关闭自动提交,执行 insert/update/delete 后必须调用 sqlSession.commit(),否则数据不会写入数据库!✅ 也可以在获取 SqlSession 时开启自动提交:MyBatisUtils.getSqlSession(true)
4、错误排查指导
一、常见错误与解决方案
| 错误现象 | 根本原因 | 解决方案 |
|---|---|---|
Invalid bound statement (not found) |
Mapper XML 与接口不匹配、namespace/id 错误、XML 未被加载 | 1. 检查 namespace 全类名正确;2. 保证接口方法名与 XML id 一致;3. 配置 Maven 资源过滤,确保 XML 被编译 |
ClassNotFoundException: com.mysql.cj.jdbc.Driver |
驱动类名错误、未导入 MySQL 依赖 | 1. MySQL 8.0+ 驱动类为 com.mysql.cj.jdbc.Driver;2. 检查 pom.xml 依赖是否正确 |
Access denied for user 'root'@'localhost' |
数据库账号 / 密码错误、URL 错误 | 检查 mybatis-config.xml 中的 username、password、url 配置 |
SQLException: Column 'xxx' not found |
数据库字段与实体类属性名不一致 | 1. 起别名;2. 使用 ResultMap 做映射;3. 开启驼峰命名自动映射 |
SQLSyntaxErrorException |
SQL 语句语法错误 | 开启日志,查看实际执行的 SQL,定位语法问题 |
Transaction rolled back |
事务未提交 / 回滚 | 增删改后必须调用 commit(),或开启自动提交 |
Maven 打包后 XML 丢失 |
Maven 默认过滤 Java 目录下的 XML 文件 | 在 pom.xml 中配置资源加载,包含 Java 目录下的 XML |
二、调试神器:开启 Log4j 日志
- 在
src/main/resources下创建log4j.properties:
properties
# 日志级别:DEBUG 可查看完整 SQL、参数、执行结果
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
- 作用:实时查看 MyBatis 执行的 SQL 语句、入参、结果,90% 的错误都能通过日志快速定位!
三、排查流程
- 开启日志,查看执行的 SQL 是否正确
- 复制 SQL 到 Navicat 手动执行,验证 SQL 语法
- 检查参数传递是否正确,实体类属性是否匹配
- 检查配置文件是否加载,Mapper 映射是否正确
- 检查事务是否提交,连接是否正常
5、Map 和模糊查询拓展
一、Map 传参:灵活传递参数
当实体类字段不足、或临时查询无需创建实体类时,使用 Map 传递参数,无需修改实体类。
1. Mapper XML
xml
<!-- Map 传参:根据用户名和密码查询用户 -->
<select id="getUserByMap" resultType="com.example.pojo.User">
SELECT * FROM user WHERE username = #{username} AND password = #{password}
</select>
2. 测试代码
@Test
public void testGetUserByMap() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
try {
Map<String, Object> map = new HashMap<>();
map.put("username", "admin");
map.put("password", "123456");
User user = sqlSession.selectOne("com.example.mapper.UserMapper.getUserByMap", map);
System.out.println(user);
} finally {
sqlSession.close();
}
}
二、模糊查询:Like 语句实现
方式 1:Java 代码拼接通配符(推荐,防 SQL 注入)
xml
@Test
public void testGetUserLike() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
try {
// Java 代码中添加 % 通配符
String keyword = "%ad%";
List<User> userList = sqlSession.selectList("com.example.mapper.UserMapper.getUserLike", keyword);
userList.forEach(System.out::println);
} finally {
sqlSession.close();
}
}
方式 2:XML 中拼接通配符(不推荐,有 SQL 注入风险)
xml
<select id="getUserLike" resultType="com.example.pojo.User">
SELECT * FROM user WHERE username LIKE '%${value}%'
</select>
⚠️ 注意:${} 会直接拼接字符串,存在 SQL 注入风险,仅在特殊场景使用;#{} 是预编译,安全无注入。
三、多条件查询拓展
-
方式 1:实体类传参:封装查询条件到实体类
-
方式 2:Map 传参:灵活传递多个条件
-
方式 3:
@Param注解传参(接口式编程推荐)// Mapper 接口
public interface UserMapper {
User getUserByParam(@Param("username") String username, @Param("password") String password);
}
6、配置之属性优化
一、核心优化:抽离数据库配置到外部文件
将数据库连接信息从 mybatis-config.xml 抽离到 db.properties,实现解耦,便于多环境切换。
1. 创建 db.properties(src/main/resources)
properties
# 数据库连接配置
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
jdbc.username=root
jdbc.password=root
2. 修改 mybatis-config.xml,引入配置文件
xml
<configuration>
<!-- 1. 引入外部 properties 配置文件 -->
<properties resource="db.properties"/>
<!-- 2. 使用 ${} 引用配置 -->
<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>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
二、其他核心配置优化
1. 开启驼峰命名自动映射
数据库字段 user_name 自动映射到实体类属性 userName,无需手动写 ResultMap:
xml
<configuration>
<settings>
<!-- 开启驼峰命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 开启日志 -->
<setting name="logImpl" value="LOG4J"/>
</settings>
<!-- 其他配置... -->
</configuration>
2. 配置多套环境(开发 / 测试 / 生产)
xml
<environments default="development">
<!-- 开发环境 -->
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 开发环境配置 -->
</dataSource>
</environment>
<!-- 测试环境 -->
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 测试环境配置 -->
</dataSource>
</environment>
</environments>
7、配置之别名优化
一、别名的作用
简化 resultType、parameterType 中的全类名书写,无需每次写 com.example.pojo.User,直接写 User 即可。
二、别名配置方式
方式 1:单个别名配置(精准控制)
xml
<configuration>
<typeAliases>
<!-- 为 User 类起别名:User -->
<typeAlias type="com.example.pojo.User" alias="User"/>
</typeAliases>
<!-- 其他配置... -->
</configuration>
✅ 优点:别名自定义,精准控制;缺点:每个类都要配置,繁琐。
方式 2:包扫描别名(批量配置,推荐)
xml
<configuration>
<typeAliases>
<!-- 扫描 com.example.pojo 包下的所有类,别名为类名(首字母大小写不敏感) -->
<package name="com.example.pojo"/>
</typeAliases>
<!-- 其他配置... -->
</configuration>
✅ 优点:批量配置,无需逐个写;缺点:别名默认是类名,不可自定义。
三、别名使用示例
配置别名后,Mapper XML 中可直接写类名,无需全类名:
xml
<!-- 配置前:全类名 -->
<select id="getUserById" resultType="com.example.pojo.User">
SELECT * FROM user WHERE id = #{id}
</select>
<!-- 配置后:直接写别名 -->
<select id="getUserById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
四、MyBatis 内置别名
MyBatis 为常用类型提供了内置别名,无需手动配置
| 类型 | 别名 |
|---|---|
int/Integer |
int/integer |
String |
string |
Map |
map |
List |
list |
Date |
date |
8、配置之映射器说明
一、映射器(Mapper)的作用
映射器是 MyBatis 的核心,用于绑定 SQL 语句与 Java 方法,是 MyBatis 执行 SQL 的入口。
二、映射器的 4 种注册方式
方式 1:resource 方式(推荐,XML 方式)
xml
<mappers>
<!-- 从类路径加载 XML 文件 -->
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
✅ 优点:简单直观,XML 与接口分离;缺点:每个 XML 都要注册。
方式 2:class 方式(注解方式)
xml
<mappers>
<!-- 注册 Mapper 接口,使用注解写 SQL -->
<mapper class="com.example.mapper.UserMapper"/>
</mappers>
✅ 优点:无需 XML,注解写 SQL;缺点:复杂 SQL 维护困难。
方式 3:package 方式(批量注册,推荐)
xml
<mappers>
<!-- 扫描 com.example.mapper 包下的所有 Mapper 接口,自动注册 -->
<package name="com.example.mapper"/>
</mappers>
✅ 优点:批量注册,无需逐个写;要求:Mapper 接口与 XML 文件名一致,且在同一包下。
方式 4:url 方式(不推荐)
xml
<mappers>
<mapper url="file:///D:/mapper/UserMapper.xml"/>
</mappers>
❌ 缺点:硬编码,环境迁移麻烦,仅特殊场景使用。
三、Mapper 接口式编程(企业级开发推荐)
1. 创建 Mapper 接口
package com.example.mapper;
import com.example.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper {
// 查询单个用户
User getUserById(Integer id);
// 查询所有用户
List<User> getUserList();
// 新增用户
int addUser(User user);
// 修改用户
int updateUser(User user);
// 删除用户
int deleteUser(Integer id);
// Map 传参查询
User getUserByMap(@Param("username") String username, @Param("password") String password);
}
2. Mapper XML 绑定接口
xml
<!-- namespace 必须写 Mapper 接口全类名 -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- id 必须与接口方法名一致 -->
<select id="getUserById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
<!-- 其他方法... -->
</mapper>
3. 测试代码
@Test
public void testMapperInterface() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
try {
// 动态代理获取 Mapper 接口实现类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 直接调用接口方法
User user = mapper.getUserById(1);
System.out.println(user);
} finally {
sqlSession.close();
}
}
✅ 优点:面向接口编程,代码更优雅,符合企业级开发规范,是 MyBatis 主流使用方式。
四、核心注意事项
- namespace 必须唯一:建议写 Mapper 接口全类名,保证唯一
- id 必须与接口方法名一致:否则无法绑定
- 参数类型与返回值类型必须匹配 :
parameterType/resultType与接口方法一致 - XML 与接口必须在同一包下(package 注册方式):文件名一致,路径一致
9、生命周期和作用域
一、核心作用域分类
MyBatis 中对象的生命周期直接影响线程安全、资源释放、数据隔离,核心分为 4 个层级:
| 作用域 | 生命周期 | 线程安全 | 核心对象 | 应用场景 |
|---|---|---|---|---|
| SqlSessionFactoryBuilder | 局部变量(方法内) | 安全 | 构建工厂 | 仅用于创建工厂,使用后立即销毁 |
| SqlSessionFactory | 全局唯一(应用级) | 安全 | 连接池、配置 | 贯穿整个应用,负责创建 SqlSession |
| SqlSession | 一次请求 / 方法 | 不安全 | 会话、连接 | 非线程安全,需手动关闭,建议方法内使用 |
| Mapper 接口 | 一次请求(方法内) | 安全 | 数据操作 | 由 SqlSession 动态代理,方法执行后销毁 |
二、详细生命周期解析
1. SqlSessionFactoryBuilder(构建器)
-
生命周期 :方法级,仅在加载配置文件、创建工厂的过程中使用
-
作用 :加载
mybatis-config.xml,构建SqlSessionFactory -
关键点:创建工厂后立即丢弃,无需全局保存,避免占用内存
-
代码示例:
// 局部变量:使用后销毁
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(Resources.getResourceAsStream("mybatis-config.xml"));
2. SqlSessionFactory(工厂)
- 生命周期 :应用级,随应用启动创建,随应用关闭销毁
- 作用 :生产
SqlSession,管理连接池、环境配置 - 关键点 :全局唯一,一个应用只需一个实例,避免重复创建造成资源浪费
- 实现方式 :可通过单例模式、工具类静态代码块实现(参考之前的
MyBatisUtils)
3. SqlSession(会话)
-
生命周期 :请求级 / 方法级,一次请求或一个方法内有效
-
作用:执行 SQL、管理事务、获取 Mapper
-
关键点 :非线程安全! 不能共享,必须方法内创建、手动关闭
-
核心方法 :
openSession():获取会话,默认不自动提交事务commit():提交事务rollback():回滚事务close():关闭会话(必须! 释放数据库连接)
-
代码示例:
// 正确使用方式:try-finally 保证关闭
try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.getUserById(1);
sqlSession.commit();
} // 自动关闭
4. Mapper 接口(映射器)
- 生命周期 :方法级,一次方法调用内有效
- 作用:定义 SQL 入口,封装参数与结果
- 关键点 :由
SqlSession.getMapper()动态代理创建,方法执行结束后销毁 - 注意:Mapper 接口必须与 XML 映射文件、namespace 绑定,且方法名与 SQL id 一致
三、线程安全总结
⚠️ 核心结论:
SqlSessionFactory:线程安全,可全局共享SqlSession:非线程安全,每次请求都要新建Mapper:线程安全,无状态(仅封装方法)
10、ResultMap 结果集映射
一、核心作用
解决数据库字段名与 Java 实体类属性名不一致的问题,实现自定义映射规则。
- 场景:数据库字段
user_name,实体类属性userName;或字段pwd,属性password
二、两种实现方式
方式 1:起别名(简单场景)
在 SQL 语句中直接起别名,使别名与实体类属性名一致:
xml
<select id="getUserById" resultType="User">
<!-- 给字段起别名,与实体类属性一致 -->
SELECT id, username, password AS pwd, email FROM user WHERE id = #{id}
</select>
方式 2:ResultMap 自定义映射(推荐,复杂场景)
定义 ResultMap 标签,手动指定字段与属性的映射关系,支持高级映射(如关联对象、集合)。
1. 定义 ResultMap
xml
<mapper namespace="com.example.mapper.UserMapper">
<!--
id:唯一标识
type:映射的实体类全类名
-->
<resultMap id="UserMap" type="User">
<!-- 主键字段:id 与数据库主键一致 -->
<id property="id" column="id"/>
<!-- 普通字段:property 为实体类属性,column 为数据库字段 -->
<result property="userName" column="user_name"/>
<result property="password" column="pwd"/>
<result property="email" column="email"/>
</resultMap>
<!-- 使用 ResultMap:resultMap 属性引用 id -->
<select id="getUserById" resultMap="UserMap">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
三、高级 ResultMap 应用
1. 关联查询(一对一,如用户 + 角色)
实体类:User 中包含 Role 对象
public class User {
private Integer id;
private String userName;
private Role role; // 关联角色对象
}
Mapper XML :使用 <association> 标签
xml
<resultMap id="UserRoleMap" type="User">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<!-- 关联对象:javaType 指定关联对象类型 -->
<association property="role" javaType="Role">
<id property="id" column="role_id"/>
<result property="roleName" column="role_name"/>
</association>
</resultMap>
2. 关联查询(一对多,如用户 + 订单)
实体类:User 中包含 List<Order> 集合
public class User {
private Integer id;
private String userName;
private List<Order> orderList; // 关联订单集合
}
Mapper XML :使用 <collection> 标签
xml
<resultMap id="UserOrderMap" type="User">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<!-- 关联集合:ofType 指定集合元素类型 -->
<collection property="orderList" ofType="Order">
<id property="id" column="order_id"/>
<result property="orderName" column="order_name"/>
<result property="userId" column="id"/>
</collection>
</resultMap>
四、核心注意事项
✅ ResultMap 优先级高于 resultType✅ 复杂查询(多表关联、嵌套查询)必须使用 ResultMap✅ 数据库字段与实体类属性完全一致时,无需 ResultMap,直接用 resultType
11、日志工厂
一、核心作用
MyBatis 日志工厂用于输出 SQL 语句、执行参数、结果,是调试和排查问题的核心工具。
- 核心日志实现:
SLF4J、LOG4J、LOG4J2、JDK_LOGGING、STDOUT_LOGGING(标准输出)
二、开启标准日志(STDOUT_LOGGING)
最简单的日志方式,直接在配置文件中开启,无需额外依赖。
1. 配置 mybatis-config.xml
xml
<configuration>
<settings>
<!-- 开启标准输出日志 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
</configuration>
2. 运行效果
控制台会打印执行的 SQL、参数、执行结果,示例:
text
==> Preparing: SELECT * FROM user WHERE id = ?
==> Parameters: 1(Integer)
<== Columns: id, user_name, pwd, email
<== Row: 1, admin, 123456, admin@example.com
<== Total: 1
三、日志工厂核心配置
在 mybatis-config.xml 中通过 <setting name="logImpl" value="日志实现类"/> 配置日志工厂:
| 日志实现类 | 说明 | 依赖 |
|---|---|---|
STDOUT_LOGGING |
标准输出(控制台) | 无 |
LOG4J |
Log4j 日志框架 | 需导入 log4j 依赖 |
SLF4J |
简单日志门面 | 需导入 SLF4J 依赖 |
JDK_LOGGING |
JDK 内置日志 | 无 |
12、Log4j 讲解
一、核心概念
Log4j 是 Apache 基金会的开源日志框架,是 MyBatis 最常用的日志实现,支持日志级别、输出格式、输出目的地的灵活配置。
二、使用步骤
1. 导入 Maven 依赖
xml
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2. 配置 log4j.properties
在 src/main/resources 下创建 log4j.properties,配置日志输出规则:
properties
# 1. 配置根日志:级别(DEBUG)、输出源(stdout)
log4j.rootLogger=DEBUG, stdout
# 2. 配置输出源:控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
# 输出格式:PatternLayout
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# 日志格式:%5p 级别左对齐5位,%t 线程名,%m 日志内容,%n 换行
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
# 3. 可选:配置文件输出(将日志写入文件)
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=mybatis.log
log4j.appender.file.MaxFileSize=10MB
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
3. 开启 Log4j 日志
在 mybatis-config.xml 中配置:
xml
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
三、核心日志级别
Log4j 日志级别从低到高:ALL < DEBUG < INFO < WARN < ERROR < FATAL < OFF
- DEBUG:调试级别,输出 SQL、参数(开发必备)
- INFO:信息级别,输出系统运行信息
- WARN:警告级别,输出潜在问题
- ERROR:错误级别,输出异常信息
四、优势
- 灵活配置:可通过配置文件动态调整日志级别
- 多输出目的地:控制台、文件、数据库等
- 高性能:低损耗,不影响系统性能
13、Limit 实现分页
一、核心概念
分页是 Web 开发必备功能,MyBatis 中通过 SQL 的 LIMIT 关键字实现简单分页,适合小数据量场景。
- 核心公式:
SELECT * FROM table LIMIT #{startIndex}, #{pageSize} startIndex:起始索引 =(当前页码 - 1) * 每页条数pageSize:每页显示条数
二、代码实现
1. Mapper 接口
public interface UserMapper {
// 分页查询:startIndex 起始索引,pageSize 每页条数
List<User> getUserByLimit(Map<String, Object> map);
}
2. Mapper XML
xml
<mapper namespace="com.example.mapper.UserMapper">
<select id="getUserByLimit" resultType="User" parameterType="map">
SELECT * FROM user LIMIT #{startIndex}, #{pageSize}
</select>
</mapper>
3. 测试代码
@Test
public void testGetUserByLimit() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 分页参数:第 1 页,每页 2 条
Map<String, Object> map = new HashMap<>();
map.put("startIndex", 0);
map.put("pageSize", 2);
List<User> userList = mapper.getUserByLimit(map);
userList.forEach(System.out::println);
sqlSession.close();
}
三、分页逻辑封装
实际开发中,需封装分页工具类,计算起始索引:
// 计算起始索引
int currentPage = 1; // 当前页码
int pageSize = 10; // 每页条数
int startIndex = (currentPage - 1) * pageSize;
14、RowBounds 分页
一、核心概念
RowBounds 是 MyBatis 提供的内存分页 方式,无需编写 SQL 的 LIMIT,通过 Java 代码实现分页,底层仍使用 LIMIT。
- 优点:与数据库解耦,切换数据库无需修改 SQL
- 缺点:内存分页,先查询所有数据再分页,大数据量下性能极低(不推荐生产使用)
二、代码实现
1. Mapper 接口(无需参数)
public interface UserMapper {
// 无需参数,RowBounds 会在运行时传入
List<User> getUserByRowBounds();
}
2. Mapper XML
xml
<select id="getUserByRowBounds" resultType="User">
SELECT * FROM user
</select>
3. 测试代码
@Test
public void testGetUserByRowBounds() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
// 1. 创建 RowBounds:offset 起始索引,limit 每页条数
RowBounds rowBounds = new RowBounds(0, 2);
// 2. 执行 SQL:传入 RowBounds
List<User> userList = sqlSession.selectList("com.example.mapper.UserMapper.getUserByRowBounds", null, rowBounds);
userList.forEach(System.out::println);
sqlSession.close();
}
三、与 Limit 分页对比
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Limit 分页 | 性能高,数据库层面分页 | 与 SQL 耦合 | 生产环境推荐 |
| RowBounds | 解耦,无需修改 SQL | 性能低,内存分页 | 简单测试、小数据量 |
15、使用注解开发(完整版)
MyBatis 提供了一套基于注解的开发方式,可以完全不写 XML 映射文件,直接在 Mapper 接口方法上写 SQL,适合简单 CRUD、快速开发场景。
15.1 核心常用注解
@Select:查询@Insert:新增@Update:修改@Delete:删除@Param:参数命名(多参数必用)@Results、@Result:替代 ResultMap 做字段映射@One、@Many:一对一、一对多关联查询
15.2 纯注解 CRUD 示例
1. 编写 Mapper 接口
package com.example.mapper;
import com.example.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
// 根据ID查询
@Select("select * from user where id = #{id}")
User getUserById(@Param("id") Integer id);
// 查询全部
@Select("select * from user")
List<User> getUserList();
// 新增用户
@Insert("insert into user(username,password,email) " +
"values(#{username},#{password},#{email})")
int addUser(User user);
// 修改用户
@Update("update user set username=#{username},password=#{password},email=#{email} " +
"where id=#{id}")
int updateUser(User user);
// 删除用户
@Delete("delete from user where id=#{id}")
int deleteUser(@Param("id") Integer id);
}
2. 在 mybatis-config.xml 中注册 Mapper
xml
<mappers>
<!-- 直接注册接口类,不需要xml -->
<mapper class="com.example.mapper.UserMapper"/>
</mappers>
3. 测试
@Test
public void testAnnotation(){
SqlSession session = MyBatisUtils.getSqlSession(true);
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
session.close();
}
15.3 字段映射 @Results / @Result
当数据库字段与实体类属性不一致时,使用注解映射:
@Select("select * from user where id=#{id}")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "user_name", property = "username"),
@Result(column = "pwd", property = "password")
})
User getUserById(@Param("id") Integer id);
15.4 注解开发优缺点
优点:
- 简单、快速,不用写 XML
- 适合简单 SQL、微服务小型项目
缺点:
- 复杂 SQL(嵌套、动态 SQL、多表关联)写起来非常乱
- 不利于 SQL 统一管理
- 企业主流依然是 XML + 接口 混合使用
16、MyBatis 核心执行流程(面试必考)
这一章是原理核心,面试 90% 会问,我给你整理成最清晰的完整版流程。
16.1 整体执行流程(一句话总结)
加载配置 → 构建工厂 → 创建会话 → 获取代理 Mapper → 执行 SQL → 结果映射 → 关闭资源
16.2 详细步骤(12 步完整版)
-
加载核心配置文件 读取
mybatis-config.xml,解析环境、数据源、mappers、settings 等。 -
加载映射文件 / 注解 解析所有
*Mapper.xml或接口上的注解 SQL,把每一条 SQL 封装成MappedStatement对象。 -
构建 SqlSessionFactory 通过
SqlSessionFactoryBuilder构建全局唯一的SqlSessionFactory。 -
创建 SqlSession
openSession()获得一次数据库会话,包含事务、连接、执行器。 -
创建 Executor 执行器MyBatis 会根据配置创建:
SimpleExecutor简单执行器ReuseExecutor重用 StatementBatchExecutor批量执行CachingExecutor带缓存的执行器
-
通过动态代理获取 Mapper 接口实例
session.getMapper(UserMapper.class)→ JDK 动态代理生成代理对象→ 代理类会根据方法名找到对应的MappedStatement -
参数处理 把 Java 参数转换成 JDBC 可识别的参数,处理
#{}预编译。 -
执行 SQL Executor 调用 JDBC 的
Statement/PreparedStatement执行 SQL。 -
结果集封装(ResultSet) 查询结果通过
ResultSetHandler转换成 Java 对象:- 字段映射
- 驼峰转换
- ResultMap 映射
- 集合 / 对象封装
-
事务处理 增删改需要
commit(),查询自动结束。 -
**缓存处理(一级 / 二级缓存)**如果开启缓存,会先查缓存,命中直接返回。
-
关闭资源关闭 SqlSession → 归还连接 → 释放资源。
16.3 极简流程图(适合写博客配图)
plaintext
mybatis-config.xml
↓
SqlSessionFactoryBuilder
↓
SqlSessionFactory 【单例】
↓
SqlSession 【非线程安全】
↓
Executor(执行器)
↓
MapperProxy(代理对象)
↓
MappedStatement(SQL信息)
↓
JDBC 执行 SQL
↓
ResultSetHandler 结果映射
↓
返回 Java 对象
16.4 高频面试题(直接背)
-
**SqlSession 为什么是非线程安全的?**因为持有数据库连接,会被多线程污染,必须一次请求一个。
-
**MyBatis 的 Executor 有哪些?**Simple、Reuse、Batch、CachingExecutor。
-
**Mapper 接口没有实现类为什么能调用?**JDK 动态代理生成代理对象。
-
**#{} 和 区别?预编译,防注入;{}` 字符串拼接,有注入风险。
-
**MyBatis 一级缓存、二级缓存是什么?**一级:SqlSession 级别,默认开启。二级:Mapper 级别,需手动开启。
17、注解增删改查
一、核心注解回顾
MyBatis 提供了一套纯注解的持久层开发方案,无需编写 XML 映射文件,直接在 Mapper 接口上通过注解定义 SQL,适合简单 CRUD 场景。核心注解如下:
| 注解 | 作用 | 对应标签 |
|---|---|---|
@Select |
执行查询 SQL | <select> |
@Insert |
执行新增 SQL | <insert> |
@Update |
执行修改 SQL | <update> |
@Delete |
执行删除 SQL | <delete> |
@Param |
为参数命名,解决多参数绑定问题 | - |
@Results/@Result |
自定义结果集映射,替代 ResultMap | <resultMap> |
二、完整注解 CRUD 实现
1. Mapper 接口代码
package com.example.mapper;
import com.example.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
// 1. 根据ID查询用户
@Select("SELECT * FROM user WHERE id = #{id}")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "user_name", property = "userName"),
@Result(column = "pwd", property = "password")
})
User getUserById(@Param("id") Integer id);
// 2. 查询所有用户
@Select("SELECT * FROM user")
List<User> getUserList();
// 3. 新增用户
@Insert("INSERT INTO user(user_name, pwd, email) VALUES(#{userName}, #{password}, #{email})")
// 开启自增主键回显(MySQL)
@Options(useGeneratedKeys = true, keyProperty = "id")
int addUser(User user);
// 4. 修改用户
@Update("UPDATE user SET user_name=#{userName}, pwd=#{password}, email=#{email} WHERE id=#{id}")
int updateUser(User user);
// 5. 删除用户
@Delete("DELETE FROM user WHERE id = #{id}")
int deleteUser(@Param("id") Integer id);
// 6. 多条件查询(@Param命名参数)
@Select("SELECT * FROM user WHERE user_name LIKE #{username} AND email LIKE #{email}")
List<User> getUserByCondition(@Param("username") String username, @Param("email") String email);
}
2. 核心配置说明
@Options注解 :用于配置 SQL 执行的额外属性,如useGeneratedKeys开启自增主键回显,keyProperty指定主键对应的实体类属性。@Results注解:当数据库字段与实体类属性名不一致时,手动配置映射关系,替代 XML 中的 ResultMap。@Param注解:当方法有多个参数时,必须使用该注解为每个参数命名,否则 MyBatis 无法正确绑定参数。
3. 注册 Mapper(mybatis-config.xml)
xml
<mappers>
<!-- 注解开发直接注册Mapper接口类 -->
<mapper class="com.example.mapper.UserMapper"/>
</mappers>
4. 测试代码
@Test
public void testAnnotationCRUD() {
SqlSession sqlSession = MyBatisUtils.getSqlSession(true); // 自动提交事务
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 新增用户
User user = new User(null, "lisi", "123456", "lisi@example.com");
int addRows = mapper.addUser(user);
System.out.println("新增成功,用户ID:" + user.getId()); // 自增主键回显
// 查询用户
User queryUser = mapper.getUserById(user.getId());
System.out.println("查询结果:" + queryUser);
// 修改用户
queryUser.setEmail("lisi_new@example.com");
int updateRows = mapper.updateUser(queryUser);
System.out.println("修改成功,影响行数:" + updateRows);
// 删除用户
int deleteRows = mapper.deleteUser(user.getId());
System.out.println("删除成功,影响行数:" + deleteRows);
sqlSession.close();
}
三、注解开发优缺点
✅ 优点 :代码简洁,无需维护 XML 文件,开发效率高,适合简单 CRUD 场景。❌ 缺点 :复杂 SQL(多表关联、动态 SQL)可读性差,难以维护,企业级项目主流仍采用XML + 接口的混合开发模式。
18、Lombok 的使用
一、Lombok 核心概念
Lombok 是一款 Java 代码简化工具,通过注解自动生成实体类的getter/setter、toString、构造方法等冗余代码,大幅减少样板代码,提升开发效率。
二、使用步骤
1. 导入 Maven 依赖
xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
2. IDEA 安装 Lombok 插件
- 打开 IDEA →
File→Settings→Plugins→ 搜索Lombok→ 安装并重启 IDEA。 - 高版本 IDEA 已内置 Lombok 插件,无需手动安装。
3. 核心常用注解
| 注解 | 作用 |
|---|---|
@Data |
自动生成getter/setter、toString、equals、hashCode、无参构造 |
@NoArgsConstructor |
生成无参构造方法 |
@AllArgsConstructor |
生成全参构造方法 |
@Getter/@Setter |
单独生成getter/setter方法 |
@ToString |
生成toString方法 |
@Builder |
生成 Builder 建造者模式代码 |
@Slf4j |
自动生成log日志对象(替代手动创建Logger) |
4. 实体类简化示例
传统实体类(冗余代码多):
public class User {
private Integer id;
private String userName;
private String password;
private String email;
public User() {}
public User(Integer id, String userName, String password, String email) {
this.id = id;
this.userName = userName;
this.password = password;
this.email = email;
}
// getter/setter、toString、equals、hashCode 省略(几十行冗余代码)
}
Lombok 简化后实体类:
package com.example.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Data // 自动生成getter/setter、toString等
@NoArgsConstructor // 无参构造
@AllArgsConstructor // 全参构造
public class User {
private Integer id;
private String userName;
private String password;
private String email;
}
三、核心优势
- 减少冗余代码:实体类代码量减少 70% 以上,提升开发效率。
- 代码可维护性高:修改属性时无需手动维护 getter/setter,避免遗漏。
- 无侵入性:注解不影响业务逻辑,编译时自动生成字节码,运行时无依赖。
19、复杂查询环境搭建
一、环境准备(多表关联查询基础)
复杂查询核心是多表关联,我们以经典的「用户 - 订单 - 商品」模型为例,搭建多表环境:
1. 数据库表设计
sql
-- 用户表
CREATE TABLE `user` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`user_name` VARCHAR(50) NOT NULL,
`pwd` VARCHAR(50) NOT NULL,
`email` VARCHAR(50)
);
-- 订单表(多对一:多个订单对应一个用户)
CREATE TABLE `orders` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`order_num` VARCHAR(50) NOT NULL,
`user_id` INT NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`)
);
-- 商品表(多对多:一个订单对应多个商品,一个商品对应多个订单)
CREATE TABLE `goods` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`goods_name` VARCHAR(50) NOT NULL,
`price` DECIMAL(10,2) NOT NULL
);
-- 订单-商品中间表(多对多关联表)
CREATE TABLE `order_goods` (
`order_id` INT NOT NULL,
`goods_id` INT NOT NULL,
PRIMARY KEY (`order_id`, `goods_id`),
FOREIGN KEY (`order_id`) REFERENCES `orders`(`id`),
FOREIGN KEY (`goods_id`) REFERENCES `goods`(`id`)
);
2. 实体类设计
// User.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String userName;
private String password;
private String email;
// 一对多:一个用户对应多个订单
private List<Orders> orderList;
}
// Orders.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Orders {
private Integer id;
private String orderNum;
// 多对一:多个订单对应一个用户
private User user;
// 多对多:一个订单对应多个商品
private List<Goods> goodsList;
}
// Goods.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Goods {
private Integer id;
private String goodsName;
private BigDecimal price;
}
3. 核心配置
- 保持 MyBatis 核心配置文件
mybatis-config.xml不变,开启驼峰命名自动映射:
xml
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="logImpl" value="LOG4J"/>
</settings>
- 配置别名,简化 XML 中的全类名:
xml
<typeAliases>
<package name="com.example.pojo"/>
</typeAliases>
20、多对一的处理
一、多对一核心场景
多个订单(Orders)对应一个用户(User),即多对一关联,需要在查询订单时,关联查询对应用户信息。
二、两种实现方式
方式 1:嵌套结果查询(推荐,一次 SQL 查询)
通过association标签,在一条 SQL 中关联查询订单和用户信息,自动封装到实体类。
1. Mapper 接口
public interface OrdersMapper {
// 查询订单,关联查询对应用户
List<Orders> getOrdersWithUser();
}
2. Mapper XML
xml
<mapper namespace="com.example.mapper.OrdersMapper">
<resultMap id="OrdersUserMap" type="Orders">
<id property="id" column="id"/>
<result property="orderNum" column="order_num"/>
<!-- 多对一关联:association标签,javaType指定关联对象类型 -->
<association property="user" javaType="User">
<id property="id" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="email" column="email"/>
</association>
</resultMap>
<select id="getOrdersWithUser" resultMap="OrdersUserMap">
SELECT o.*, u.user_name, u.email
FROM orders o
LEFT JOIN user u ON o.user_id = u.id
</select>
</mapper>
方式 2:嵌套查询(分步查询,两次 SQL)
先查询订单,再根据订单的user_id关联查询用户信息,适合复杂场景。
1. Mapper 接口
public interface OrdersMapper {
List<Orders> getOrdersWithUserStep();
}
public interface UserMapper {
User getUserById(@Param("id") Integer id);
}
2. Mapper XML
xml
<!-- OrdersMapper.xml -->
<resultMap id="OrdersUserStepMap" type="Orders">
<id property="id" column="id"/>
<result property="orderNum" column="order_num"/>
<!-- 多对一:select指定关联查询的SQL,column指定传递的参数 -->
<association property="user" javaType="User"
select="com.example.mapper.UserMapper.getUserById"
column="user_id"/>
</resultMap>
<select id="getOrdersWithUserStep" resultMap="OrdersUserStepMap">
SELECT * FROM orders
</select>
三、核心注意事项
association标签用于多对一 / 一对一 关联,javaType指定关联对象的类型。- 嵌套结果查询性能更高(一次 SQL),嵌套查询更灵活(分步查询,可复用 SQL)。
- 关联查询必须使用
ResultMap,无法使用resultType。
21、一对多的处理
一、一对多核心场景
一个用户(User)对应多个订单(Orders),即一对多关联,需要在查询用户时,关联查询对应所有订单信息。
二、两种实现方式
方式 1:嵌套结果查询(推荐,一次 SQL 查询)
通过collection标签,在一条 SQL 中关联查询用户和订单信息,自动封装到实体类。
1. Mapper 接口
public interface UserMapper {
// 查询用户,关联查询对应所有订单
List<User> getUsersWithOrders();
}
2. Mapper XML
xml
<mapper namespace="com.example.mapper.UserMapper">
<resultMap id="UserOrdersMap" type="User">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="email" column="email"/>
<!-- 一对多关联:collection标签,ofType指定集合元素类型 -->
<collection property="orderList" ofType="Orders">
<id property="id" column="order_id"/>
<result property="orderNum" column="order_num"/>
</collection>
</resultMap>
<select id="getUsersWithOrders" resultMap="UserOrdersMap">
SELECT u.*, o.id AS order_id, o.order_num
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
</select>
</mapper>
方式 2:嵌套查询(分步查询,两次 SQL)
先查询用户,再根据用户 ID 关联查询所有订单信息。
1. Mapper 接口
public interface UserMapper {
List<User> getUsersWithOrdersStep();
}
public interface OrdersMapper {
List<Orders> getOrdersByUserId(@Param("userId") Integer userId);
}
2. Mapper XML
xml
<!-- UserMapper.xml -->
<resultMap id="UserOrdersStepMap" type="User">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<!-- 一对多:select指定关联查询的SQL,column指定传递的参数 -->
<collection property="orderList" ofType="Orders"
select="com.example.mapper.OrdersMapper.getOrdersByUserId"
column="id"/>
</resultMap>
<select id="getUsersWithOrdersStep" resultMap="UserOrdersStepMap">
SELECT * FROM user
</select>
<!-- OrdersMapper.xml -->
<select id="getOrdersByUserId" resultType="Orders">
SELECT * FROM orders WHERE user_id = #{userId}
</select>
三、核心注意事项
collection标签用于一对多 关联,ofType指定集合中元素的类型(区别于association的javaType)。- 一对多关联查询,实体类中必须定义
List<Orders>类型的属性。 - 嵌套结果查询需注意 SQL 别名,避免字段名冲突(如
id需起别名order_id)。
22、动态 SQL 环境搭建
一、动态 SQL 核心概念
动态 SQL 是 MyBatis 最强大的特性之一,用于根据条件动态拼接 SQL 语句,解决传统 JDBC 中 SQL 拼接繁琐、易出错的问题,支持条件判断、循环、分支等逻辑。
二、环境搭建
1. 核心依赖
保持原有 MyBatis 依赖不变,无需额外引入,动态 SQL 是 MyBatis 原生支持的功能。
2. 核心配置
在mybatis-config.xml中开启驼峰命名、日志,配置别名,确保 Mapper XML 正常加载。
3. 测试实体类与 Mapper
以用户多条件查询为例,搭建动态 SQL 测试环境:
// UserMapper接口
public interface UserMapper {
// 多条件动态查询
List<User> getUserByCondition(User user);
}
23、动态 SQL 之 IF 语句
一、IF 语句核心作用
if标签是动态 SQL 最常用的标签,用于条件判断,根据参数是否为空,动态拼接 SQL 片段,实现多条件模糊查询。
二、代码实现
Mapper XML
xml
<mapper namespace="com.example.mapper.UserMapper">
<select id="getUserByCondition" resultType="User">
SELECT * FROM user
WHERE 1=1 <!-- 解决第一个条件为空时的语法错误 -->
<if test="userName != null and userName != ''">
AND user_name LIKE CONCAT('%', #{userName}, '%')
</if>
<if test="email != null and email != ''">
AND email LIKE CONCAT('%', #{email}, '%')
</if>
<if test="id != null">
AND id = #{id}
</if>
</select>
</mapper>
三、核心说明
test属性:条件判断表达式,支持 OGNL 语法,判断参数是否为空、是否满足条件。WHERE 1=1:解决第一个条件为空时,SQL 出现WHERE AND的语法错误(后续可使用where标签替代)。CONCAT('%', #{userName}, '%'):MySQL 中拼接模糊查询通配符,防 SQL 注入(推荐)。
四、测试代码
@Test
public void testDynamicIf() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 条件1:仅查询用户名包含"admin"的用户
User condition1 = new User();
condition1.setUserName("admin");
List<User> list1 = mapper.getUserByCondition(condition1);
list1.forEach(System.out::println);
// 条件2:查询用户名包含"admin"且邮箱包含"example"的用户
User condition2 = new User();
condition2.setUserName("admin");
condition2.setEmail("example");
List<User> list2 = mapper.getUserByCondition(condition2);
list2.forEach(System.out::println);
sqlSession.close();
}
24、动态 SQL 常用标签
一、核心常用标签汇总
| 标签 | 作用 | 替代语法 |
|---|---|---|
if |
条件判断,动态拼接 SQL | 传统 if-else 拼接 |
where |
自动处理WHERE关键字,去除多余的AND/OR |
手动写WHERE 1=1 |
set |
自动处理SET关键字,去除多余的逗号 |
手动拼接 UPDATE 语句 |
choose/when/otherwise |
多分支条件判断,相当于 Java 的if-else if-else |
多层 if 嵌套 |
foreach |
循环遍历集合,动态拼接 IN 语句 | 手动循环拼接 |
trim |
自定义字符串截取,去除前缀 / 后缀 | 手动处理 SQL 语法 |
bind |
自定义参数绑定,适配不同数据库 | 数据库函数拼接 |
二、各标签详细实现
1. where标签(替代WHERE 1=1)
自动去除 SQL 中多余的AND/OR,无需手动写WHERE 1=1:
xml
<select id="getUserByCondition" resultType="User">
SELECT * FROM user
<where>
<if test="userName != null and userName != ''">
AND user_name LIKE CONCAT('%', #{userName}, '%')
</if>
<if test="email != null and email != ''">
AND email LIKE CONCAT('%', #{email}, '%')
</if>
</where>
</select>
2. set标签(动态 UPDATE)
自动去除 UPDATE 语句中多余的逗号:
xml
<update id="updateUserSelective">
UPDATE user
<set>
<if test="userName != null and userName != ''">
user_name = #{userName},
</if>
<if test="email != null and email != ''">
email = #{email},
</if>
<if test="password != null and password != ''">
pwd = #{password},
</if>
</set>
WHERE id = #{id}
</update>
3. choose/when/otherwise(多分支判断)
相当于 Java 的if-else if-else,只会执行第一个满足条件的分支:
xml
<select id="getUserByChoose" resultType="User">
SELECT * FROM user
<where>
<choose>
<when test="id != null">
AND id = #{id}
</when>
<when test="userName != null and userName != ''">
AND user_name = #{userName}
</when>
<otherwise>
AND 1=1
</otherwise>
</choose>
</where>
</select>
4. foreach标签(循环遍历,IN 查询)
用于遍历集合,动态拼接IN语句,适合批量查询、批量删除:
xml
<!-- 批量查询:根据ID列表查询用户 -->
<select id="getUserByIdList" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 批量删除 -->
<delete id="deleteUserByIdList">
DELETE FROM user
WHERE id IN
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
collection:指定遍历的集合(List/Array/Map)item:遍历的当前元素open/close:拼接的前缀 / 后缀separator:元素之间的分隔符
5. trim标签(自定义截取)
自定义 SQL 前缀 / 后缀的截取,灵活处理 SQL 语法:
xml
<select id="getUserByCondition" resultType="User">
SELECT * FROM user
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="userName != null and userName != ''">
AND user_name LIKE CONCAT('%', #{userName}, '%')
</if>
<if test="email != null and email != ''">
AND email LIKE CONCAT('%', #{email}, '%')
</if>
</trim>
</select>
prefix:添加前缀prefixOverrides:去除前缀(如多余的AND/OR)suffixOverrides:去除后缀(如多余的逗号)
25、动态 SQL 之 Foreach
一、核心作用
foreach 标签是动态 SQL 中处理集合循环 的最常用标签,主要用于批量删除、批量查询(IN 语句),可以自动遍历集合或数组,拼接 SQL 语句。
二、核心参数解析
| 参数 | 作用 | 示例 |
|---|---|---|
| collection | 指定要遍历的集合参数 | list / array / map |
| item | 循环中的当前元素变量名 | item="id" |
| index | 索引(遍历 List/Map 时) | index="i" |
| open | 拼接 SQL 的开头 | 通常为 ( |
| close | 拼接 SQL 的结尾 | 通常为 ) |
| separator | 元素之间的分隔符 | 通常为 , |
三、代码实现(批量查询与删除)
1. Mapper 接口
package com.example.mapper;
import com.example.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
public interface UserMapper {
// 批量查询:根据ID列表查询用户
List<User> getUserByIdList(@Param("idList") List<Integer> idList);
// 批量删除:根据ID列表删除
int deleteUserByIdList(@Param("idList") List<Integer> idList);
// 批量添加(可选)
int addUserBatch(List<User> userList);
}
2. Mapper XML 配置
xml
<mapper namespace="com.example.mapper.UserMapper">
<!-- 1. 批量查询:IN 条件 -->
<select id="getUserByIdList" resultType="User">
SELECT * FROM user
WHERE id IN
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 2. 批量删除:IN 条件 -->
<delete id="deleteUserByIdList">
DELETE FROM user
WHERE id IN
<foreach collection="idList" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<!-- 3. 批量添加(性能较高) -->
<insert id="addUserBatch">
INSERT INTO user (user_name, pwd, email)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.userName}, #{user.password}, #{user.email})
</foreach>
</insert>
</mapper>
四、测试代码
@Test
public void testForeach() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 1. 批量查询
List<Integer> ids = Arrays.asList(1, 2, 3);
List<User> userList = mapper.getUserByIdList(ids);
userList.forEach(System.out::println);
// 2. 批量删除
// int rows = mapper.deleteUserByIdList(ids);
// System.out.println("删除行数:" + rows);
sqlSession.close();
}
五、避坑指南
⚠️ collection 属性必须指定:
- 如果参数是
List,默认填list。 - 如果参数是
Array,默认填array。 - 如果使用
@Param("xxx")注解,必须填xxx。
26、缓存简介
一、什么是 MyBatis 缓存?
MyBatis 缓存是指将查询结果存入内存(或磁盘) ,当执行相同的查询时,直接从缓存中获取结果,减少数据库访问,提高性能。
二、缓存体系结构
MyBatis 缓存分为一级缓存 和二级缓存,层级关系如下:
- 一级缓存(本地缓存) :
SqlSession级别,默认开启,无法关闭。 - 二级缓存(全局缓存) :
Mapper级别,默认开启,但需手动配置。 - 第三方缓存 :如
Ehcache、Redis,用于替代默认的二级缓存。
27、一级缓存
一、核心定义
一级缓存是SqlSession 会话级的缓存。它是 MyBatis 自动开启的,不需要任何配置。
- 作用域 :同一个
SqlSession内有效。 - 存储结构 :
PerSessionCache(Map 结构)。
二、工作机制
- 查询流程 :
- 第一次查询:发送 SQL 到数据库,存入缓存。
- 第二次查询(同 SqlSession):直接从缓存取,不执行 SQL。
- 缓存失效场景 :
- 执行 增删改 操作(
insert/update/delete)会清空缓存。 - 手动调用
sqlSession.clearCache()清空。 SqlSession关闭或提交后,缓存失效。
- 执行 增删改 操作(
三、代码演示
@Test
public void testFirstLevelCache() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询
User u1 = mapper.getUserById(1);
System.out.println(u1);
// 中间执行了一次增删改,缓存被清空
// mapper.deleteUser(1);
// sqlSession.commit();
// 第二次查询
User u2 = mapper.getUserById(1);
System.out.println(u2);
// 关键:u1 == u2 ?
System.out.println(u1 == u2); // true,说明是同一个对象(缓存命中)
sqlSession.close();
}
四、总结
✅ 一级缓存是 MyBatis 底层机制,保证单线程下的数据一致性,通常不需要手动干预。
28、二级缓存
一、核心定义
二级缓存是Mapper 级别 的缓存(全局缓存)。它跨 SqlSession,多个会话可以共享缓存。
- 作用域:整个应用内(基于 Mapper)。
- 默认状态 :开启,但需要手动在 XML 中开启
<cache/>。
二、使用步骤
1. 开启二级缓存(Mapper XML)
在 UserMapper.xml 头部添加:
xml
<mapper namespace="com.example.mapper.UserMapper">
<!-- 开启当前 Mapper 的二级缓存 -->
<cache/>
<!-- 或者配置属性:eviction="LRU" 缓存策略 -->
<!-- <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/> -->
</mapper>
2. 实体类序列化(必须!)
MyBatis 要求缓存的实体类必须实现 Serializable 接口,因为二级缓存可能序列化到磁盘或网络传输。
import lombok.Data;
import java.io.Serializable; // 必须导入
@Data
public class User implements Serializable { // 必须实现序列化
private Integer id;
private String userName;
private String password;
private String email;
}
3. 测试代码
@Test
public void testSecondLevelCache() {
// 会话1
try (SqlSession session1 = MyBatisUtils.getSqlSession()) {
UserMapper mapper1 = session1.getMapper(UserMapper.class);
User u1 = mapper1.getUserById(1);
System.out.println(u1);
session1.close(); // 关闭会话,一级缓存会存入二级缓存
}
// 会话2
try (SqlSession session2 = MyBatisUtils.getSqlSession()) {
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User u2 = mapper2.getUserById(1); // 不会发SQL,走二级缓存
System.out.println(u2);
session2.close();
}
}
三、核心配置属性
| 属性 | 说明 |
|---|---|
| eviction | 缓存回收策略,默认 LRU(最近最少使用)。 |
| flushInterval | 刷新间隔,单位毫秒。 |
| size | 缓存引用数量,默认 1024。 |
| readOnly | 是否只读。true 返回代理对象(性能高);false 返回克隆对象(安全)。 |
29、MyBatis 缓存原理
一、底层结构图
text
SqlSession
↓
Executor (执行器)
↓
CachingExecutor (装饰器模式) -> 【负责二级缓存】
↓
BaseExecutor -> 【负责一级缓存】
↓
JDBC 执行
二、执行流程剖析
- 查询流程 :
- 用户请求 ->
SqlSession-> 获取Mapper->CachingExecutor - CachingExecutor 先查二级缓存(Mapper 级别)。
- 二级缓存未命中 ->
BaseExecutor查一级缓存(SqlSession 级别)。 - 一级缓存未命中 -> 执行 JDBC 查询 -> 结果存入一级缓存 -> 返回。
- 用户请求 ->
- 写入流程 :
- 查询结果先写入一级缓存。
- 事务提交(
commit)时,一级缓存数据写入二级缓存。
三、为什么需要二级缓存?
一级缓存只能在一个会话内有效,无法共享数据。二级缓存跨会话,能大幅降低相同查询的压力。
30、自定义缓存 Ehcache
一、核心概念
MyBatis 默认的二级缓存是内存级的,重启应用会丢失。如果需要分布式缓存、持久化缓存 ,可以使用 Ehcache 或 Redis。
二、整合 Ehcache 步骤
1. 导入 Maven 依赖
xml
<!-- MyBatis 与 Ehcache 整合包 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
<!-- Ehcache 核心包 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.9.2</version>
</dependency>
2. 配置 Mapper 使用 Ehcache
xml
<mapper namespace="com.example.mapper.UserMapper">
<!-- 指定使用 Ehcache 缓存 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
</mapper>
3. 配置 Ehcache 文件(ehcache.xml)
在 resources 下创建 ehcache.xml,定义缓存策略:
xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盘缓存路径 -->
<diskStore path="java.io.tmpdir/ehcache-mybatis"/>
<!-- 默认缓存策略 -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiredThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<!-- 自定义针对 User 的缓存策略 -->
<cache name="com.example.mapper.UserMapper"
maxElementsInMemory="100"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"/>
</ehcache>
三、对比与总结
- Ehcache:适合本地缓存,支持磁盘持久化,性能好。
- Redis:适合分布式缓存,跨服务器,功能更强。
31、MyBatis 总结
一、整体知识图谱
这是一份 MyBatis 从入门到精通的完整路线,建议收藏:
- 基础篇:配置、SqlSession、CRUD、日志。
- 进阶篇:ResultMap、生命周期、插件。
- 高级篇:动态 SQL(IF、Where、Foreach)、多表关联(一对一、一对多)。
- 性能篇:缓存(一级 / 二级 / Ehcache)、分页插件。
- 整合篇:与 Spring、SpringBoot 整合。
二、核心技术点回顾
表格
| 模块 | 核心知识点 |
|---|---|
| SQL 映射 | #{}, ${}, parameterType, resultType, ResultMap |
| 动态 SQL | if, where, set, choose, foreach |
| 关联查询 | association (多对一), collection (一对多) |
| 缓存 | 一级缓存(SqlSession),二级缓存(Mapper),Ehcache |
| 注解 | @Select, @Insert, @Param, @Results |
| 工具 | Lombok, PageHelper, Log4j |
三、MyBatis 为何流行?
✅ SQL 与代码分离 :维护方便,DBA 可优化。✅ 动态 SQL 强大 :适配各种业务场景。✅ 轻量 :小巧灵活,易于上手。❌ 与 Spring 对比:Spring 是 IOC/AOP 容器,MyBatis 是持久层框架。MyBatis 需与 Spring 配合使用。
32、聊聊 Spring 这东西(过渡与总结)
一、MyBatis 与 Spring 的关系
MyBatis 是持久层框架 ,负责操作数据库;Spring 是全能容器框架,负责管理 Bean、事务、AOP。
- 整合目的 :
- 用 Spring 管理
SqlSessionFactory和Mapper接口。 - 用 Spring 的声明式事务(
@Transactional)替代 MyBatis 手动事务。 - 整合 SpringMVC 完成完整的 Web 三层架构(SSM)。
- 用 Spring 管理
二、SSM 架构流转图
text
用户 (View: JSP/Vue)
↓
SpringMVC (Controller:接收请求,返回数据)
↓
Spring (Service:业务逻辑,事务管理)
↓
MyBatis (Mapper:操作数据库)
↓
MySQL
三、学习建议
- 先吃透 MyBatis:理解 ORM 原理、动态 SQL、缓存。
- 再学 Spring:理解 IOC(控制反转)、AOP(面向切面编程)。
- 最后学 SpringBoot:自动配置,大幅减少 XML,快速开发。
四、结尾寄语
MyBatis 是 Java 后端开发的基石。掌握了它,你就掌握了数据持久化的核心逻辑。接下来的学习路径建议:
- 进阶:学习 Spring + SpringMVC + MyBatis (SSM) 整合。
- 实战:做一个完整的项目(如电商、管理系统)。
- 前沿:学习 MyBatis-Plus(MP),它是 MyBatis 的增强版,大幅提升开发效率。