从入门到精通:Spring Boot 整合 MyBatis 全攻略

一、初识 MyBatis:为什么它如此受欢迎?

在 Java 持久层框架的版图中,MyBatis 无疑占据着举足轻重的地位。它是一个优秀的半自动 ORM(对象关系映射)框架,能够将 Java 对象与数据库记录进行映射,支持自定义 SQL、存储过程以及高级映射。相比于 Hibernate 的全自动 ORM,MyBatis 允许开发者直接编写 SQL,在需要精细控制 SQL 执行和复杂查询的场景中,展现出无可比拟的灵活性和性能优势。

简单来说,MyBatis 的核心工作就是 将 SQL 语句与 Java 代码解耦,通过 XML 或注解的配置方式,让你既能享受框架带来的便捷,又能牢牢掌控 SQL 的每一个细节。正因如此,MyBatis 在国内的 Java 开发社区中堪称"半壁江山",几乎是企业级项目数据访问层的标准配置。

1.1 MyBatis 的核心优势

SQL 与代码解耦:MyBatis 通过 XML 或注解方式将 SQL 语句与 Java 代码分离,开发者可独立维护数据库操作逻辑。DBA 可以直接优化 SQL 而无需修改业务代码,这在需要精细控制 SQL 的遗留系统迁移场景中尤为珍贵。

动态 SQL 灵活构建 :MyBatis 提供 <if><choose><foreach> 等标签实现条件拼接,相比于 JPA 的 Criteria API,MyBatis 的动态 SQL 更接近原生 SQL 语法,调试成本可降低 60% 以上。

性能优化空间巨大 :一级缓存默认开启,可减少重复查询,缓存命中率可达 35%;延迟加载通过 fetchType="lazy" 实现关联对象按需加载,避免 N+1 查询问题;批处理模式下,1000 条插入操作可从 10 秒压缩至 2 秒。

数据库兼容性强 :支持 Oracle、MySQL、PostgreSQL 等 12 种主流数据库,通过 typeAliastypeHandlers 可无缝适配特殊数据类型。

1.2 MyBatis 的局限性

当然,任何框架都不是万能的。当实体类包含 50+ 字段时,XML 映射文件容易变得臃肿(单个实体映射文件超过 800 行时,字段修改导致的维护错误率可能上升 40%)。此外,分页需要手动处理,相比 MyBatis-Plus 的自动分页略显繁琐。

二、MyBatis 核心架构解析

2.1 核心组件

MyBatis 的核心组件如同一个精密的机器,各司其职:

组件 作用
SqlSessionFactoryBuilder 使用建造者模式,解析配置文件并创建 SqlSessionFactory
SqlSessionFactory 工厂类,用于创建 SqlSession,应设计为单例
SqlSession 代表一次数据库会话,提供增删改查 API,线程不安全
Configuration 存储全局配置信息、映射信息、类型处理器,是 MyBatis 的"大脑"
Executor 执行器,负责执行 SQL,维护一级缓存
StatementHandler 封装 JDBC Statement 的操作
ParameterHandler 负责处理 SQL 参数
ResultSetHandler 负责结果集的映射

2.2 一条 SQL 的执行全流程

以执行一次 userMapper.selectUserById(1) 为例,背后的故事是这样的:

复制代码
1. 初始化
   ├─ SqlSessionFactoryBuilder → 构建 SqlSessionFactory
   └─ SqlSessionFactory → 解析 XML/注解,构建 Configuration

2. 获取 SqlSession
   └─ SqlSessionFactory.openSession() → 创建 DefaultSqlSession

3. Mapper 动态代理
   └─ UserMapper mapper = sqlSession.getMapper(UserMapper.class)(生成 MapperProxy 代理对象)

4. 执行 SQL
   ├─ mapper.selectUserById(1)
   ├─ MapperProxy.invoke() → 根据方法找到 MappedStatement
   └─ SqlSession.selectOne(mappedStatement, params)

