乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 MyBatis概述与快速上手

    • 本章定位
    • MyBatis简介
    • 环境搭建
    • 第一个MyBatis程序
    • SqlSessionFactoryBuilder与openSession重载
    • SqlSessionFactory与SqlSession
    • SqlSession核心方法
    • 不使用 XML 构建 SqlSessionFactory
    • Mapper接口与映射方式
    • Java API 目录结构
  • 第2章 全局配置文件详解

    • 本章定位
    • properties
    • settings
    • typeAliases
    • typeHandlers
    • objectFactory
    • plugins
    • environments
    • transactionManager
    • dataSource
    • databaseIdProvider
    • mappers
    • 日志配置
  • 第3章 SQL映射文件基础

    • 本章定位
    • select
    • insert
    • update
    • delete
    • 参数传递与占位符
    • 主键生成策略
    • resultType
    • resultMap
    • 自动映射详解
    • sql片段
    • SQL 语句构建器
  • 第4章 动态SQL

    • 本章定位
    • if
    • choose、when、otherwise
    • where
    • set
    • foreach
    • trim
    • bind
    • script 元素:在注解映射器中启用动态 SQL
    • _databaseId 与动态 SQL 的多数据库支持
    • 动态 SQL 中插入脚本语言
  • 第5章 结果映射与关联查询

    • 本章定位
    • resultMap详解
    • association
    • collection
    • discriminator
    • N+1查询问题
    • 延迟加载
  • 第6章 MyBatis注解开发

    • 本章定位
    • @Select
    • @Insert
    • @Update
    • @Delete
    • @Param
    • @Options
    • @SelectKey
    • @Results
    • @Result
    • @One
    • @Many
    • @SelectProvider
  • 第7章 缓存与性能优化

    • 本章定位
    • 一级缓存
    • 二级缓存
    • 缓存配置详解
    • 自定义缓存
    • Executor执行器类型
    • 分页插件

dataSource

导学

本节学习目标:

  • 理解 dataSource 是 MyBatis 获取数据库连接的"源头"
  • 掌握三种内置数据源类型 UNPOOLED、POOLED、JNDI 的行为差异
  • 深入理解 POOLED 连接池的核心参数与工作原理
  • 能够根据项目场景选择合适的数据源类型并合理调优

定义

数据库连接是昂贵资源:建立一次 TCP 连接、完成 MySQL 握手认证、分配线程和内存,整个过程耗时数十到数百毫秒。如果每次执行 SQL 都新建连接,应用性能将急剧下降,数据库也会因频繁创建连接而负载飙升。

dataSource 元素的作用就是配置 MyBatis 如何获取和管理数据库连接。MyBatis 内置了三种数据源实现,覆盖从开发调试到生产部署的全场景需求。


适用位置与核心属性

dataSource 位于 mybatis-config.xml 中每个 environment 内部,且必须是 transactionManager 的下一个子元素。

<environment id="development">
  <transactionManager type="JDBC"/>
  <dataSource type="POOLED">
    <property name="driver" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
    <property name="poolMaximumActiveConnections" value="20"/>
    <property name="poolMaximumIdleConnections" value="10"/>
  </dataSource>
</environment>
属性是否必填说明
type必填数据源类型。内置支持 UNPOOLED、POOLED、JNDI,也可指定自定义数据源类的全限定名

三种内置类型对比

类型连接行为适用场景
UNPOOLED每次请求都新建连接,用完立即关闭开发调试、单元测试、极轻量级应用
POOLED维护连接池,复用已有连接生产环境、高并发应用(强烈推荐)
JNDI从容器(如 Tomcat、WebLogic)的 JNDI 树中获取 DataSource企业级 Java EE 应用、容器统一管理数据源

POOLED 核心参数

属性名默认值说明
poolMaximumActiveConnections10连接池中最大活跃连接数。同时正在使用的连接数不能超过此值
poolMaximumIdleConnections5连接池中最大空闲连接数。空闲连接超过此值时会被回收
poolMaximumCheckoutTime20000连接被取出后最大允许使用时间(毫秒)。超过此时间未归还,连接被强制回收
poolTimeToWait20000当连接池无可用连接时,线程等待的最大时间(毫秒)
poolMaximumLocalBadConnectionTolerance3容忍的坏连接次数。获取到无效连接时的重试次数
poolPingQueryNO PING QUERY SET检测连接是否有效的测试 SQL,如 SELECT 1
poolPingEnabledfalse是否开启连接健康检测
poolPingConnectionsNotUsedFor0连接空闲超过此时间(毫秒)才执行检测。0 表示每次获取都检测

