一文详解SpringBoot数据源与MyBatis-Plus自定义配置

 更新时间:2026年11月05日 08:51:18   作者:霸道流氓气质  
本文将为大家详细介绍SpringBoot中HikariCP数据源配置、自定义SqlSessionFactory关键项以及MyBatis-Plus分页插件的Interceptor机制与协同装配等内容,感兴趣的小伙伴可以了解下

一、背景与整体脉络

一个典型的 Spring Boot 持久层由三层配置串联而成:

HikariCP 连接池(管理物理连接)
      ↓ 提供 DataSource
SqlSessionFactory(MyBatis 会话工厂,定义 SQL 执行行为)
      ↓ 注册插件
MybatisPlusInterceptor(分页等拦截器)

当默认的自动装配无法满足需求时(例如要定制缓存策略、日志实现、下划线转驼峰、分页上限等),就需要手动声明 SqlSessionFactory Bean 来接管配置。本文围绕"单数据源 HikariCP + 自定义 SqlSessionFactory + 分页插件"这套组合,梳理背后的技术、概念与通用示例。

二、数据源与 HikariCP 连接池

2.1 为什么需要连接池

创建一个数据库物理连接(TCP 握手、认证、会话初始化)开销很大。连接池的核心思想是:预先创建并复用一批连接,请求用完后归还而非关闭,从而避免频繁建连销连的开销,并通过上限控制保护数据库。

2.2 HikariCP

HikariCP 是 Spring Boot 2.x 起的默认连接池,以"轻量、高性能、低延迟"著称。其性能优势来自字节码精简、无锁设计、FastList/ConcurrentBag 等自研数据结构。

2.3 核心参数解析

以"单数据源、最大 60 连接"为例的典型配置:

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://host:3306/db?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
    username: app_user
    password: ******
    hikari:
      auto-commit: true            # 自动提交(非事务方法下每条语句即提交)
      minimum-idle: 10             # 最小空闲连接数
      maximum-pool-size: 60        # 最大连接数(并发上限)
      idle-timeout: 30000          # 空闲连接存活时间 30s
      max-lifetime: 1800000        # 连接最大生命周期 30min
      connection-timeout: 30000    # 获取连接的最长等待 30s
      connection-test-query: select 1   # 连接存活检测语句
参数作用调优要点
maximum-pool-size连接池上限,决定并发访问 DB 的最大能力并非越大越好,受数据库 max_connections 制约;经验公式 连接数 ≈ CPU核数 × 2 + 有效磁盘数
minimum-idle保持的最小空闲连接,降低突发流量建连延迟官方建议与 maximum-pool-size 设为相同值以获得最稳定性能
max-lifetime连接最大寿命,到期后主动淘汰重建应略小于数据库/防火墙的连接回收时间(如 MySQL wait_timeout)
idle-timeout空闲连接回收时间仅在 minimum-idle < maximum-pool-size 时生效
connection-timeout等不到连接时抛异常的阈值过短易误报,过长会堆积请求
connection-test-query校验连接有效性现代驱动支持 Connection.isValid(),一般可省略

2.4 单数据源 vs 多数据源

  • 单数据源:整个应用只有一个 DataSource Bean,Spring Boot 自动装配即可直接使用,SqlSessionFactory、事务管理器都绑定到它。配置简单、无需路由、事务一致性天然成立。
  • 多数据源:应用需要连接多个库(如主库 + 从库、业务库 + 日志库)。需要手动声明多个 DataSource,并为每个配独立的 SqlSessionFactory / TransactionManager,还要通过 @MapperScan 的 sqlSessionFactoryRef 或 AbstractRoutingDataSource 做路由。复杂度显著上升,跨库事务还需分布式事务方案。

选择单数据源的意义:当业务只访问单一数据库时,单数据源能避免多数据源带来的路由复杂度、事务边界模糊、配置冗余等问题。即便手动声明了 SqlSessionFactory,只要只注入唯一的 DataSource,依然是单数据源架构。

三、自定义 SqlSessionFactory

3.1 SqlSessionFactory 是什么

SqlSessionFactory 是 MyBatis 的核心工厂对象,负责创建 SqlSession(一次数据库会话)。它持有一份全局 Configuration,决定了所有 SQL 的执行行为:映射规则、缓存策略、日志、类型处理器、插件等。