5. Executor 执行
   ├─ CachingExecutor (二级缓存) → delegate → SimpleExecutor
   ├─ 创建 StatementHandler
   ├─ ParameterHandler 设置参数
   ├─ JDBC PreparedStatement 执行 SQL
   └─ ResultSetHandler 映射结果

6. 返回结果

整个流程的核心在于:配置 → SqlSessionFactory → SqlSession → Executor → MappedStatement → 数据库

2.3 MappedStatement 的秘密

MappedStatement 是 MyBatis 中一个关键的底层封装对象,它包装了 MyBatis 配置信息及 SQL 映射信息。mapper.xml 文件中的一个 SQL 对应一个 MappedStatement 对象,SQL 的 id 就是 MappedStatement 的 id。在 SQL 执行前,Executor 通过 MappedStatement 将输入的 Java 对象映射至 SQL 中;执行后,再将输出结果映射至 Java 对象中。

三、Spring Boot 整合 MyBatis(实战篇)

3.1 依赖导入

在 Spring Boot 项目中整合 MyBatis,首先需要在 pom.xml 中添加相关依赖:

xml 复制代码
<!-- MyBatis Spring Boot Starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.2</version>
</dependency>

<!-- MySQL 驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- 可选:连接池(HikariCP 已默认包含) -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
</dependency>

💡 小贴士mybatis-spring-boot-starter 会自动引入 mybatismybatis-spring 依赖,我们只需添加这一个 starter 即可。

3.2 配置文件详解(application.yml)

yaml 复制代码
server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      maximum-pool-size: 10              # 最大连接池数
      minimum-idle: 5                     # 最小空闲连接
      connection-timeout: 30000           # 连接超时时间
      idle-timeout: 600000                # 空闲超时时间
      max-lifetime: 1800000               # 连接最大生命周期

