初识Mybatis
Mybatis的概念
MyBatis 是一个Java 持久层框架,核心作用是简化数据库操作,把 SQL 和 Java 代码解耦。
ORM框架
MyBatis是一个ORM 框架
所谓ORM 框架,就是把数据库里的表、字段、关系,映射成编程语言里的类、属性、对象引用,从而让我们用“面向对象”的方式去操作关系型数据库,而不用写大量繁复的 JDBC/SQL。
简而言之,半自动化ORM,将SQL与Java对象映射,不自动生成SQL,只负责把“你写的 SQL”和“你写的 Java 对象”精准地对接起来,半自动化 ORM 是“SQL 与 Java 对象之间的翻译官”。
核心组件
SqlSessionFactory
作用: MyBatis 的核心工厂,负责创建和管理 SqlSession 实例。
特点:
- 全局单例:在整个应用中只需要一个实例,线程安全。
- 初始化:加载 MyBatis 配置文件、解析映射文件,初始化数据库连接池等资源。
功能: 通过 openSession() 方法创建 SqlSession 实例,用于执行 SQL 操作。
SqlSession
作用:MyBatis 的一个会话对象,用于执行 SQL 操作。
特点:
- 线程不安全:每次请求或操作数据库时需要创建一个新的实例,用完后必须关闭。
- 功能:提供 crud等方法,用于执行 SQL 语句。它还管理事务(提交或回滚)。
- 生命周期:通常与一个请求或一个业务操作绑定,用完即关闭。
Executor
作用:它是 SqlSession的底层执行器,负责执行 SQL 语句。
类型:
- Simple:每次执行 SQL 时都创建新的 Statement,适合简单场景。
- Reuse:复用 Statement,减少 Statement 的创建和销毁开销。
- Batch:批量执行 SQL,适合批量插入或更新操作,可以减少数据库交互次数。
功能:负责执行 SQL 语句,管理 Statement 的生命周期,以及维护一级缓存。
MapperProxy
作用:它是 MyBatis 的动态代理机制,用于实现 Mapper 接口。
特点:
- 动态代理:通过 JDK 动态代理技术,为 Mapper 接口生成代理实例。
- 功能:将 Mapper 接口的方法调用转换为具体的 SQL 执行操作。
- 解耦:让开发者只需要关注 Mapper 接口的定义,而不需要关心底层的 SQL 执行细节。
配置文件
此处介绍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>
<!-- 配置mybatis的环境-->
<environments default="mysql">
<!-- id和default的值必须保持一致,接下来配置连接mysql的具体信息-->
<environment id="mysql">
<!-- 配置事务类型 JDBC-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源 它的取值有POOLED 和 UNPOOLED 前者是mysql提供的默认数据源进行数据库的连接
后者是不使用数据源,多次与数据库进行交互-->
<dataSource type="POOLED">
<!-- 配置连接数据库的驱动 url 用户名和密码-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 在核心配置文件里要导入接口的映射文件-->
<mappers>
<mapper resource="com/noy/dao/UserDao.xml"></mapper>
</mappers>
</configuration>
Mapper XML 文件
若要通过xml来执行SQL操作,那么Mapper 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 namespace="com.noy.dao.UserDao">
<!--
select标签 就是一个执行查询操作的标签
id必须和接口方法名称一样,才可以保持映射成功
parameterType参数类型的意思,这里不传参可以不写
resultType 是接口的返回值 如果是泛型 ,写泛型的全限定名
-->
<select id="findAll" resultType="com.noy.pojo.User">
SELECT * FROM user
</select>
</mapper>
搭建mybatis入门案例的步骤
- 导入mybatis相关的依赖
- 创建pojo,将pojo和数据表进行关联映射 定义接口
- 创建mybatis的核心配置文件sqlMapConfig.xml
- 创建接口的映射文件 接口所在的目录 和接口文件所在的目录保持一致
- 编写测试代码
SQL映射
本质上是解耦 SQL 与 Java 代码,通过声明式配置实现数据库操作
基础 CRUD 标签
标签 | 核心属性 | |
---|---|---|
<select> | id , resultType/resultMap | |
<insert> | id , parameterType | |
<update> | id , parameterType | |
<delete> | id , parameterType |
id 必须与 Mapper 接口方法同名,MyBatis 就靠这个把接口方法与 SQL 绑定在一起。
参数 & 占位符
#{}表示一个占位符号 通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换, #{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类 型值,#{}括号中可以是 value 或其它名称。 ${}表示拼接 sql 串 通过${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, ${}可以接收简单类型值或 pojo 属性值,如果 parameterType传输单个简单类型值,${}括号中只能是value。
总而言之,安全用 #{} ,拼接才用 ${}
列表演示:
特性 | #{} (预编译占位符) | ${} (字符串替换) |
---|---|---|
SQL 安全 | 防止 SQL 注入 ✅ | 有注入风险 ❗ |
类型处理 | 自动类型转换 (Java→JDBC) | 直接字符串替换 |
使用场景 | 值参数(WHERE 条件值) | 动态表名/列名 |
日志输出 | ? 占位符 | 替换后的完整 SQL |
返回结果
resultType :简单场景,直接写全限定类名或别名,列名与属性名一致即可自动映射。
例: <select id="getById" resultType="com.noy.User">
resultMap :复杂场景,自定义映射规则(列名≠属性名、一对多、枚举转换等)。
例: <resultMap id="userMap" type="User"><result column="user_name" property="name"/></resultMap>
主键回填(insert 专属)
useGeneratedKeys="true" 开启 JDBC 自动生成主键回填。
keyProperty="id" 指定把数据库生成的主键值塞进参数对象的哪个属性。
<insert id="save" parameterType="User"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO user(name) VALUES(#{name})
</insert>
插入完成后, User#getId() 就能直接拿到新主键。
多级映射
一个人可能有不同的身份,不同的人可能有同一种身份,不同的人也可能会有不同的身份,所以映射也是如此
关系类型 | 数据库表现 | 对象关系示例 | MyBatis 标签 | 使用场景 |
---|---|---|---|---|
一对一 | 主表外键指向关联表主键 | 用户 ↔ 身份证 | <association> | 用户详情页显示身份证信息 |
一对多 | 主表主键出现在关联表的外键字段 | 部门 ↔ 员工 | <collection> | 部门页面显示下属员工列表 |
多对多 | 通过中间表维护两个主表的关系 | 学生 ↔ 课程 | 两个<collection> | 学生选课系统/课程学生名单 |
一对一
SQL
CREATE TABLE user (
id INT PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE id_card (
id INT PRIMARY KEY,
card_number VARCHAR(20),
user_id INT UNIQUE, -- 唯一外键
FOREIGN KEY (user_id) REFERENCES user(id)
);
Java
public class User {
private Integer id;
private String name;
private IdCard idCard; // 一对一关联对象
}
public class IdCard {
private String cardNumber;
}
MyBatis 映射
<resultMap id="userMap" type="User">
<id property="id" column="user_id"/>
<result property="name" column="user_name"/>
<!-- 一对一映射 -->
<association property="idCard" javaType="IdCard">
<result property="cardNumber" column="card_number"/>
</association>
</resultMap>
<select id="getUserWithCard" resultMap="userMap">
SELECT
u.id AS user_id,
u.name AS user_name,
c.card_number
FROM user u
JOIN id_card c ON u.id = c.user_id
WHERE u.id = #{id}
</select>
一对多
SQL
CREATE TABLE department (
id INT PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE employee (
id INT PRIMARY KEY,
name VARCHAR(50),
dept_id INT, -- 非唯一外键
FOREIGN KEY (dept_id) REFERENCES department(id)
);
Java
public class Department {
private Integer id;
private String name;
private List<Employee> employees; // 一对多关联集合
}
public class Employee {
private String name;
}
MyBatis 映射
<resultMap id="deptMap" type="Department">
<id property="id" column="dept_id"/>
<result property="name" column="dept_name"/>
<!-- 一对多映射 -->
<collection property="employees" ofType="Employee">
<result property="name" column="emp_name"/>
</collection>
</resultMap>
<select id="getDeptWithEmployees" resultMap="deptMap">
SELECT
d.id AS dept_id,
d.name AS dept_name,
e.name AS emp_name
FROM department d
LEFT JOIN employee e ON d.id = e.dept_id
WHERE d.id = #{id}
</select>
多对多
SQL
CREATE TABLE student (
id INT PRIMARY KEY,
name VARCHAR(50)
);
CREATE TABLE course (
id INT PRIMARY KEY,
name VARCHAR(50)
);
-- 中间表
CREATE TABLE student_course (
student_id INT,
course_id INT,
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES student(id),
FOREIGN KEY (course_id) REFERENCES course(id)
);
Java
public class Student {
private Integer id;
private String name;
private List<Course> courses; // 多对多关联
}
public class Course {
private String name;
private List<Student> students; // 反向关联(可选)
}
MyBatis 映射
<resultMap id="studentMap" type="Student">
<id property="id" column="student_id"/>
<result property="name" column="student_name"/>
<!-- 多对多映射(通过中间表) -->
<collection property="courses" ofType="Course">
<result property="name" column="course_name"/>
</collection>
</resultMap>
<select id="getStudentWithCourses" resultMap="studentMap">
SELECT
s.id AS student_id,
s.name AS student_name,
c.name AS course_name
FROM student s
JOIN student_course sc ON s.id = sc.student_id
JOIN course c ON sc.course_id = c.id
WHERE s.id = #{id}
</select>
实际上,一对多/多对多使用<collection>
时,大数据量建议开启延迟加载,具体操作见下列映射策略
映射策略
-
自动映射:配置
autoMappingBehavior
(PARTIAL/FULL/NONE) -
嵌套查询:
<association select="queryDeptById">
+column="dept_id"
-
延迟加载:在全局启用
lazyLoadingEnabled=true
<settings>
<!-- 开启全局延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 关闭积极加载(3.4.1+ 默认为 false,高于此版本的mybatis可以不写) -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
动态 SQL用法
其目的为
-
动态生成 SQL:根据参数条件自动拼接不同 SQL 片段
-
替代硬编码:避免在 Java 中手动拼接 SQL 字符串
-
智能去除多余关键字(AND/OR/)
-
用声明式 XML 标签代替过程式代码拼接,使 SQL 更清晰、更安全、更易维护。
核心标签为
标签 | 作用 | 常用场景 |
---|---|---|
<if> | 条件判断 | 按需添加 WHERE 条件 |
<choose> | 多选一(类似 switch-case) | 多种查询方案选择 |
<where> | 智能处理 WHERE 子句 | 自动去除开头多余的 AND/OR |
<set> | 智能处理 UPDATE 的 SET 子句 | 自动去除尾部逗号 |
<foreach> | 遍历集合 | IN 查询、批量插入 |
它们的用法语法也类似于Java
基础条件控制 <if>
<select id="search">
SELECT * FROM users
<where>
<!-- 姓名不为空时添加条件 -->
<if test="name != null">
AND name = #{name}
</if>
<!-- 年龄大于0时添加条件 -->
<if test="age > 0">
AND age > #{age}
</if>
</where>
</select>
多分支选择 <choose>
<select id="getData">
SELECT * FROM products
<where>
<choose>
<!-- 优先按ID查 -->
<when test="id != null">
id = #{id}
</when>
<!-- 其次按名称查 -->
<when test="name != null">
name LIKE #{name}
</when>
<!-- 默认查上架商品 -->
<otherwise>
status = 'ON_SALE'
</otherwise>
</choose>
</where>
</select>
智能更新 <set>
<update id="updateUser">
UPDATE users
<set>
<!-- 动态更新字段 -->
<if test="name != null">name=#{name},</if>
<if test="email != null">email=#{email},</if>
update_time = NOW() <!-- 固定更新 -->
</set>
WHERE id = #{id}
</update>
集合遍历 <foreach>
<!-- IN 查询 -->
<select id="findByIds">
SELECT * FROM users
WHERE id IN
<foreach item="id" collection="ids"
open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 批量插入 -->
<insert id="batchInsert">
INSERT INTO users (name, age)
VALUES
<foreach item="user" collection="list" separator=",">
(#{user.name}, #{user.age})
</foreach>
</insert>
此外,在动态SQL中,WHERE条件 → 用 <where>
标签,UPDATE 操作 → 用 <set>
标签
空集合处理
<!-- 检查集合非空 -->
<if test="list != null and !list.isEmpty()">
...
</if>