1.1 MyBatis 简介与特点
MyBatis 是一款半自动的 ORM(对象关系映射)持久层框架,核心特点如下:
-
SQL 灵活性高:支持手动编写 SQL,可根据需求自由定制
-
高级映射能力:支持一对一、一对多等关联关系映射
-
动态 SQL:可根据参数条件动态拼接 SQL 语句
-
支持延迟加载与缓存:提升数据查询性能
-
数据库无关性较低:因需手写 SQL,切换数据库时可能需适配不同方言(Dialect)
1.2 ORM 概念
ORM(Object Relation Mapping)即对象关系映射,用于建立 Java 对象与数据库关系模型之间的对应关系:
-
对象:Java 中的实体类(如 Student 类)
-
关系:数据库中的表(如 student 表)
-
映射规则:实体类属性与表字段一一对应,一个实体对象对应表中的一行数据
1.3 为什么 MyBatis 是半自动 ORM 框架?
半自动的核心原因是需要手动编写 SQL 语句,与全自动 ORM 框架(如 Hibernate)的区别如下:
-
MyBatis:需手动编写 SQL,灵活性高,但数据库无关性低,学习成本较低
-
Hibernate:无需编写 SQL,仅需定义 ORM 映射关系即可完成 CRUD 操作,数据库无关性高,但灵活性差,学习成本高
相比 JDBC,MyBatis 提供了输入映射、输出映射、关联查询等功能,简化了参数设置和结果集封装,大幅提升开发效率。
二、快速入门
2.1 核心步骤
-
编写全局配置文件(核心配置,含数据源等信息)
-
编写 Mapper 映射文件(编写 SQL 语句,定义输入/输出参数)
-
加载全局配置文件,生成 SqlSessionFactory
-
创建 SqlSession,调用 Mapper 中的 SQL 执行 CRUD 操作
2.2 原生开发示例
步骤 1:环境准备
-
数据库:在本地 MySQL 创建库 yogurt,新建 student 表
-
项目:IDEA 创建 Maven 项目,导入核心依赖
xml
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
步骤 2:创建 PO 类
java
package com.yogurt.po;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
private Integer id;
private String name;
private Integer score;
private Integer age;
private Integer gender;
}
步骤 3:编写 Mapper 映射文件(StudentMapper.xml)
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">
<mapper namespace="test">
<select id="findAll" resultType="com.yogurt.po.Student">
SELECT * FROM student;
</select>
<insert id="insert" parameterType="com.yogurt.po.Student">
INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
</insert>
<delete id="delete" parameterType="int">
DELETE FROM student WHERE id = #{id};
</delete>
</mapper>
步骤 4:编写数据源配置文件(db.properties)
properties
db.url=jdbc:mysql://192.168.183.129:3306/yogurt?characterEncoding=utf8
db.user=root
db.password=root
db.driver=com.mysql.jdbc.Driver
步骤 5:编写全局配置文件(mybatis-config.xml)
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="properties/db.properties"></properties>
<!-- 配置环境 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${db.driver}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.user}"/>
<property name="password" value="${db.password}"/>
</dataSource>
</environment>
</environments>
<!-- 加载 Mapper 映射文件 -->
<mappers>
<mapper resource="StudentMapper.xml"/>
</mappers>
</configuration>
步骤 6:编写 DAO 类
java
package com.yogurt.dao;
import com.yogurt.po.Student;
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;
import java.util.List;
public class StudentDao {
private SqlSessionFactory sqlSessionFactory;
public StudentDao(String configPath) throws IOException {
InputStream inputStream = Resources.getResourceAsStream(configPath);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
public List<Student> findAll() {
SqlSession sqlSession = sqlSessionFactory.openSession();
List<Student> studentList = sqlSession.selectList("findAll");
sqlSession.close();
return studentList;
}
public int addStudent(Student student) {
SqlSession sqlSession = sqlSessionFactory.openSession();
int rowsAffected = sqlSession.insert("insert", student);
sqlSession.commit();
sqlSession.close();
return rowsAffected;
}
public int deleteStudent(int id) {
SqlSession sqlSession = sqlSessionFactory.openSession();
int rowsAffected = sqlSession.delete("delete", id);
sqlSession.commit();
sqlSession.close();
return rowsAffected;
}
}
步骤 7:编写测试类
java
import com.yogurt.dao.StudentDao;
import com.yogurt.po.Student;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
public class SimpleTest {
private StudentDao studentDao;
@Before
public void init() throws IOException {
studentDao = new StudentDao("mybatis-config.xml");
}
@Test
public void insertTest() {
Student student = new Student();
student.setName("yogurt");
student.setAge(24);
student.setGender(1);
student.setScore(100);
studentDao.addStudent(student);
}
@Test
public void findAllTest() {
List<Student> all = studentDao.findAll();
all.forEach(System.out::println);
}
}
三、核心配置详解
3.1 全局配置文件标签顺序
MyBatis 加载配置文件时按固定顺序解析标签,需严格遵循以下顺序:
xml
<configuration>
<!-- 1. 属性配置 -->
<properties/>
<!-- 2. 全局设置 -->
<settings/>
<!-- 3. 类型别名 -->
<typeAliases/>
<!-- 4. 类型处理器 -->
<typeHandlers/>
<!-- 5. 对象工厂 -->
<objectFactory/>
<!-- 6. 插件 -->
<plugins/>
<!-- 7. 环境配置 -->
<environments>
<environment>
<transactionManager/>
<dataSource/>
</environment>
</environments>
<!-- 8. Mapper 加载 -->
<mappers/>
</configuration>
3.2 核心子标签说明
3.2.1 properties
用于引入外部属性文件(如数据源配置),可通过 ${} 占位符引用属性值,简化配置维护。
3.2.2 settings
用于开启/关闭 MyBatis 核心特性,常用配置:
-
lazyLoadingEnabled:开启延迟加载(默认 false)
-
cacheEnabled:开启二级缓存(默认 true)
xml
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="cacheEnabled" value="true"/>
</settings>
3.2.3 typeAliases
为实体类配置别名,简化 Mapper 中参数类型和返回值类型的书写,支持两种配置方式:
xml
<!-- 单个类配置别名 -->
<typeAliases>
<typeAlias type="com.yogurt.po.Student" alias="student"/>
</typeAliases>
<!-- 包扫描配置(所有类别名默认为简单类名小写) -->
<typeAliases>
<package name="com.yogurt.po"/>
</typeAliases>
MyBatis 对基本类型、包装类及 String 提供默认别名(如 String → string、Integer → integer)。
3.2.4 plugins
配置 MyBatis 插件(如分页插件),底层通过责任链模式+动态代理实现。示例:PageHelper 分页插件配置
xml
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
3.2.5 mappers
加载 Mapper 映射文件或接口,支持三种方式:
xml
<!-- 1. 加载 XML 文件(相对类路径) -->
<mappers>
<mapper resource="StudentMapper.xml"/>
</mappers>
<!-- 2. 加载 Mapper 接口(注解开发或 XML 与接口同目录同名) -->
<mappers>
<mapper class="com.yogurt.mapper.StudentMapper"/>
</mappers>
<!-- 3. 包扫描加载(XML 与接口需同目录同名) -->
<mappers>
<package name="com.yogurt.mapper"/>
</mappers>
注意:Maven 环境下,src/main/java 目录下的 XML 文件需通过 build-resources 配置才能被打包,否则会丢失。
xml
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
3.3 Mapper 中 #{} 与 ${} 的区别
| 特性 | #{} | ${} |
|---|---|---|
| 解析方式 | 解析为 PreparedStatement 的 ?,预编译 | 直接字符串拼接,无预编译 |
| SQL 注入 | 可防止 SQL 注入 | 存在 SQL 注入风险 |
| 参数类型解析 | 自动解析参数类型(如 String 加引号) | 不解析类型,直接拼接 |
| 适用场景 | 普通参数传递(推荐) | 模糊查询、表名/列名动态拼接 |
| 简单类型参数名 | 可任意命名 | 必须为 value |
示例:
xml
<!-- #{} 普通参数传递 -->
<select id="findById" parameterType="int" resultType="student">
SELECT * FROM student WHERE id = #{id}
</select>
<!-- ${} 模糊查询 -->
<select id="findByName" parameterType="string" resultType="student">
SELECT * FROM student WHERE name like '%${value}%'
</select>
四、MyBatis 开发方式
4.1 原生 DAO 开发(已过时)
即快速入门中的开发方式,需手动编写 DAO 类,通过 SqlSession 调用 SQL 标签 ID(字符串形式),易出错、维护成本高。
4.2 Mapper 接口代理开发(推荐)
无需编写 DAO 类,通过 Mapper 接口与 XML 映射文件绑定,MyBatis 自动生成代理实现类,简化开发。
4.2.1 核心绑定规则
-
Mapper 接口全限定名 = XML 标签的 namespace 属性值
-
接口方法名 = XML 中 SQL 标签的 id 属性值
-
接口方法入参类型 = XML 中 parameterType 属性值
-
接口方法出参类型 = XML 中 resultType/resultMap 属性值
4.2.2 开发示例
java
// 1. 创建 Mapper 接口
package com.yogurt.mapper;
import com.yogurt.po.Student;
import java.util.List;
public interface StudentMapper {
List<Student> findAll();
int insert(Student student);
int delete(Integer id);
List<Student> findByName(String value);
}
xml
<!-- 2. 编写 Mapper XML(namespace 绑定接口) -->
<mapper namespace="com.yogurt.mapper.StudentMapper">
<select id="findAll" resultType="com.yogurt.po.Student">
SELECT * FROM student;
</select>
<insert id="insert" parameterType="com.yogurt.po.Student">
INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
</insert>
<delete id="delete" parameterType="int">
DELETE FROM student WHERE id = #{id};
</delete>
<select id="findByName" parameterType="string" resultType="student">
SELECT * FROM student WHERE name like '%${value}%';
</select>
</mapper>
java
// 3. 测试代码
public class MapperProxyTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
}
@Test
public void test() {
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取 Mapper 代理对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// 调用接口方法(自动关联 XML 中的 SQL)
List<Student> studentList = mapper.findAll();
studentList.forEach(System.out::println);
sqlSession.close();
}
}
4.3 注解开发(简化配置)
无需编写 Mapper XML,直接在接口方法上通过注解编写 SQL,适合简单场景,维护性较差(修改 SQL 需改代码)。
4.3.1 开发示例
java
// 1. 创建注解式 Mapper 接口
package com.yogurt.mapper;
import com.yogurt.po.Student;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface PureStudentMapper {
@Select("SELECT * FROM student")
List<Student> findAll();
@Insert("INSERT INTO student (name,age,score,gender) VALUES (#{name},#{age},#{score},#{gender})")
int insert(Student student);
}
xml
<!-- 2. 全局配置文件加载 Mapper 接口 -->
<mappers>
<mapper class="com.yogurt.mapper.PureStudentMapper"/>
</mappers>
4.3.2 多参数传递(@Param 注解)
多参数场景需通过 @Param 注解指定参数名,MyBatis 会自动封装为 Map 对象:
java
import org.apache.ibatis.annotations.Param;
public interface PureStudentMapper {
@Select("SELECT * FROM student WHERE name like '%${name}%' AND gender = #{gender}")
List<Student> findByCondition(@Param("name") String name, @Param("gender") Integer gender);
}
五、核心应用场景
5.1 主键返回
针对自增主键,插入数据后获取数据库生成的主键 ID,支持两种方式:
xml
<!-- 方式 1:useGeneratedKeys + keyProperty(推荐,适用于 MySQL 等支持自增的数据库) -->
<insert id="insert" parameterType="student" useGeneratedKeys="true" keyProperty="id">
INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
</insert>
<!-- 方式 2:selectKey 子标签(通用,支持非自增主键) -->
<insert id="insert" parameterType="student">
INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
<selectKey keyProperty="id" order="AFTER" resultType="int">
SELECT LAST_INSERT_ID(); <!-- MySQL 函数,获取最新插入的主键 -->
</selectKey>
</insert>
测试验证:插入后通过实体对象的 getId() 方法获取主键。
5.2 动态 SQL
根据参数条件动态拼接 SQL,避免冗余代码,MyBatis 提供多种动态标签:
5.2.1 if 标签(条件判断)
xml
<select id="find" resultType="student" parameterType="student">
SELECT * FROM student WHERE age >= 18
<if test="name != null and name != ''">
AND name like '%${name}%'
</if>
</select>
5.2.2 choose/when/otherwise 标签(分支选择)
类似 Java 的 switch 语句,仅执行第一个满足条件的分支:
xml
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = 'ACTIVE'
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
5.2.3 where/trim 标签(SQL 拼接优化)
where 标签自动处理 AND/OR 前缀,避免 SQL 语法错误:
xml
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">state = #{state}</if>
<if test="title != null">AND title like #{title}</if>
<if test="author != null and author.name != null">AND author_name like #{author.name}</if>
</where>
</select>
trim 标签更灵活,可自定义前缀/后缀及需要去除的字符:
xml
<!-- 等效于 where 标签 -->
<trim prefix="WHERE" prefixOverrides="AND | OR">
<if test="state != null">state = #{state}</if>
<if test="title != null">AND title like #{title}</if>
</trim>
5.2.4 set 标签(更新语句优化)
自动处理字段后的逗号,避免更新语句语法错误:
xml
<update id="updateStudent" parameterType="student">
UPDATE student
<set>
<if test="name != null">name = #{name},</if>
<if test="age != null">age = #{age},</if>
<if test="score != null">score = #{score}</if>
</set>
WHERE id = #{id}
</update>
5.2.5 foreach 标签(循环迭代)
常用于 IN 查询、批量操作,支持 List/Array 类型参数:
xml
<!-- 批量查询(参数为 List,引用时变量名必须为 list) -->
<select id="batchFind" resultType="student" parameterType="java.util.List">
SELECT * FROM student
<where>
<if test="list != null and list.size() > 0">
AND id in
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
</select>
5.2.6 sql/include 标签(SQL 片段复用)
提取重复 SQL 片段,通过 include 引用,减少冗余:
xml
<!-- 定义 SQL 片段 -->
<sql id="whereClause">
<where>
<if test="name != null and name != ''">
AND name like '%${name}%'
</if>
</where>
</sql>
<!-- 引用 SQL 片段 -->
<select id="findUser" parameterType="student" resultType="student">
SELECT * FROM student
<include refid="whereClause"/>
</select>
5.3 缓存机制
MyBatis 提供两级缓存,用于减少数据库查询次数,提升性能:
5.3.1 一级缓存(默认开启)
-
作用域:SqlSession 级别,同一 SqlSession 内重复查询相同 SQL 会复用缓存
-
清除时机:执行 DML 操作、SqlSession 提交/关闭、设置 flushCache=true、开启 localCacheScope=STATEMENT
5.3.2 二级缓存(默认关闭)
-
作用域:Mapper 级别,多个 SqlSession 可共享同一 Mapper 的缓存
-
开启方式:1. 全局配置开启 cacheEnabled=true;2. Mapper XML 中添加 标签
-
注意:缓存数据需序列化(实体类实现 Serializable 接口),SqlSession 提交后数据才会写入二级缓存
5.4 关联查询与延迟加载
5.4.1 关联查询标签
通过 resultMap 中的 association(一对一)和 collection(一对多)标签实现关联查询:
xml
<resultMap id="studentExt" type="com.yogurt.po.StudentExt">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!-- 一对一关联:学生 → 班级 -->
<association property="clazz" javaType="com.yogurt.po.Clazz"
column="class_id" select="com.yogurt.mapper.ClassMapper.findById"/>
</resultMap>
5.4.2 延迟加载(解决 N+1 问题)
N+1 问题:查询 N 个主对象时,触发 N 次从对象查询,性能低下。延迟加载可按需加载从对象,提升性能。
-
开启方式:全局配置
-
局部禁用:在 association/collection 标签中设置 fetchType="eager"
-
触发时机:访问主对象的从对象属性时,才执行关联查询
N+1 问题其他解决方案:
-
连接查询:通过 LEFT JOIN 一次性查询主从数据(需手动处理笛卡尔积)
-
子查询:先查询主对象 ID 集合,再通过 IN 子查询查询从对象
六、实用工具
6.1 MyBatis 逆向工程
通过 mybatis-generator 工具自动生成单表的 PO 类、Mapper 接口和 XML 文件,支持单表 CRUD,不支持关联查询。
6.1.1 配置步骤
xml
<!-- 1. 配置 Maven 插件 -->
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>
xml
<!-- 2. 编写 generatorConfig.xml 配置文件 -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<properties resource="properties/db.properties"/>
<!-- 指定 MySQL 驱动路径 -->
<classPathEntry location="C:\Users\Vergi\.m2\repository\mysql\mysql-connector-java\8.0.11\mysql-connector-java-8.0.11.jar"/>
<context id="default" targetRuntime="MyBatis3">
<!-- 关闭自动生成注释 -->
<commentGenerator>
<property name="suppressDate" value="true"/>
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!-- 数据库连接配置 -->
<jdbcConnection driverClass="${db.driver}"
connectionURL="${db.url}"
userId="${db.user}"
password="${db.password}">
</jdbcConnection>
<!-- Java 类型解析 -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- PO 类生成配置 -->
<javaModelGenerator targetPackage="mybatis.generator.model" targetProject=".\src\main\java">
<property name="enableSubPackages" value="false"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- Mapper XML 生成配置 -->
<sqlMapGenerator targetPackage="mybatis.generator.mappers" targetProject=".\src\main\resources">
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<!-- Mapper 接口生成配置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="mybatis.generator.dao" targetProject=".\src\main\java">
<property name="enableSubPackages" value="false"/>
</javaClientGenerator>
<!-- 指定要生成的表 -->
<table tableName="student"/>
<table tableName="product"/>
</context>
</generatorConfiguration>
6.1.2 执行与使用
-
执行:双击 Maven 插件 mybatis-generator:generate
-
生成文件:PO 类、Example 类(条件查询工具)、Mapper 接口、Mapper XML
-
Example 类使用示例:
java
@Test
public void testExample() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// 创建条件对象
StudentExample example = new StudentExample();
StudentExample.Criteria criteria = example.createCriteria();
// 添加查询条件:name 包含 "o"
criteria.andNameLike("%o%");
// 执行查询
List<Student> students = mapper.selectByExample(example);
students.forEach(System.out::println);
sqlSession.close();
}
6.2 PageHelper 分页插件
快速实现分页功能,底层通过拼接 LIMIT 关键字(MySQL)实现。
6.2.1 配置步骤
xml
<!-- 1. 导入依赖 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.6</version>
</dependency>
xml
<!-- 2. 全局配置文件添加插件 -->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
6.2.2 使用示例
java
@Test
public void testPageHelper() {
SqlSession sqlSession = sqlSessionFactory.openSession();
ProductMapper mapper = sqlSession.getMapper(ProductMapper.class);
// 设置分页:第 1 页,每页 3 条数据
PageHelper.startPage(1, 3);
// 执行查询(自动分页)
List<Product> products = mapper.selectByExample(new ProductExample());
// 获取分页信息
PageInfo<Product> pageInfo = new PageInfo<>(products);
System.out.println("总记录数:" + pageInfo.getTotal());
System.out.println("总页数:" + pageInfo.getPages());
products.forEach(System.out::println);
sqlSession.close();
}
注意:Mapper XML 中的 SQL 结尾不要加 ;,否则会导致 LIMIT 拼接失败。