mybatis:
  # 实体类包路径,用于别名自动注册
  type-aliases-package: com.example.demo.entity
  # Mapper XML 文件位置
  mapper-locations: classpath:mapper/*.xml
  configuration:
    # 开启驼峰命名自动映射(例如:user_name → userName)
    map-underscore-to-camel-case: true
    # 开启二级缓存
    cache-enabled: true
    # 开启延迟加载
    lazy-loading-enabled: true
    # 日志输出
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

关键配置解读:

  • map-underscore-to-camel-case: true :这个配置非常实用!开启后,数据库字段 user_name 会自动映射到 Java 实体类的 userName 属性,省去了大量手动映射的烦恼。
  • type-aliases-package:指定实体类包路径后,在 Mapper XML 中就可以直接使用类名(小写形式)而非全限定名。
  • mapper-locations:指定 XML 映射文件的存放位置。

3.3 基于 XML 的开发方式

这是 MyBatis 最经典的开发模式,适合复杂的 SQL 映射场景。

Step 1:创建实体类

java 复制代码
package com.example.demo.entity;

import lombok.Data;
import java.util.Date;

@Data
public class User {
    private Long id;
    private String userName;
    private String password;
    private String email;
    private Integer age;
    private Integer status;
    private Date createTime;
    private Date updateTime;
}

Step 2:创建 Mapper 接口

java 复制代码
package com.example.demo.mapper;

import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;

@Mapper
public interface UserMapper {
    User selectUserById(@Param("id") Long id);
    List<User> selectUserList(@Param("userName") String userName);
    int insertUser(User user);
    int updateUser(User user);
    int deleteUserById(@Param("id") Long id);
}

Step 3:编写 Mapper XML 文件

src/main/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">
<mapper namespace="com.example.demo.mapper.UserMapper">
    
    <!-- 结果映射 -->
    <resultMap id="BaseResultMap" type="User">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="user_name" property="userName" jdbcType="VARCHAR"/>
        <result column="password" property="password" jdbcType="VARCHAR"/>
        <result column="email" property="email" jdbcType="VARCHAR"/>
        <result column="age" property="age" jdbcType="INTEGER"/>
        <result column="status" property="status" jdbcType="INTEGER"/>
        <result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
        <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
    </resultMap>
    
    <!-- 基础字段列表 -->
    <sql id="Base_Column_List">
        id, user_name, password, email, age, status, create_time, update_time
    </sql>
    
    <!-- 根据 ID 查询用户 -->
    <select id="selectUserById" resultMap="BaseResultMap">
        SELECT <include refid="Base_Column_List"/>
        FROM user
        WHERE id = #{id}
    </select>
    
    <!-- 条件查询用户列表 -->
    <select id="selectUserList" resultMap="BaseResultMap">
        SELECT <include refid="Base_Column_List"/>
        FROM user
        WHERE 1=1
        <if test="userName != null and userName != ''">
            AND user_name LIKE CONCAT('%', #{userName}, '%')
        </if>
        ORDER BY create_time DESC
    </select>
    
    <!-- 插入用户 -->
    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (user_name, password, email, age, status, create_time, update_time)
        VALUES (#{userName}, #{password}, #{email}, #{age}, #{status}, NOW(), NOW())
    </insert>
    
    <!-- 更新用户 -->
    <update id="updateUser">
        UPDATE user
        <set>
            <if test="userName != null">user_name = #{userName},</if>
            <if test="password != null">password = #{password},</if>
            <if test="email != null">email = #{email},</if>
            <if test="age != null">age = #{age},</if>
            <if test="status != null">status = #{status},</if>
            update_time = NOW()
        </set>
        WHERE id = #{id}
    </update>
    
    <!-- 删除用户 -->
    <delete id="deleteUserById">
        DELETE FROM user WHERE id = #{id}
    </delete>
    
</mapper>

Step 4:创建 Service 层

java 复制代码
package com.example.demo.service;

import com.example.demo.entity.User;
import java.util.List;

public interface UserService {
    User getUserById(Long id);
    List<User> getUserList(String userName);
    int addUser(User user);
    int modifyUser(User user);
    int removeUser(Long id);
}
java 复制代码
package com.example.demo.service.impl;

import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
@Transactional
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Override
    public User getUserById(Long id) {
        return userMapper.selectUserById(id);
    }
    
    @Override
    public List<User> getUserList(String userName) {
        return userMapper.selectUserList(userName);
    }
    
    @Override
    public int addUser(User user) {
        return userMapper.insertUser(user);
    }
    
    @Override
    public int modifyUser(User user) {
        return userMapper.updateUser(user);
    }
    
    @Override
    public int removeUser(Long id) {
        return userMapper.deleteUserById(id);
    }
}

Step 5:Controller 层测试

java 复制代码
package com.example.demo.controller;

import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userService.getUserById(id);
    }
    
    @GetMapping("/list")
    public List<User> getUserList(@RequestParam(required = false) String userName) {
        return userService.getUserList(userName);
    }
    
    @PostMapping
    public int addUser(@RequestBody User user) {
        return userService.addUser(user);
    }
    
    @PutMapping
    public int updateUser(@RequestBody User user) {
        return userService.modifyUser(user);
    }
    
    @DeleteMapping("/{id}")
    public int deleteUser(@PathVariable Long id) {
        return userService.removeUser(id);
    }
}

3.4 基于注解的开发方式

对于简单的 CRUD 操作,注解方式更加简洁高效:

java 复制代码
package com.example.demo.mapper;

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

@Mapper
public interface UserAnnotationMapper {
    
    @Select("SELECT id, user_name, password, email, age, status, create_time, update_time FROM user WHERE id = #{id}")
    @Results(id = "userResultMap", value = {
        @Result(column = "id", property = "id", id = true),
        @Result(column = "user_name", property = "userName"),
        @Result(column = "create_time", property = "createTime"),
        @Result(column = "update_time", property = "updateTime")
    })
    User selectUserById(@Param("id") Long id);
    
    @SelectProvider(type = UserSqlProvider.class, method = "selectUserList")
    @ResultMap("userResultMap")
    List<User> selectUserList(@Param("userName") String userName);
    
    @Insert("INSERT INTO user (user_name, password, email, age, status, create_time, update_time) " +
            "VALUES (#{userName}, #{password}, #{email}, #{age}, #{status}, NOW(), NOW())")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insertUser(User user);
    
    @Update("UPDATE user SET user_name = #{userName}, password = #{password}, email = #{email}, " +
            "age = #{age}, status = #{status}, update_time = NOW() WHERE id = #{id}")
    int updateUser(User user);
    
    @Delete("DELETE FROM user WHERE id = #{id}")
    int deleteUserById(@Param("id") Long id);
}

对于复杂查询,可以使用 @SelectProvider 配合 SQL 构建器实现动态 SQL:

java 复制代码
package com.example.demo.mapper;

import org.apache.ibatis.jdbc.SQL;

public class UserSqlProvider {
    
    public String selectUserList(String userName) {
        return new SQL() {{
            SELECT("id, user_name, password, email, age, status, create_time, update_time");
            FROM("user");
            if (userName != null && !userName.isEmpty()) {
                WHERE("user_name LIKE CONCAT('%', #{userName}, '%')");
            }
            ORDER_BY("create_time DESC");
        }}.toString();
    }
}

3.5 Mapper 扫描配置

除了在每一个 Mapper 接口上添加 @Mapper 注解,还可以通过 @MapperScan 在配置类中批量扫描:

java 复制代码
package com.example.demo.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.example.demo.mapper")  // 扫描整个 mapper 包
public class MyBatisConfig {
}

四、MyBatis 核心功能深入

4.1 参数传递方式

在 MyBatis 中,Mapper 接口方法支持多种参数传递方式:

方式一:使用 @Param 注解(推荐)

java 复制代码
User selectUser(@Param("id") Long id, @Param("status") Integer status);
xml 复制代码
<select id="selectUser" resultType="User">
    SELECT * FROM user WHERE id = #{id} AND status = #{status}
</select>

方式二:使用对象参数

java 复制代码
int insertUser(User user);
xml 复制代码
<insert id="insertUser">
    INSERT INTO user (user_name, password) VALUES (#{userName}, #{password})
</insert>

方式三:使用 Map 参数

java 复制代码
List<User> selectUserByMap(Map<String, Object> params);

4.2 结果集映射

MyBatis 提供了两种结果集映射方式:

简单映射(resultType) :当查询结果的列名与 Java 对象属性名一致(或开启驼峰映射后能匹配)时使用。

复杂映射(resultMap) :支持一对一、一对多等复杂关系映射:

  • 一对一(association) :适用于主表与从表是一对一关系(如:用户和用户详情)
  • 一对多(collection) :适用于主表与从表是一对多关系(如:用户和订单列表)
xml 复制代码
<!-- 一对一关联映射 -->
<resultMap id="UserWithDetailResultMap" type="User" extends="BaseResultMap">
    <association property="userDetail" javaType="UserDetail" column="id"
                 select="com.example.demo.mapper.UserDetailMapper.selectByUserId"/>
</resultMap>

<!-- 一对多关联映射 -->
<resultMap id="UserWithOrdersResultMap" type="User" extends="BaseResultMap">
    <collection property="orderList" ofType="Order" column="id"
                select="com.example.demo.mapper.OrderMapper.selectByUserId"/>
</resultMap>

💡 性能提示 :对于关联映射,建议开启延迟加载(lazyLoadingEnabled=true),避免 N+1 查询问题。

4.3 动态 SQL(核心精华)

动态 SQL 是 MyBatis 最强大的特性之一,允许根据运行时条件动态生成 SQL 语句。

常用动态 SQL 标签详解
标签 作用 示例
<if> 条件判断,满足条件时包含 SQL 片段 <if test="name != null">AND name = #{name}</if>
<choose> 多条件分支(类似 switch-case) <choose><when test="age != null">AND age = #{age}</when><otherwise>AND age > 18</otherwise></choose>
<where> 智能处理 WHERE 子句,自动去除开头 AND/OR <where><if test="name != null">AND name = #{name}</if></where>
<set> 智能处理 UPDATE 语句,自动去除结尾逗号 <set><if test="name != null">name = #{name},</if></set>
<foreach> 遍历集合生成批量操作 <foreach item="id" collection="ids" open="(" separator="," close=")">#{id}</foreach>
<trim> 自定义字符串修剪 <trim prefix="WHERE" prefixOverrides="AND">...</trim>
<bind> 创建变量并绑定到上下文 <bind name="pattern" value="'%' + keyword + '%'" />
批量插入示例
xml 复制代码
<insert id="batchInsertUsers">
    INSERT INTO user (user_name, password, email) VALUES
    <foreach collection="list" item="user" separator=",">
        (#{user.userName}, #{user.password}, #{user.email})
    </foreach>
</insert>
批量删除示例
xml 复制代码
<delete id="batchDeleteUsers">
    DELETE FROM user WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</delete>

动态 SQL 最佳实践 :避免嵌套超过 3 层,使用 <where><set> 减少无效字符处理开销,参数合法性在 Java 层校验,简单场景可用 @SelectProvider 替代 XML。

4.4 缓存机制

MyBatis 提供了两级缓存来提升查询性能:

一级缓存(Local Cache)
  • 作用域:SqlSession 级别,默认开启且无法关闭
  • 生命周期 :与 SqlSession 一致,执行 INSERT/UPDATE/DELETE 操作或调用 commit()close() 时会清空
  • 适用场景:同一会话内的重复查询,缓存命中率约 5%
二级缓存(Global Cache)
  • 作用域:Mapper 级别(跨 SqlSession 共享),需手动配置开启
  • 存储介质:默认 JVM 内存,可集成 Redis、Ehcache 等
  • 适用场景:多用户并发访问、查询频率高且更新不频繁的数据,缓存命中率可达 90%

启用二级缓存配置

yaml 复制代码
# application.yml
mybatis:
  configuration:
    cache-enabled: true   # 全局开启二级缓存
xml 复制代码
<!-- Mapper XML 中添加 cache 标签 -->
<mapper namespace="com.example.demo.mapper.UserMapper">
    <cache eviction="LRU" flushInterval="60000" size="512" readOnly="false"/>
</mapper>
java 复制代码
// 或者使用注解方式
@CacheNamespace(eviction = LruCache.class, flushInterval = 60000, size = 512)
public interface UserMapper {
}

五、分页方案

5.1 PageHelper 插件(推荐)

PageHelper 是目前最流行的 MyBatis 分页插件,只需在查询前调用 PageHelper.startPage() 即可自动实现物理分页。

添加依赖

xml 复制代码
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>

使用示例

java 复制代码
@Service
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    public PageInfo<User> getUserListByPage(int pageNum, int pageSize, String userName) {
        // 开启分页(紧跟在后面的第一个查询会被分页)
        PageHelper.startPage(pageNum, pageSize);
        
        // 执行查询
        List<User> userList = userMapper.selectUserList(userName);
        
        // 封装分页结果
        return new PageInfo<>(userList);
    }
}

Controller 调用

java 复制代码
@GetMapping("/page")
public PageInfo<User> getUserByPage(@RequestParam(defaultValue = "1") int pageNum,
                                     @RequestParam(defaultValue = "10") int pageSize,
                                     @RequestParam(required = false) String userName) {
    return userService.getUserListByPage(pageNum, pageSize, userName);
}

5.2 分页实现方式对比

MyBatis 主要有五种分页实现方式:

方式 类型 原理 适用场景 性能
RowBounds 逻辑分页 查询全部结果,内存中截取 小数据量 大数据量易内存溢出
PageHelper 物理分页 拦截器动态添加 LIMIT 子句 通用推荐 优秀
SQL 分页 物理分页 手动编写 LIMIT/OFFSET 简单场景 较好
数组分页 逻辑分页 查全表后 subList 截取 小数据量
拦截器分页 物理分页 自定义拦截器统一处理 需要统一分页规范时 优秀

💡 最佳实践 :推荐优先使用 PageHelper 等物理分页方式,避免深分页陷阱。对于超大数据量场景,建议设置 count=false 跳过统计总记录数以提升性能。

六、性能优化指南

6.1 SQL 语句优化

  • 精准查询字段 :避免使用 SELECT *,只查询业务所需字段
  • 使用 <sql> 标签:定义可复用字段集合,减少重复书写
  • 批量操作合并 :使用 <foreach> 生成批量插入/更新语句,减少数据库交互次数
  • 避免 N+1 查询:合理使用延迟加载或联表查询

6.2 缓存策略优化

  • 一级缓存 :默认开启,需配合 @Transactional 注解生效,适用于单事务内重复查询场景
  • 二级缓存:需手动配置开启,适合多用户并发访问场景。注意:不推荐跨 Mapper 共享热点数据,易引发缓存雪崩
  • 分布式缓存 :在二级缓存基础上集成 Redis,通过 @Cacheable 注解实现跨服务缓存一致性

6.3 连接池与监控

  • 连接池推荐 :HikariCP(Spring Boot 默认)或 Druid,根据业务负载调整 maximum-pool-sizeconnection-timeout 等参数
  • 监控工具:通过 Druid 监控 SQL 执行时间,识别慢查询
  • 日志控制 :生产环境关闭 MyBatis 详细日志(log4j.logger.org.mybatis=ERROR),减少 IO 开销

七、多数据源配置

在微服务或复杂业务场景中,多数据源的需求十分常见。推荐使用 dynamic-datasource-spring-boot-starter 开源组件来优雅地实现。

7.1 添加依赖

xml 复制代码
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>4.3.0</version>
</dependency>

7.2 配置文件

yaml 复制代码
spring:
  datasource:
    dynamic:
      primary: master                    # 默认数据源
      strict: false                     # 严格模式(匹配不到时抛异常)
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/master_db
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
        slave:
          url: jdbc:mysql://localhost:3306/slave_db
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver

7.3 使用 @DS 注解切换数据源

java 复制代码
@Service
@DS("master")                    // 类级别指定默认数据源
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    @Override
    @DS("slave")                 // 方法级别覆盖,从从库读取
    public User getUserById(Long id) {
        return userMapper.selectUserById(id);
    }
    
    @Override
    @DS("master")                // 写入操作使用主库
    public int addUser(User user) {
        return userMapper.insertUser(user);
    }
}

💡 注解优先顺序:方法级别 > 类级别,dynamic-datasource 还支持读写分离、敏感信息加密、分布式事务(基于 Seata)等高级特性。

八、MyBatis-Plus:MyBatis 的超强增强工具

MyBatis-Plus(简称 MP)是 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,极大简化了单表 CRUD 操作。

8.1 核心特性

  • 无侵入:只做增强,不改动 MyBatis 原有逻辑
  • 内置通用 Mapper :提供 BaseMapper 接口,包含常用 CRUD 方法,无需编写 XML
  • 条件构造器 :提供 LambdaQueryWrapper,类型安全的条件构造体验
  • 代码生成器:自动生成 Entity、Mapper、Service、Controller 全套代码
  • 内置分页插件:基于物理分页,支持多种数据库

8.2 代码生成器

MyBatis-Plus 的代码生成器 AutoGenerator 可以根据数据库表结构自动生成整套代码,极大提升开发效率。

添加依赖

xml 复制代码
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.3</version>
</dependency>
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.31</version>
</dependency>

编写代码生成器主类

java 复制代码
public class CodeGenerator {
    public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis_demo", "root", "123456")
            .globalConfig(builder -> {
                builder.author("your_name")
                       .outputDir(System.getProperty("user.dir") + "/src/main/java")
                       .commentDate("yyyy/MM/dd");
            })
            .packageConfig(builder -> {
                builder.parent("com.example.demo")
                       .entity("entity")
                       .mapper("mapper")
                       .service("service")
                       .serviceImpl("service.impl")
                       .controller("controller");
            })
            .strategyConfig(builder -> {
                builder.addInclude("user")        // 指定要生成的表名
                       .entityBuilder()
                       .enableLombok()            // 开启 Lombok 支持
                       .idType(IdType.AUTO);      // 主键策略
            })
            .templateEngine(new FreemarkerTemplateEngine())
            .execute();
    }
}

九、常见问题与解决方案

9.1 驼峰命名映射问题

如果开启了 map-underscore-to-camel-case 后仍映射失败,检查:

  1. 实体类属性是否使用了 Lombok(确保有 getter/setter)
  2. 数据库字段名与属性名是否真的匹配(如 userName vs user_name

9.2 SQL 注入问题

始终使用 #{}(预编译参数)而不是 ${}(字符串拼接)${} 仅用于无法预编译的场景,如动态表名、列名。务必对用户输入进行严格校验。

9.3 分页失效问题

如果 PageHelper 分页不生效,检查:

  1. PageHelper.startPage() 后是否立即执行了查询
  2. 分页参数是否被正确传递
  3. 是否有其他拦截器干扰

9.4 延迟加载不生效

确保在配置文件中开启 lazyLoadingEnabled=true,且关联查询使用 select 属性指定子查询。

十、总结与展望

本文从 MyBatis 的核心概念、架构原理入手,详细讲解了 Spring Boot 整合 MyBatis 的两种主流方式(XML 与注解),深入剖析了动态 SQL、缓存机制、分页方案、性能优化、多数据源配置等进阶内容,最后介绍了 MyBatis-Plus 这个强大的增强工具。

在实际项目开发中,建议根据场景选择合适的组合:

  • 简单项目:Spring Boot + MyBatis(注解方式)+ PageHelper
  • 复杂项目:Spring Boot + MyBatis(XML 方式)+ MyBatis-Plus(仅使用增强功能)
  • 极速开发:Spring Boot + MyBatis-Plus + 代码生成器

MyBatis 以其灵活性和高性能,在国内 Java 社区拥有极其庞大的用户基础。掌握 MyBatis,不仅是一项必备的 Java 开发技能,更是通向高级工程师道路上的一块重要基石。希望本文能帮助你全面掌握 MyBatis 的核心知识,在实际项目中游刃有余!


📚 参考资料

相关推荐
摆烂工程师2 小时前
教你如何查询 Codex 最新额度是多少,以及 ChatGPT Pro、Plus、Business 最新额度变化
前端·后端·ai编程
任聪聪2 小时前
我做了一款通用本地化部署模型运行调度器,运行所有大模型!
后端
开发者如是说2 小时前
可能是最好用的多语言管理工具
android·前端·后端
何陋轩4 小时前
AI时代,程序员何去何从?别慌,看完这篇你就明白了
后端·面试
weixin_408099674 小时前
OCR 识别率提升实战:模糊 / 倾斜 / 反光图片全套优化方案(附 Python / Java / PHP 代码)
图像处理·人工智能·后端·python·ocr·api·抠图
weixin_408099674 小时前
【实战教程】懒人精灵如何实现 OCR 文字识别?接口调用完整指南(附可运行示例)
java·前端·人工智能·后端·ocr·api·懒人精灵
珍朱(珠)奶茶4 小时前
Spring Boot3整合Jxls工具包实现模版excel导出文件
spring boot·后端·excel
Daemon4 小时前
AI Agent系列记录(第二篇)
前端·人工智能·后端
冰心少年4 小时前
ROS2节点:机器人的工作细胞
后端