核心原理

连接池获取/归还连接时序图

源码层面:PooledDataSource 内部维护两个 ArrayList:

  • activeConnections:当前被取出的活跃连接(PooledConnection 代理)
  • idleConnections:当前空闲的连接

当应用调用 connection.close() 时,PooledConnection 的代理逻辑会拦截该方法,将连接归还到 idleConnections 而非真正关闭物理连接。


完整示例

场景说明

乐途公司员工管理系统部署在生产环境,预计峰值并发约 50 个数据库操作线程。需要配置 POOLED 数据源,确保:

  • 最大活跃连接数能支撑峰值并发
  • 空闲连接不过度占用数据库资源
  • 连接异常时能自动检测和回收

操作前的状态

未使用连接池时(UNPOOLED),每次查询都经历完整的 TCP 连接建立过程:

获取连接耗时: 45ms
执行 SQL 耗时: 5ms
关闭连接耗时: 3ms

完整配置代码

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

  <properties resource="db.properties"/>
  <settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
  </settings>
  <typeAliases>
    <package name="com.feixiang.entity"/>
  </typeAliases>

  <environments default="production">
    <environment id="production">
      <transactionManager type="JDBC"/>
      
      <!-- POOLED 连接池配置(生产环境推荐) -->
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
        
        <!-- 连接池核心参数 -->
        <property name="poolMaximumActiveConnections" value="50"/>
        <property name="poolMaximumIdleConnections" value="20"/>
        <property name="poolMaximumCheckoutTime" value="30000"/>
        <property name="poolTimeToWait" value="20000"/>
        
        <!-- 连接健康检测 -->
        <property name="poolPingEnabled" value="true"/>
        <property name="poolPingQuery" value="SELECT 1"/>
        <property name="poolPingConnectionsNotUsedFor" value="600000"/>
      </dataSource>
    </environment>

    <environment id="development">
      <transactionManager type="JDBC"/>
      <!-- 开发环境使用 UNPOOLED,简化调试 -->
      <dataSource type="UNPOOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>

  <mappers>
    <mapper resource="mapper/EmployeeMapper.xml"/>
  </mappers>

</configuration>

Java 性能对比测试代码:

public class DataSourceDemo {
    public static void main(String[] args) throws Exception {
        // 测试 POOLED 模式
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory pooledFactory = new SqlSessionFactoryBuilder().build(is, "production");

        long start = System.currentTimeMillis();
        for (int i = 0; i < 100; i++) {
            try (SqlSession session = pooledFactory.openSession()) {
                EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
                mapper.selectById(1);
            } // 连接归还连接池
        }
        long pooledCost = System.currentTimeMillis() - start;
        System.out.println("POOLED 模式 100 次查询总耗时: " + pooledCost + "ms");

        // 测试 UNPOOLED 模式
        is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory unpooledFactory = new SqlSessionFactoryBuilder().build(is, "development");

        start = System.currentTimeMillis();
        for (int i = 0; i < 100; i++) {
            try (SqlSession session = unpooledFactory.openSession()) {
                EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
                mapper.selectById(1);
            } // 连接真正关闭
        }
        long unpooledCost = System.currentTimeMillis() - start;
        System.out.println("UNPOOLED 模式 100 次查询总耗时: " + unpooledCost + "ms");
    }
}

实际效果/结果

控制台输出(典型值):

POOLED 模式 100 次查询总耗时: 520ms
UNPOOLED 模式 100 次查询总耗时: 4850ms

效果验证:

  • POOLED 模式首次获取连接需要建立物理连接(约 40ms),后续 99 次直接从连接池取出(约 1-2ms)
  • UNPOOLED 模式每次都要新建和关闭连接,总耗时约为 POOLED 的 9 倍
  • 开启 poolPingEnabled 后,空闲超过 10 分钟的连接在取出时会执行 SELECT 1 验证有效性,避免使用已被数据库端关闭的"僵尸连接"

分析

  • poolMaximumActiveConnections 不应设置过大,否则数据库端连接数耗尽,影响其他应用。一般设置为 (峰值并发线程数 × 单线程最大同时连接需求) + 缓冲值
  • poolMaximumIdleConnections 建议设置为 poolMaximumActiveConnections 的 30%-50%,既能应对突发流量,又不会长期占用过多数据库连接
  • poolMaximumCheckoutTime 是防止连接泄漏的"保险丝":如果应用取出连接后忘记归还(如异常未正确关闭 SqlSession),超过 30 秒会被强制回收
  • poolPingConnectionsNotUsedFor 不建议设为 0(每次获取都检测),会增加一次网络往返。生产环境建议设为 5-10 分钟