Spring Boot 的 mybatis-spring-boot-starter 会自动创建一个 SqlSessionFactory。但当需要精细控制 Configuration 时,就手动声明一个 SqlSessionFactory Bean 来覆盖默认行为。使用 MyBatis-Plus 时,用的是它增强版的 MybatisSqlSessionFactoryBean 和 MybatisConfiguration。

3.2 通用示例

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import javax.sql.DataSource;
import org.apache.ibatis.logging.slf4j.Slf4jImpl;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.LocalCacheScope;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

@Configuration
@MapperScan(
    basePackages = {"com.example.app.**.dao.mybatis"},
    sqlSessionFactoryRef = "sqlSessionFactory")
public class DataSourceConfig {

    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(
            DataSource dataSource,
            MybatisPlusInterceptor mybatisPlusInterceptor) throws Exception {

        // 1. 全局 Configuration
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setMapUnderscoreToCamelCase(true);     // 下划线转驼峰
        configuration.setLogImpl(Slf4jImpl.class);            // 日志用 Slf4j 输出 SQL
        configuration.setLocalCacheScope(LocalCacheScope.STATEMENT); // 一级缓存降为语句级
        configuration.setCallSettersOnNulls(true);            // 值为 null 也调用 setter

        // 2. 工厂 Bean
        MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
        factoryBean.setConfiguration(configuration);
        factoryBean.setDataSource(dataSource);

        // 3. 加载 Mapper XML
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(
                resolver.getResources("classpath*:mapper/**/*.xml"));

        // 4. 注册插件(分页拦截器等)
        factoryBean.setPlugins(new Interceptor[] { mybatisPlusInterceptor });

        return factoryBean.getObject();
    }
}

3.3 关键配置项逐一拆解

1.mapUnderscoreToCamelCase = true—— 下划线转驼峰

数据库列名通常是下划线风格(create_time、user_id),Java 字段是驼峰(createTime、userId)。开启后 MyBatis 自动完成映射,无需在每个 resultMap 里手写 column/property 对应关系。

数据库列 create_time  →  Java 字段 createTime   ✔ 自动映射

2.logImpl = Slf4jImpl.class—— 日志实现

MyBatis 支持多种日志框架(Slf4j、Log4j2、Commons Logging、Stdout 等)。显式指定 Slf4jImpl 让 MyBatis 的 SQL、参数、结果日志统一走项目的 Slf4j/Logback 体系,便于按包名调整日志级别(如把 Mapper 包设为 DEBUG 打印完整 SQL)。

3.localCacheScope = STATEMENT—— 一级缓存降级(重点)

MyBatis **一级缓存(Local Cache)**默认作用域是 SESSION:在同一个 SqlSession 内,相同的查询会直接返回缓存结果,不再查库。这会带来两个问题:

  • 脏读风险:同一会话/事务内,期间若有其他事务更新了数据,缓存仍返回旧值。
  • 中间状态不一致:复杂业务里同一事务多次查询同一条记录,期望拿到最新状态,却被缓存拦截。

设为 STATEMENT 后,缓存只在单条语句执行期间有效,语句结束即清空。等价于关闭一级缓存的跨查询复用,每次查询都真实访问数据库,保证读到最新数据。

作用域缓存范围适用场景
SESSION(默认)整个 SqlSession 生命周期单次会话内重复读同一数据,追求性能
STATEMENT单条语句执行期间对数据实时性要求高,规避脏读

注意:一级缓存与二级缓存(跨 SqlSession)是两套机制,这里调整的是一级缓存。

4.callSettersOnNulls = true—— 空值也调用 setter

默认情况下,当查询结果某列值为 null 时,MyBatis 不会调用对应字段的 setter 方法。这在两种场景下会出问题:

  • 返回 Map 结果:值为 null 的 key 直接不存在于 Map 中,调用方 map.get("key") 得到 null 无法区分"没这个字段"还是"字段值为 null"。
  • 基本类型/默认值语义:期望字段被显式置为 null,但实际保留了对象初始化的默认值。

开启后,即使列值为 null 也会调用 setter(对 Map 则会 put 一个 value 为 null 的 entry),使结果更完整、可预期。

5.mapperLocations—— XML 定位

指定 Mapper XML 文件的位置。classpath*: 前缀表示扫描所有 classpath(含依赖 jar)下匹配的资源,** 表示任意层级目录。

