Skip to content

MyBatis 框架

1. 简介

1.1 MyBatis 概述

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

发展历程

核心特点

  • 半自动 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=debug

2.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

特性resultTyperesultMap
复杂度简单映射复杂映射
适用场景单表简单查询多表复杂查询
配置自动映射手动配置映射关系

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=false

6. 动态 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 插件
  • 配置数据库连接

生成代码

  1. 在数据库表上右键
  2. 选择 MyBatisX-Generator
  3. 配置生成路径和选项

生成的文件

  • 实体类(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=reuse

9.2 代码规范

  • 使用@Param 注解明确参数名称
  • 复杂的查询使用 resultMap 而不是 resultType
  • 批量操作使用 foreach 标签
  • 敏感操作使用#{}防止 SQL 注入

9.3 性能优化

  • 合理使用延迟加载
  • 大数据量查询使用分页
  • 频繁查询考虑使用缓存
  • 关联查询使用分步查询减少 JOIN