MyBatis 全面解析 & Spring Boot 集成实战

目录

[一、MyBatis 核心原理](#一、MyBatis 核心原理)

[1. 核心架构](#1. 核心架构)

[2. 核心特性](#2. 核心特性)

[二、Spring Boot 集成 MyBatis 实战](#二、Spring Boot 集成 MyBatis 实战)

[1. 环境准备](#1. 环境准备)

(1)依赖引入(Maven)

(2)核心配置(application.yml)

[2. 代码实现](#2. 代码实现)

(1)数据库表设计

(2)实体类

[(3)Mapper 接口](#(3)Mapper 接口)

[(4)Mapper XML 文件(resources/mapper/UserMapper.xml)](#(4)Mapper XML 文件(resources/mapper/UserMapper.xml))

[(5)Service 层(业务逻辑)](#(5)Service 层(业务逻辑))

(6)启动类

[3. 测试验证](#3. 测试验证)

[三、MyBatis 高级用法](#三、MyBatis 高级用法)

[1. 分页插件(PageHelper)](#1. 分页插件(PageHelper))

(1)引入依赖

(2)配置(application.yml)

(3)使用示例

[2. 二级缓存](#2. 二级缓存)

(1)开启全局缓存(application.yml)

[(2)在 Mapper XML 中配置缓存](#(2)在 Mapper XML 中配置缓存)

[3. 自定义类型处理器](#3. 自定义类型处理器)

(1)自定义类型处理器

(2)配置类型处理器

(3)使用示例(XML)

四、常见问题与最佳实践

[1. 常见问题](#1. 常见问题)

[2. 最佳实践](#2. 最佳实践)

五、总结


MyBatis 是一款轻量级、高性能的持久层框架,核心优势是灵活控制 SQL + 简化 JDBC 操作,既保留了手写 SQL 的灵活性,又解决了传统 JDBC 繁琐的资源管理、参数设置和结果映射问题。本文将从核心原理、核心特性、Spring Boot 集成实战、高级用法四个维度全面解析 MyBatis。

一、MyBatis 核心原理

1. 核心架构

MyBatis 的核心执行流程可概括为:

plaintext

复制代码
配置加载 → SqlSessionFactory 创建 → SqlSession 获取 → Mapper 代理生成 → SQL 执行 → 结果映射

关键组件说明:

组件 作用
SqlSessionFactory 会话工厂(单例),负责创建 SqlSession,通过 SqlSessionFactoryBuilder 构建
SqlSession 数据库会话(非线程安全),封装增删改查 API,每次请求 / 事务独立创建
Mapper 接口 数据操作接口,MyBatis 通过动态代理生成实现类,接口方法与 SQL 绑定
Mapper XML / 注解 存储 SQL 语句、参数映射、结果映射规则
Configuration 核心配置类,存储全局配置(数据源、事务、别名、映射器等)
Executor 执行器,负责 SQL 执行(SimpleExecutor/BatchExecutor/ReuseExecutor)
StatementHandler 处理 Statement(PreparedStatement/Statement),设置参数、执行 SQL
ResultSetHandler 将 ResultSet 映射为 Java 对象(结果映射核心)

2. 核心特性

  • 动态 SQL :通过 <if>/<where>/<foreach> 等标签动态拼接 SQL,适配复杂条件
  • 结果映射:支持一对一、一对多、多对多关联映射,解决字段名与属性名不一致问题
  • 参数解析:支持多种参数类型(基本类型、实体、Map、注解参数),防止 SQL 注入
  • 缓存机制:一级缓存(SqlSession 级别)+ 二级缓存(Mapper 级别),提升查询性能
  • 插件扩展:支持自定义插件(如分页、拦截 SQL),扩展执行流程

二、Spring Boot 集成 MyBatis 实战

1. 环境准备

(1)依赖引入(Maven)

xml

复制代码
<!-- Spring Boot 父工程 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</version>
    <relativePath/>
</parent>

<dependencies>
    <!-- MyBatis + Spring Boot 整合启动器 -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>3.0.3</version>
    </dependency>
    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    <!-- Spring Boot 测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- Lombok(简化实体类) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
(2)核心配置(application.yml)

yaml

复制代码
# 数据源配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
    username: root
    password: 123456
    # 连接池配置(默认 HikariCP)
    hikari:
      maximum-pool-size: 10  # 最大连接数
      minimum-idle: 5        # 最小空闲连接
      idle-timeout: 300000    # 空闲连接超时时间

# MyBatis 配置
mybatis:
  # Mapper XML 文件位置
  mapper-locations: classpath:mapper/**/*.xml
  # 实体类别名包(简化 XML 中 type 配置)
  type-aliases-package: com.example.mybatis.entity
  # 全局配置
  configuration:
    map-underscore-to-camel-case: true  # 自动下划线转驼峰(如 user_name → userName)
    cache-enabled: false                # 关闭二级缓存(默认开启,按需开启)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印 SQL 日志
    default-statement-timeout: 30       # SQL 执行超时时间

2. 代码实现

(1)数据库表设计

sql

复制代码
CREATE DATABASE IF NOT EXISTS mybatis_demo;
USE mybatis_demo;

-- 用户表
CREATE TABLE `user` (
  `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
  `user_name` VARCHAR(50) NOT NULL COMMENT '用户名',
  `age` INT COMMENT '年龄',
  `email` VARCHAR(100) COMMENT '邮箱',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) COMMENT '用户表';

-- 订单表(一对多关联:一个用户多个订单)
CREATE TABLE `order` (
  `id` BIGINT AUTO_INCREMENT PRIMARY KEY,
  `order_no` VARCHAR(32) NOT NULL COMMENT '订单号',
  `user_id` BIGINT NOT NULL COMMENT '用户ID',
  `amount` DECIMAL(10,2) NOT NULL COMMENT '订单金额',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  FOREIGN KEY (`user_id`) REFERENCES `user`(`id`)
) COMMENT '订单表';
(2)实体类

java

运行

复制代码
// User.java
package com.example.mybatis.entity;

import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;

@Data
public class User {
    private Long id;
    private String userName; // 对应数据库 user_name(下划线转驼峰)
    private Integer age;
    private String email;
    private LocalDateTime createTime;
    // 一对多关联:用户的订单列表
    private List<Order> orderList;
}

// Order.java
package com.example.mybatis.entity;

import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
public class Order {
    private Long id;
    private String orderNo;
    private Long userId;
    private BigDecimal amount;
    private LocalDateTime createTime;
}
(3)Mapper 接口

java

运行

复制代码
// UserMapper.java
package com.example.mybatis.mapper;

import com.example.mybatis.entity.User;
import org.apache.ibatis.annotations.*;
import java.util.List;

// 方式1:注解扫描(推荐),也可在启动类加 @MapperScan("com.example.mybatis.mapper")
@Mapper
public interface UserMapper {
    /**
     * 根据ID查询用户(含订单列表)
     */
    User selectById(Long id);

    /**
     * 条件查询用户(动态SQL)
     */
    List<User> selectByCondition(@Param("userName") String userName, @Param("age") Integer age);

    /**
     * 新增用户(主键回填)
     */
    int insert(User user);

    /**
     * 更新用户(动态更新)
     */
    int update(User user);

    /**
     * 删除用户
     */
    int delete(Long id);
}
(4)Mapper XML 文件(resources/mapper/UserMapper.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">
<!-- namespace 必须与 Mapper 接口全限定名一致 -->
<mapper namespace="com.example.mybatis.mapper.UserMapper">

    <!-- 基础结果映射:解决字段名与属性名映射 -->
    <resultMap id="BaseResultMap" type="User">
        <id column="id" property="id"/> <!-- 主键映射 -->
        <result column="user_name" property="userName"/>
        <result column="age" property="age"/>
        <result column="email" property="email"/>
        <result column="create_time" property="createTime"/>
    </resultMap>

    <!-- 关联结果映射:用户 + 订单列表(一对多) -->
    <resultMap id="UserWithOrderResultMap" type="User" extends="BaseResultMap">
        <!-- collection 映射一对多关系 -->
        <collection property="orderList" ofType="Order" column="id"
                    select="com.example.mybatis.mapper.OrderMapper.selectByUserId"/>
    </resultMap>

    <!-- 根据ID查询用户(关联订单) -->
    <select id="selectById" resultMap="UserWithOrderResultMap">
        SELECT id, user_name, age, email, create_time
        FROM user
        WHERE id = #{id}
    </select>

    <!-- 条件查询(动态SQL) -->
    <select id="selectByCondition" resultMap="BaseResultMap">
        SELECT id, user_name, age, email, create_time
        FROM user
        <where>
            <if test="userName != null and userName != ''">
                AND user_name LIKE CONCAT('%', #{userName}, '%')
            </if>
            <if test="age != null">
                AND age = #{age}
            </if>
        </where>
    </select>

    <!-- 新增用户:useGeneratedKeys 开启主键回填,keyProperty 绑定实体主键 -->
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (user_name, age, email)
        VALUES (#{userName}, #{age}, #{email})
    </insert>

    <!-- 动态更新:set 标签自动处理逗号 -->
    <update id="update">
        UPDATE user
        <set>
            <if test="userName != null and userName != ''">
                user_name = #{userName},
            </if>
            <if test="age != null">
                age = #{age},
            </if>
            <if test="email != null and email != ''">
                email = #{email}
            </if>
        </set>
        WHERE id = #{id}
    </update>

    <!-- 删除用户 -->
    <delete id="delete">
        DELETE FROM user WHERE id = #{id}
    </delete>
</mapper>

xml

复制代码
<!-- OrderMapper.xml(resources/mapper/OrderMapper.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="com.example.mybatis.mapper.OrderMapper">
    <resultMap id="BaseResultMap" type="Order">
        <id column="id" property="id"/>
        <result column="order_no" property="orderNo"/>
        <result column="user_id" property="userId"/>
        <result column="amount" property="amount"/>
        <result column="create_time" property="createTime"/>
    </resultMap>

    <select id="selectByUserId" resultMap="BaseResultMap">
        SELECT id, order_no, user_id, amount, create_time
        FROM `order`
        WHERE user_id = #{userId}
    </select>
</mapper>
(5)Service 层(业务逻辑)

java

运行

复制代码
// UserService.java
package com.example.mybatis.service;

import com.example.mybatis.entity.User;
import com.example.mybatis.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
public class UserService {
    @Resource
    private UserMapper userMapper;

    /**
     * 根据ID查询用户
     */
    public User selectById(Long id) {
        return userMapper.selectById(id);
    }

    /**
     * 条件查询
     */
    public List<User> selectByCondition(String userName, Integer age) {
        return userMapper.selectByCondition(userName, age);
    }

    /**
     * 新增用户(事务控制)
     */
    @Transactional(rollbackFor = Exception.class)
    public int insert(User user) {
        return userMapper.insert(user);
    }

    /**
     * 更新用户
     */
    @Transactional(rollbackFor = Exception.class)
    public int update(User user) {
        return userMapper.update(user);
    }

    /**
     * 删除用户
     */
    @Transactional(rollbackFor = Exception.class)
    public int delete(Long id) {
        return userMapper.delete(id);
    }
}
(6)启动类

java

运行

复制代码
package com.example.mybatis;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 方式2:批量扫描 Mapper 接口(替代每个接口加 @Mapper)
// @MapperScan("com.example.mybatis.mapper")
@SpringBootApplication
public class MybatisDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybatisDemoApplication.class, args);
    }
}

3. 测试验证

java

运行

复制代码
package com.example.mybatis;

import com.example.mybatis.entity.User;
import com.example.mybatis.service.UserService;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;

@SpringBootTest
public class UserServiceTest {
    @Resource
    private UserService userService;

    @Test
    public void testInsert() {
        User user = new User();
        user.setUserName("张三");
        user.setAge(25);
        user.setEmail("zhangsan@example.com");
        int rows = userService.insert(user);
        System.out.println("新增行数:" + rows + ",用户ID:" + user.getId());
    }

    @Test
    public void testSelectById() {
        User user = userService.selectById(1L);
        System.out.println("用户信息:" + user);
        System.out.println("用户订单:" + user.getOrderList());
    }

    @Test
    public void testSelectByCondition() {
        List<User> userList = userService.selectByCondition("张", 25);
        System.out.println("条件查询结果:" + userList);
    }

    @Test
    public void testUpdate() {
        User user = new User();
        user.setId(1L);
        user.setAge(26);
        int rows = userService.update(user);
        System.out.println("更新行数:" + rows);
    }

    @Test
    public void testDelete() {
        int rows = userService.delete(1L);
        System.out.println("删除行数:" + rows);
    }
}

三、MyBatis 高级用法

1. 分页插件(PageHelper)

(1)引入依赖

xml

复制代码
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.7</version>
</dependency>
(2)配置(application.yml)

yaml

复制代码
pagehelper:
  helper-dialect: mysql  # 数据库方言
  reasonable: true       # 页码合理化(页码<=0 查第1页,页码>总页数查最后一页)
  support-methods-arguments: true # 支持通过参数传递分页参数
(3)使用示例

java

运行

复制代码
@Test
public void testPage() {
    // 第1页,每页10条
    PageHelper.startPage(1, 10);
    List<User> userList = userService.selectByCondition(null, null);
    // 分页结果封装
    PageInfo<User> pageInfo = new PageInfo<>(userList);
    System.out.println("总条数:" + pageInfo.getTotal());
    System.out.println("总页数:" + pageInfo.getPages());
    System.out.println("当前页数据:" + pageInfo.getList());
}

2. 二级缓存

(1)开启全局缓存(application.yml)

yaml

复制代码
mybatis:
  configuration:
    cache-enabled: true  # 开启二级缓存(默认 true)
(2)在 Mapper XML 中配置缓存

xml

复制代码
<!-- UserMapper.xml 中添加 -->
<cache 
  eviction="LRU"        # 缓存淘汰策略(LRU/FIFO/SOFT/WEAK)
  flushInterval="60000" # 自动刷新时间(毫秒)
  size="1024"           # 缓存最大条目数
  readOnly="true"/>     # 只读缓存(性能更高)

注意 :二级缓存基于 Mapper 级别,仅适用于查询频繁、修改较少的场景;实体类需实现 Serializable 接口。

3. 自定义类型处理器

解决特殊类型映射(如 JSON 字段、枚举),示例:将 List<String> 映射为数据库 JSON 字段。

(1)自定义类型处理器

java

运行

复制代码
package com.example.mybatis.typehandler;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import com.alibaba.fastjson2.JSON;
import java.sql.*;
import java.util.List;

public class ListTypeHandler extends BaseTypeHandler<List<String>> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, JSON.toJSONString(parameter));
    }

    @Override
    public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return JSON.parseArray(rs.getString(columnName), String.class);
    }

    @Override
    public List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return JSON.parseArray(rs.getString(columnIndex), String.class);
    }

    @Override
    public List<String> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return JSON.parseArray(cs.getString(columnIndex), String.class);
    }
}
(2)配置类型处理器

yaml

复制代码
mybatis:
  type-handlers-package: com.example.mybatis.typehandler # 扫描自定义类型处理器
(3)使用示例(XML)

xml

复制代码
<resultMap id="BaseResultMap" type="User">
    <result column="tags" property="tags" typeHandler="com.example.mybatis.typehandler.ListTypeHandler"/>
</resultMap>

四、常见问题与最佳实践

1. 常见问题

问题场景 解决方案
字段名与属性名不一致 开启 map-underscore-to-camel-case 或自定义 resultMap
SQL 注入风险 使用 #{}(预编译),避免 ${}(直接拼接);参数校验
关联查询性能差 按需使用 association/collection,避免 N+1 查询(可通过 fetchType="lazy" 懒加载)
分页查询繁琐 集成 PageHelper 插件
主键回填失败 确保 useGeneratedKeys="true" + keyProperty 绑定正确,数据库主键自增

2. 最佳实践

  1. SQL 管理 :复杂 SQL 写 XML,简单 SQL 用注解(如 @Select
  2. 参数传递 :多参数使用 @Param 注解,避免 Map 传递(可读性差)
  3. 结果映射 :统一使用 resultMap,避免重复配置
  4. 事务控制 :在 Service 层加 @Transactional,指定 rollbackFor = Exception.class
  5. 日志调试:开启 MyBatis SQL 日志,便于定位问题
  6. 性能优化
    • 避免全表扫描,给查询字段加索引
    • 合理使用缓存(一级缓存默认开启,二级缓存按需开启)
    • 批量操作使用 foreach + BatchExecutor

五、总结

MyBatis 与 Spring Boot 的整合核心是自动配置 + 简化开发

  • Spring Boot 自动创建 SqlSessionFactorySqlSession,无需手动管理
  • 通过 @Mapper@MapperScan 扫描 Mapper 接口,动态代理生成实现类
  • 结合 XML / 注解灵活编写 SQL,适配各类业务场景

掌握 MyBatis 的核心是理解映射规则动态 SQL,而 Spring Boot 集成则重点关注配置简化、插件扩展和事务管理。在实际开发中,需结合业务场景选择合适的用法(XML / 注解、缓存策略、分页方式),兼顾灵活性和性能。

相关推荐
A尘埃2 小时前
Java业务场景(高并发+高可用+分布式)
java·开发语言·分布式
白仑色2 小时前
java中的anyMatch和allMatch方法
java·linux·windows·anymatch·allmatch
刃神太酷啦2 小时前
C++ list 容器全解析:从构造到模拟实现的深度探索----《Hello C++ Wrold!》(16)--(C/C++)
java·c语言·c++·qt·算法·leetcode·list
wearegogog1232 小时前
C# 条码打印程序(一维码 + 二维码)
java·开发语言·c#
码农阿豪2 小时前
用 PlaylistDL 攒私人音乐库?加个 cpolar,出门在外也能随时听!
java
LaughingDangZi2 小时前
vue+java分离项目实现微信公众号开发全流程梳理
java·前端·后端
爬山算法2 小时前
Netty(14)如何处理Netty中的异常和错误?
java·前端·数据库
李慕婉学姐2 小时前
【开题答辩过程】以《基于Android的健康助手APP的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
android·java·mysql