常见坑:当手动声明 SqlSessionFactory 并设置了 mapperLocations 后,application.yml 里的 mybatis.mapper-locations 配置会失效(被手动 Bean 覆盖)。若两处路径不一致,容易出现"XML 找不到、Mapper 方法报 Invalid bound statement"的问题。排查时务必以手动 Bean 中的路径为准。

四、MyBatis-Plus 分页插件

4.1 插件机制(MyBatis Interceptor)

MyBatis 提供 拦截器(Interceptor / Plugin) 扩展点,可对四大核心对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)的方法进行拦截增强。分页正是通过拦截 SQL 执行、动态改写为 LIMIT 语句实现的。

MyBatis-Plus 把多个内部拦截器统一收敛到一个 MybatisPlusInterceptor 里,通过 addInnerInterceptor 添加具体功能(分页、乐观锁、防全表更新删除、多租户等)。

4.2 通用示例

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        // 分页内部拦截器,指定数据库类型以生成正确的分页 SQL
        PaginationInnerInterceptor pagination = new PaginationInnerInterceptor(DbType.MYSQL);
        // 单页最大条数限制,超过则被强制截断;-1 表示不限制
        pagination.setMaxLimit(500L);

        interceptor.addInnerInterceptor(pagination);
        return interceptor;
    }
}

4.3 关键点

配置说明
DbType.MYSQL指定数据库方言。不同数据库分页语法不同(MySQL LIMIT、Oracle ROWNUM、PostgreSQL LIMIT/OFFSET),需显式声明以生成正确 SQL
setMaxLimit(500L)单页最大条数保护。若前端传入 pageSize=100000,会被强制限制到 500,防止一次查询拉取海量数据拖垮数据库和内存(这是一种防御性设计)

4.4 使用方式

// 构造分页对象:第 1 页,每页 20 条
Page<User> page = new Page<>(1, 20);
Page<User> result = userMapper.selectPage(page, queryWrapper);

result.getRecords();  // 当前页数据
result.getTotal();    // 总记录数
result.getPages();    // 总页数

分页插件会自动:① 执行 count 查询得到总数;② 在原 SQL 后拼接 LIMIT;全程无需手写分页 SQL。

五、三者如何协同装配

┌─────────────────────────────────────────────────────┐
│  Spring Boot 自动装配 HikariDataSource(单数据源)      │
└───────────────┬─────────────────────────────────────┘
                │ 注入
                ▼
┌─────────────────────────────────────────────────────┐
│  自定义 SqlSessionFactory Bean                         │
│   · MybatisConfiguration(驼峰/日志/STATEMENT缓存/null)│
│   · setDataSource(dataSource)                         │
│   · setMapperLocations(...)                           │
│   · setPlugins([ mybatisPlusInterceptor ])  ◄─────┐   │
└───────────────┬───────────────────────────────────┼───┘
                │ @MapperScan 绑定                    │ 注入
                ▼                                     │
┌─────────────────────────┐          ┌───────────────┴──────────┐
│  Mapper 接口 + XML        │          │ MybatisPlusInterceptor   │
│  执行 SQL                 │          │  └ PaginationInnerInt.   │
└─────────────────────────┘          │     (MYSQL, maxLimit=500) │
                                      └──────────────────────────┘

依赖关系清晰:数据源 被注入 SqlSessionFactory;分页插件 作为 plugin 注册进 SqlSessionFactory;@MapperScan 把 Mapper 接口绑定到这个指定的 SqlSessionFactory。

六、注意事项与最佳实践

  1. 连接池大小不是越大越好:maximum-pool-size 过大会耗尽数据库连接、增加上下文切换开销。应结合数据库 max_connections 和应用实例数综合计算。
  2. max-lifetime 要小于数据库超时:避免使用到已被数据库/中间件单方面关闭的"僵尸连接"。
  3. STATEMENT 缓存的代价:关闭一级缓存复用意味着同一事务内重复查询会多次访问 DB,牺牲少量性能换取数据实时性,需确认业务确实需要。
  4. callSettersOnNulls 对 Map 返回尤为重要:若有大量 resultType="map" 的查询,建议开启,否则 null 字段会"消失"。
  5. 手动 SqlSessionFactory 会覆盖 yml 配置:mybatis.mapper-locations、type-aliases-package 等 yml 项在手动声明工厂后可能不生效,应统一在代码里配置,避免路径不一致导致 Mapper 绑定失败。
  6. 分页 maxLimit 是安全阀:务必设置合理上限,防止恶意或误传的超大 pageSize 造成全表级查询。
  7. 方言要匹配真实数据库:DbType 配错会生成错误分页 SQL 或静默失效。