易错场景/常见误区

误区错误表现正解
生产环境使用 UNPOOLED应用响应慢,数据库连接数波动剧烈,频繁出现 Too many connections生产环境必须使用 POOLED,这是最基本的数据库性能优化
poolMaximumActiveConnections 设置过大数据库端连接数被打满,导致其他应用或备份任务无法连接根据数据库最大连接数和实际并发评估,通常不超过 50-100
poolMaximumActiveConnections 设置过小高并发时线程频繁等待,超时抛出异常通过压测确定合理值,公式:峰值并发 × 1.2
未开启连接健康检测应用运行一段时间后出现 Communications link failure,查询失败生产环境开启 poolPingEnabled,配置轻量检测 SQL(如 SELECT 1)
认为 poolMaximumCheckoutTime 是连接最大存活时间连接被频繁强制关闭,性能下降poolMaximumCheckoutTime 是"单次取出最大使用时间",不是连接总寿命。连接总寿命由数据库的 wait_timeout 控制
JNDI 类型在独立应用中使用启动报 NameNotFoundExceptionJNDI 只能在 Java EE 容器(Tomcat、WebLogic 等)中使用,独立应用应使用 POOLED

面试考点

Q1:MyBatis 的 POOLED 数据源和 UNPOOLED 数据源有什么区别?

A:UNPOOLED 每次请求都新建物理连接,用完立即关闭,适合开发和测试,但生产环境性能极差。POOLED 维护一个连接池,复用已有连接,显著减少连接建立开销,是生产环境的标准选择。POOLED 内部通过 PooledConnection 动态代理拦截 close() 方法,实现连接的归还而非真正关闭。

Q2:POOLED 连接池的核心参数有哪些?如何调优?

A:核心参数包括:

  • poolMaximumActiveConnections:最大活跃连接数,根据峰值并发和数据库容量设置
  • poolMaximumIdleConnections:最大空闲连接数,通常为活跃数的 30%-50%
  • poolMaximumCheckoutTime:连接最大取出时间,防止连接泄漏
  • poolTimeToWait:无可用连接时的等待时间
  • poolPingEnabled + poolPingQuery:连接健康检测,避免使用失效连接 调优原则:通过压测确定活跃连接数,开启健康检测,设置合理的超时时间防止泄漏。

Q3:为什么 POOLED 模式下调用 connection.close() 不会真正关闭连接?

A:因为 MyBatis 返回给应用的是 PooledConnection 动态代理对象。当应用调用 close() 时,代理对象的 invoke() 方法会拦截该调用,将底层物理连接从 activeConnections 移回 idleConnections,而非调用真实连接的 close()。这是连接池复用连接的核心机制。

Q4:MyBatis 内置连接池与 HikariCP、Druid 等第三方连接池相比如何?

A:MyBatis 内置的 POOLED 是一个轻量级连接池,功能相对简单,适合不引入额外依赖的场景。但生产环境中,HikariCP(性能极致)、Druid(监控完善)等专用连接池在性能、监控、防护(SQL 注入检测、连接泄漏回收)方面更强大。Spring Boot 项目通常将 dataSource 类型设为 UNPOOLED 或 JNDI,由 Spring 注入 HikariCP 等数据源。


小结

dataSource 是 MyBatis 与数据库之间的"桥梁",其配置直接决定了应用的性能基线。UNPOOLED 仅用于开发和测试;POOLED 是生产环境的唯一选择,合理调优连接池参数能显著提升吞吐量并防止连接泄漏;JNDI 则服务于企业级容器环境。记住:连接池不是越大越好,匹配实际并发、开启健康检测、设置超时保护,才是调优的正道。


下一章引子

连接池解决了单数据库的性能问题,但现代企业应用常常需要适配多种数据库(如 Oracle 用于核心账务、MySQL 用于业务系统、PostgreSQL 用于数据分析)。MyBatis 的 databaseIdProvider 提供了多数据库适配机制,允许同一套 Mapper 根据当前数据库类型执行不同的 SQL 方言。接下来,我们将探索这一高级特性。

上一页
transactionManager
下一页
databaseIdProvider