初识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 mapperPUBLIC "-//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_numberFROM user uJOIN id_card c ON u.id = c.user_idWHERE 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_nameFROM department dLEFT JOIN employee e ON d.id = e.dept_idWHERE 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_nameFROM student sJOIN student_course sc ON s.id = sc.student_idJOIN course c ON sc.course_id = c.idWHERE 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 usersWHERE 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>