七、涉及技术/概念清单

  • 连接池:HikariCP、maximum-pool-size、minimum-idle、max-lifetime、idle-timeout、connection-timeout、connection-test-query、auto-commit
  • 数据源架构:单数据源 vs 多数据源、AbstractRoutingDataSource、sqlSessionFactoryRef
  • MyBatis 核心:SqlSessionFactory、Configuration、SqlSession、@MapperScan、mapperLocations、classpath*: 资源解析
  • MyBatis 配置项:mapUnderscoreToCamelCase、logImpl/Slf4jImpl、localCacheScope(SESSION/STATEMENT 一级缓存)、callSettersOnNulls
  • MyBatis-Plus:MybatisSqlSessionFactoryBean、MybatisConfiguration、MybatisPlusInterceptor、PaginationInnerInterceptor、DbType、maxLimit、Page
  • MyBatis 插件机制:Interceptor 拦截四大对象、InnerInterceptor

到此这篇关于一文详解SpringBoot数据源与MyBatis-Plus自定义配置的文章就介绍到这了,更多相关SpringBoot数据源内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • mysql Buffer Pool的存储结构和内存淘汰机制详解

    mysql Buffer Pool的存储结构和内存淘汰机制详解

    这篇文章主要介绍了mysql Buffer Pool的存储结构和内存淘汰机制详解,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-03-03
  • 与MSSQL对比学习MYSQL的心得(八)--插入 更新 删除

    与MSSQL对比学习MYSQL的心得(八)--插入 更新 删除

    这一篇《与MSSQL对比学习MYSQL的心得(八)》将会讲解MYSQL的插入、更新和删除语句
    2014-08-08
  • MySQL 的 20+ 条最佳实践

    MySQL 的 20+ 条最佳实践

    数据库操作是当今 Web 应用程序中的主要瓶颈。 不仅是 DBA(数据库管理员)需要为各种性能问题操心,程序员为做出准确的结构化表,优化查询性能和编写更优代码,也要费尽心思。 在本文中,我列出了一些针对程序员的 MySQL 优化技术
    2016-12-12
  • phpstudy_pro无法启动MySQL以及3306被占用的问题解决办法

    phpstudy_pro无法启动MySQL以及3306被占用的问题解决办法

    在进行PHP开发时,PHPStudy Pro是一款功能强大且易于使用的集成环境,它能帮助开发者快速搭建起包含Apache、Nginx、PHP、MySQL等的本地开发环境,这篇文章主要介绍了phpstudy_pro无法启动MySQL以及3306被占用的问题解决办法的相关资料,需要的朋友可以参考下
    2026-04-04
  • Windows版mysql 8.0.28 安装配置方法图文教程

    Windows版mysql 8.0.28 安装配置方法图文教程

    这篇文章主要为大家详细介绍了Windows版mysql 8.0.28 安装配置方法图文教程,文中安装步骤介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • MySQL触发器 Update触发Insert失败

    MySQL触发器 Update触发Insert失败

    这篇文章主要介绍了MySQL触发器 Update触发Insert失败 的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-07-07
  • MySQL DATE_ADD和ADDDATE函数实现向日期添加指定时间间隔

    MySQL DATE_ADD和ADDDATE函数实现向日期添加指定时间间隔

    这篇文章主要介绍了MySQL DATE_ADD和ADDDATE函数实现向日期添加指定时间间隔,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • MYSQL分页limit速度太慢的优化方法

    MYSQL分页limit速度太慢的优化方法

    这篇文章主要介绍了MYSQL分页limit速度太慢的优化方法,需要的朋友可以参考下
    2016-05-05
  • MySQL 8.0数据字典缓存管理机制解析

    MySQL 8.0数据字典缓存管理机制解析

    MySQL 8.0中的数据字典,通过对两级缓存的逐级访问,以及精妙的对缓存未命中情况的处理方式,有效的加速了在不同场景下数据库对DD的访问速度,显著的提升了数据库访问元数据信息的效率,这篇文章主要介绍了解读MySQL 8.0数据字典缓存管理机制,需要的朋友可以参考下
    2024-07-07
  • mysql case when group by 实例详解

    mysql case when group by 实例详解

    这篇文章主要介绍了mysql 中类似php switch case 的语句,需要的朋友可以参考下
    2018-01-01

最新评论