Appearance
MyBatis 框架
1. 简介
1.1 MyBatis 概述
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
发展历程:
- 最初是 Apache 的开源项目 iBatis
- 2010 年迁移到 Google Code 并更名为 MyBatis
- 2013 年代码迁移到 Github
- 官网:https://mybatis.org/mybatis-3/zh_CN/index.html
核心特点:
- 半自动 ORM 框架:研发效率较低,但程序执行效率较高
- 全自动 ORM 框架:如 Hibernate,研发效率高,但程序执行效率低
- ORM:对象关系映射,将对象属性与表中字段建立映射关系
1.2 持久化层框架对比
| 框架 | 优点 | 缺点 |
|---|---|---|
| JDBC | 性能最高 | SQL 耦合在代码中,维护困难 |
| Hibernate/JPA | 开发效率高,操作简便 | 复杂 SQL 需要绕过框架,性能较低 |
| MyBatis | 性能出色,SQL 与 Java 分离 | 开发效率稍逊于 Hibernate |
效率对比:
- 开发效率:Hibernate > MyBatis > JDBC
- 运行效率:JDBC > MyBatis > Hibernate
2. 快速入门
2.1 环境准备
数据库准备:
sql
CREATE DATABASE `db_mybatis`;
USE `db_mybatis`;
CREATE TABLE `t_emp`(
emp_id INT AUTO_INCREMENT,
emp_name CHAR(100),
emp_salary DOUBLE(10,5),
PRIMARY KEY(emp_id)
);
INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("tom",200.33);
INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("jerry",666.66);2.2 依赖配置
Spring Boot 版本:
xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>2.3 配置文件
application.properties:
properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/db_mybatis
spring.datasource.username=root
spring.datasource.password=root
# MyBatis配置
mybatis.mapper-locations=classpath:mappers/*.xml
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.type-aliases-package=com.at.pojo
logging.level.com.at.mapper=debug2.4 Mapper 接口
java
@Mapper
public interface EmployeeMapper {
List<Employee> selectAllEmps();
}2.5 映射文件
EmployeeMapper.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">
<mapper namespace="com.at.mapper.EmployeeMapper">
<select id="selectAllEmps" resultType="employee">
SELECT emp_id, emp_name, emp_salary FROM t_emp
</select>
</mapper>2.6 测试代码
java
@SpringBootTest
class MyBatisTest {
@Autowired
private EmployeeMapper employeeMapper;
@Test
void testSelectAll() {
List<Employee> employees = employeeMapper.selectAllEmps();
employees.forEach(System.out::println);
}
}3. 数据输入
3.1 参数传递方式
3.1.1 单个普通参数
java
Employee selectById(int empId);xml
<select id="selectById" resultType="employee">
SELECT * FROM t_emp WHERE emp_id = #{empId}
</select>3.1.2 多个普通参数
java
Employee selectByCondition(int empId, String empName);xml
<select id="selectByCondition" resultType="employee">
SELECT * FROM t_emp
WHERE emp_id = #{param1} AND emp_name = #{param2}
</select>3.1.3 命名参数
java
Employee selectByNamedParam(@Param("id") int empId, @Param("name") String empName);xml
<select id="selectByNamedParam" resultType="employee">
SELECT * FROM t_emp
WHERE emp_id = #{id} AND emp_name = #{name}
</select>3.1.4 POJO 参数
java
void insertEmployee(Employee employee);xml
<insert id="insertEmployee">
INSERT INTO t_emp(emp_name, emp_salary)
VALUES(#{empName}, #{empSalary})
</insert>3.1.5 Map 参数
java
List<Employee> selectByMap(Map<String, Object> map);xml
<select id="selectByMap" resultType="employee">
SELECT * FROM t_emp
WHERE emp_id = #{id} AND emp_name = #{name}
</select>3.2 #{}与${}的区别
| 特性 | #{} | ${} |
|---|---|---|
| 底层实现 | PreparedStatement(?占位符) | Statement(字符串拼接) |
| 安全性 | 防止 SQL 注入,安全 | 存在 SQL 注入风险,不安全 |
| 使用场景 | 参数值 | 表名、列名等非参数位置 |
4. 数据输出
4.1 返回类型处理
4.1.1 返回单个字面量
java
Integer selectEmpCount();xml
<select id="selectEmpCount" resultType="int">
SELECT COUNT(*) FROM t_emp
</select>4.1.2 返回单个 POJO
java
Employee selectById(int empId);xml
<select id="selectById" resultType="employee">
SELECT * FROM t_emp WHERE emp_id = #{empId}
</select>4.1.3 返回 List 类型
java
List<Employee> selectAll();xml
<select id="selectAll" resultType="employee">
SELECT * FROM t_emp
</select>4.1.4 返回 Map 类型
java
@MapKey("empId")
Map<Integer, Employee> selectAllReturnMap();xml
<select id="selectAllReturnMap" resultType="employee">
SELECT * FROM t_emp
</select>4.2 主键返回
4.2.1 自增主键
xml
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">
INSERT INTO t_emp(emp_name, emp_salary)
VALUES(#{empName}, #{empSalary})
</insert>4.2.2 非自增主键
xml
<insert id="insertUser" parameterType="User">
<selectKey keyProperty="id" resultType="java.lang.String" order="BEFORE">
SELECT UUID() as id
</selectKey>
INSERT INTO user (id, username, password)
VALUES (#{id}, #{username}, #{password})
</insert>5. 结果映射
5.1 resultType vs resultMap
| 特性 | resultType | resultMap |
|---|---|---|
| 复杂度 | 简单映射 | 复杂映射 |
| 适用场景 | 单表简单查询 | 多表复杂查询 |
| 配置 | 自动映射 | 手动配置映射关系 |
5.2 实体类关系映射
5.2.1 对一关系
java
// 员工对部门(一对一)
public class Employee {
private Integer empId;
private String empName;
private Department dept; // 对一关系
}5.2.2 对多关系
java
// 部门对员工(一对多)
public class Department {
private Integer deptId;
private String deptName;
private List<Employee> employees; // 对多关系
}5.3 级联映射
xml
<resultMap id="empAndDeptResultMap" type="Employee">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<result column="dept_id" property="dept.deptId"/>
<result column="dept_name" property="dept.deptName"/>
</resultMap>5.4 association 映射
xml
<resultMap id="empAndDeptResultMap" type="Employee">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<association property="dept" javaType="Department">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</association>
</resultMap>5.5 collection 映射
xml
<resultMap id="deptAndEmpResultMap" type="Department">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
<collection property="employees" ofType="Employee">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
</collection>
</resultMap>5.6 分步查询
5.6.1 对一分步查询
xml
<resultMap id="empAndDeptStepMap" type="Employee">
<id column="emp_id" property="empId"/>
<result column="emp_name" property="empName"/>
<association property="dept"
select="com.at.mapper.DeptMapper.selectById"
column="dept_id"/>
</resultMap>
<select id="selectEmpWithDept" resultMap="empAndDeptStepMap">
SELECT * FROM t_emp WHERE emp_id = #{empId}
</select>5.6.2 对多分步查询
xml
<resultMap id="deptAndEmpStepMap" type="Department">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
<collection property="employees"
select="com.at.mapper.EmpMapper.selectByDeptId"
column="dept_id"/>
</resultMap>
<select id="selectDeptWithEmps" resultMap="deptAndEmpStepMap">
SELECT * FROM t_dept WHERE dept_id = #{deptId}
</select>5.7 延迟加载
配置开启延迟加载:
properties
mybatis.configuration.lazy-loading-enabled=true
mybatis.configuration.aggressive-lazy-loading=false6. 动态 SQL
6.1 常用动态 SQL 标签
6.1.1 if 标签
xml
<select id="selectByCondition" resultType="Employee">
SELECT * FROM t_emp
<where>
<if test="empName != null and empName != ''">
AND emp_name LIKE CONCAT('%', #{empName}, '%')
</if>
<if test="minSalary != null">
AND emp_salary >= #{minSalary}
</if>
</where>
</select>6.1.2 choose/when/otherwise
xml
<select id="selectByChoose" resultType="Employee">
SELECT * FROM t_emp
<where>
<choose>
<when test="empId != null">
emp_id = #{empId}
</when>
<when test="empName != null">
emp_name = #{empName}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</where>
</select>6.1.3 trim 标签
xml
<insert id="insertSelective">
INSERT INTO t_emp
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="empName != null">emp_name,</if>
<if test="empSalary != null">emp_salary,</if>
</trim>
<trim prefix="VALUES (" suffix=")" suffixOverrides=",">
<if test="empName != null">#{empName},</if>
<if test="empSalary != null">#{empSalary},</if>
</trim>
</insert>6.1.4 set 标签
xml
<update id="updateSelective">
UPDATE t_emp
<set>
<if test="empName != null">emp_name = #{empName},</if>
<if test="empSalary != null">emp_salary = #{empSalary},</if>
</set>
WHERE emp_id = #{empId}
</update>6.1.5 foreach 标签
xml
<select id="selectByIds" resultType="Employee">
SELECT * FROM t_emp
WHERE emp_id IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
<insert id="insertBatch">
INSERT INTO t_emp(emp_name, emp_salary) VALUES
<foreach collection="employees" item="emp" separator=",">
(#{emp.empName}, #{emp.empSalary})
</foreach>
</insert>6.1.6 bind 标签
xml
<select id="selectByLikeName" resultType="Employee">
<bind name="pattern" value="'%' + empName + '%'"/>
SELECT * FROM t_emp
WHERE emp_name LIKE #{pattern}
</select>6.1.7 sql/include 标签
xml
<sql id="baseColumn">
emp_id, emp_name, emp_salary, dept_id
</sql>
<select id="selectAll" resultType="Employee">
SELECT <include refid="baseColumn"/> FROM t_emp
</select>7. 分页插件
7.1 PageHelper 配置
添加依赖:
xml
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>使用示例:
java
@Test
public void testPageHelper() {
// 开启分页
PageHelper.startPage(1, 10);
// 查询数据
List<Employee> employees = employeeMapper.selectAll();
// 封装分页信息
PageInfo<Employee> pageInfo = new PageInfo<>(employees);
System.out.println("当前页:" + pageInfo.getPageNum());
System.out.println("总页数:" + pageInfo.getPages());
System.out.println("总记录数:" + pageInfo.getTotal());
System.out.println("每页大小:" + pageInfo.getPageSize());
}8. 逆向工程
8.1 MyBatisX 插件使用
安装插件:
- 在 IDEA 中安装 MyBatisX 插件
- 配置数据库连接
生成代码:
- 在数据库表上右键
- 选择 MyBatisX-Generator
- 配置生成路径和选项
生成的文件:
- 实体类(POJO)
- Mapper 接口
- Mapper XML 文件
8.2 生成代码示例
实体类:
java
@Data
public class Employee {
private Integer empId;
private String empName;
private Double empSalary;
private Integer deptId;
}Mapper 接口:
java
public interface EmployeeMapper {
int deleteByPrimaryKey(Integer empId);
int insert(Employee record);
Employee selectByPrimaryKey(Integer empId);
int updateByPrimaryKey(Employee record);
}9. 最佳实践
9.1 配置优化
properties
# 开启自动映射
mybatis.configuration.auto-mapping-behavior=full
# 开启二级缓存
mybatis.configuration.cache-enabled=true
# 设置默认执行器
mybatis.configuration.default-executor-type=reuse9.2 代码规范
- 使用@Param 注解明确参数名称
- 复杂的查询使用 resultMap 而不是 resultType
- 批量操作使用 foreach 标签
- 敏感操作使用#{}防止 SQL 注入
9.3 性能优化
- 合理使用延迟加载
- 大数据量查询使用分页
- 频繁查询考虑使用缓存
- 关联查询使用分步查询减少 JOIN