MyBatis数据源模块详解

从源码层面深入理解MyBatis连接池的设计与实现

一、MyBatis整体架构与数据源模块

在深入数据源模块之前,我们先了解MyBatis的整体架构,以及数据源模块在其中的重要地位。

从架构图可以看出,MyBatis采用了分层架构设计,而数据源模块(DataSource)位于基础支撑层,是数据库访问的基础设施。它负责管理数据库连接、维护连接池、提供高效的连接获取和释放机制,是整个框架与数据库交互的入口。

1.1 数据源模块的核心职责

数据源模块主要承担以下核心职责:

复制代码
管理数据库连接 - 创建、初始化和管理数据库连接
连接池管理 - 维护连接池,提高连接复用率
连接获取与释放 - 提供高效的连接获取和释放机制
连接配置管理 - 管理数据库连接的配置信息
性能优化 - 通过连接池减少连接创建开销

1.2 为什么需要连接池?

在传统的JDBC编程中,每次数据库操作都需要创建和关闭连接:

java 复制代码
//传统方式:每次创建新连接
Connection conn = DriverManager.getConnection(url, user, password);
try {
    // 执行SQL操作
} finally {
    conn.close(); // 关闭连接
}

这种方式存在明显问题:

复制代码
频繁创建连接 - 连接创建是昂贵的操作 
资源浪费 - 频繁创建和销毁连接消耗资源
性能低下 - 每次操作都要等待连接建立

使用连接池后的流程:

markdown 复制代码
应用启动 → 创建连接池 → 预创建N个连接
    ↓
业务请求 → 从池中获取连接 → 执行操作 → 归还连接到池
    ↓
应用关闭 → 销毁连接池 → 释放所有连接

1.3 MyBatis的数据源类型

数据源类型 说明 使用场景
UNPOOLED 不使用连接池 测试环境、简单应用
POOLED 使用连接池 生产环境、高并发应用
JNDI 使用JNDI查找 应用服务器环境

二、数据源接口架构

MyBatis的数据源模块采用了清晰的接口设计。

2.1 DataSource接口

DataSource是数据源的顶层接口:

java 复制代码
public interface DataSource {
    // 获取数据库连接
    Connection getConnection() throws SQLException;
    Connection getConnection(String username, String password) 
        throws SQLException;
}

虽然MyBatis没有直接定义DataSource接口(使用的是javax.sql.DataSource),但它通过工厂模式创建不同类型的DataSource实现。

2.2 DataSourceFactory接口

MyBatis定义了DataSourceFactory接口用于创建DataSource:

csharp 复制代码
public interface DataSourceFactory {
    // 设置数据源属性
    void setProperties(Properties props);
    // 获取数据源
    DataSource getDataSource();
}

2.3数据源工厂实现

MyBatis提供了三种DataSourceFactory实现:

java 复制代码
DataSourceFactory
├── UnpooledDataSourceFactory (无池数据源工厂)
├── PooledDataSourceFactory (池化数据源工厂)
└── JndiDataSourceFactory (JNDI数据源工厂)
public class UnpooledDataSourceFactory implements DataSourceFactory {
    private static final String DRIVER_PROPERTY_PREFIX = "driver.";
    private static final int DRIVER_PROPERTY_PREFIX_LENGTH = 
        DRIVER_PROPERTY_PREFIX.length();

    protected DataSource dataSource;

    public UnpooledDataSourceFactory() {
        this.dataSource = new UnpooledDataSource();
    }

    @Override
    public void setProperties(Properties properties) {
        Properties driverProperties = new Properties();
        DataSource dataSource = getDataSource();

        // 创建数据源
        MetaObject metaDataSource = 
            SystemMetaObject.forObject(dataSource);

        // 设置属性
        for (Object key : properties.keySet()) {
            String propertyName = (String) key;
            if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
                driverProperties.setProperty(
                    propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), 
                    properties.getProperty(propertyName)
                );
            } else if (metaDataSource.hasSetter(propertyName)) {
                String value = (String) properties.get(propertyName);
                Object convertedValue = 
                    convertValue(metaDataSource, propertyName, value);
                metaDataSource.setValue(propertyName, convertedValue);
            }
        }

        if (dataSource instanceof UnpooledDataSource) {
            ((UnpooledDataSource) dataSource)
                .setDriverProperties(driverProperties);
        }
    }

    @Override
    public DataSource getDataSource() {
        return dataSource;
    }
}
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
    public PooledDataSourceFactory() {
        //注意:这里创建的是PooledDataSource
        this.dataSource = new PooledDataSource();
    }
}

UnpooledDataSource是不使用连接池的数据源实现,每次请求都创建新连接。

3.1 UnpooledDataSource核心代码

java 复制代码
public class UnpooledDataSource implements DataSource {
    // 驱动配置
    private Class<?> driver;
    private String url;
    private String username;
    private String password;
    private Boolean autoCommit;
    private Integer defaultTransactionIsolationLevel;
    private Properties driverProperties;

    // 缓存驱动,避免重复加载
    private static Map<String, Driver> registeredDrivers = 
        new ConcurrentHashMap<>();

    @Override
    public Connection getConnection() throws SQLException {
        return doGetConnection(username, password);
    }

    @Override
    public Connection getConnection(String username, String password) 
        throws SQLException {
        return doGetConnection(username, password);
    }

    private Connection doGetConnection(String username, String password) 
        throws SQLException {
        Properties props = new Properties();
        if (driverProperties != null) {
            props.putAll(driverProperties);
        }
        if (username != null) {
            props.setProperty("user", username);
        }
        if (password != null) {
            props.setProperty("password", password);
        }
        return doGetConnection(props);
    }

    private Connection doGetConnection(Properties properties) 
        throws SQLException {
        // 初始化驱动
        initializeDriver();
        // 获取连接
        Connection connection = 
            DriverManager.getConnection(url, properties);
        // 配置连接
        configureConnection(connection);
        return connection;
    }

    private synchronized void initializeDriver() throws SQLException {
        if (!registeredDrivers.containsKey(driver)) {
            try {
                Class<?> driverType = Class.forName(driver);
                Driver driverInstance = (Driver) driverType.newInstance();
                DriverManager.registerDriver(
                    new DriverProxy(driverInstance)
                );
                registeredDrivers.put(driver, driverInstance);
            } catch (Exception e) {
                throw new SQLException(
                    "Error setting driver on UnpooledDataSource. Cause: " + e
                );
            }
        }
    }

    private void configureConnection(Connection conn) throws SQLException {
        if (autoCommit != null && conn.getAutoCommit() != autoCommit) {
            conn.setAutoCommit(autoCommit);
        }
        if (defaultTransactionIsolationLevel != null) {
            conn.setTransactionIsolation(defaultTransactionIsolationLevel);
        }
    }
}

3.2 UnpooledDataSource特点

优点:

复制代码
实现简单,易于理解
不需要维护连接池状态
适合测试和简单应用

缺点:

复制代码
每次都创建新连接,性能低
频繁创建销毁连接,资源消耗大
不适合高并发场景

四、PooledDataSource实现

PooledDataSource是MyBatis的连接池实现,提供了高效的连接管理。

4.1 PooledDataSource核心结构

arduino 复制代码
public class PooledDataSource implements DataSource {
    // 数据源状态
    private final PoolState state;
    // 无池数据源(用于创建实际连接)
    private final UnpooledDataSource dataSource;
    // 连接池配置
    private int expectedTransactionType;
    // 最大活动连接数
    private int poolMaximumActiveConnections = 10;
    // 最大空闲连接数
    private int poolMaximumIdleConnections = 5;
    // 最大Checkout时间(毫秒)
    private int poolMaximumCheckoutTime = 20000;
    // 等待时间(毫秒)
    private int poolTimeToWait = 20000;
    // 最大连接数
    private int poolMaximumLocalBadConnectionTolerance = 3;
    // 测试连接的SQL
    private String poolPingQuery = "NO PING QUERY SET";
    // 是否测试连接
    private boolean poolPingEnabled;
    // 测试连接的间隔(毫秒)
    private int poolPingConnectionsNotUsedFor;

    public PooledDataSource() {
        state = new PoolState(this);
        dataSource = new UnpooledDataSource();
    }

    public PooledDataSource(String driver, String url, 
        String username, String password) {
        state = new PoolState(this);
        dataSource = new UnpooledDataSource(driver, url, username, password);
    }

    @Override
    public Connection getConnection() throws SQLException {
        return popConnection(
            dataSource.getUsername(), 
            dataSource.getPassword()
        ).getProxyConnection();
    }

    @Override
    public Connection getConnection(String username, String password) 
        throws SQLException {
        return popConnection(username, password).getProxyConnection();
    }
}

4.2 PoolState连接池状态

arduino 复制代码
public class PoolState {
    //数据源
    protected PooledDataSource dataSource;

    //空闲连接列表
    protected final List<PooledConnection> idleConnections = 
        new ArrayList<>();
    //活动连接列表
    protected final List<PooledConnection> activeConnections = 
        new ArrayList<>();

    // 统计指标
    protected long requestCount = 0;              // 请求计数
    protected long accumulatedRequestTime = 0;    // 累计请求时间
    protected long accumulatedCheckoutTime = 0;   // 累计Checkout时间
    protected long accumulatedWaitTime = 0;       // 累计等待时间
    protected long badConnectionCount = 0;        // 累计无效连接数

    public PoolState(PooledDataSource dataSource) {
        this.dataSource = dataSource;
    }
}

4.3 popConnection获取连接

ini 复制代码
private PooledConnection popConnection(String username, String password) 
    throws SQLException {
    boolean countedWait = false;
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    int localBadConnectionCount = 0;

    while (conn == null) {
        synchronized (state) {
            //检查是否有空闲连接
            if (!state.idleConnections.isEmpty()) {
                // 从空闲连接池获取
                conn = state.idleConnections.remove(0);
                if (log.isDebugEnabled()) {
                    log.debug("Checked out connection " + 
                        conn.getRealHashCode() + " from pool.");
                }
            } else {
                //没有空闲连接
                if (state.activeConnections.size() < 
                    poolMaximumActiveConnections) {
                    // 可以创建新连接
                    conn = new PooledConnection(
                        dataSource.getConnection(), this
                    );
                    if (log.isDebugEnabled()) {
                        log.debug("Created connection " + 
                            conn.getRealHashCode() + ".");
                    }
                } else {
                    //活动连接已满,需要等待
                    countedWait = true;
                    try {
                        state.wait(poolTimeToWait);
                    } catch (InterruptedException e) {
                        break;
                    }
                }
            }

            if (conn != null) {
                //检查连接是否有效
                if (conn.isValid()) {
                    // 测试连接
                    if (!poolPingEnabled || 
                        conn.pingConnection(poolPingConnectionsNotUsedFor)) {
                        // 连接有效,加入活动连接池
                        state.activeConnections.add(conn);
                        state.requestCount++;
                        state.accumulatedRequestTime += 
                            System.currentTimeMillis() - t;
                        return conn;
                    }
                }
                // 连接无效,标记为坏连接
                state.badConnectionCount++;
                localBadConnectionCount++;
                if (conn.getRealConnection() != null) {
                    conn.close();
                }
                conn = null;
            }
        }

        //检查是否超时
        if (!countedWait) {
            countedWait = true;
        } else if (localBadConnectionCount > 
            poolMaximumLocalBadConnectionTolerance) {
            throw new SQLException(
                "PooledDataSource: Could not get a good connection."
            );
        }
    }

    if (conn == null) {
        throw new SQLException(
            "PooledDataSource: Unknown error. Could not get a connection."
        );
    }

    return conn;
}

4.4 pushConnection归还连接

scss 复制代码
protected void pushConnection(PooledConnection conn) throws SQLException {
    synchronized (state) {
        //从活动连接池移除
        state.activeConnections.remove(conn);

        //检查连接是否有效
        if (conn.isValid()) {
            //检查空闲连接池是否已满
            if (state.idleConnections.size() < poolMaximumIdleConnections && 
                conn.getConnectionType() == expectedTransactionType) {
                // 归还到空闲连接池
                state.accumulatedCheckoutTime += conn.getCheckoutTime();
                if (!conn.getRealConnection().getAutoCommit()) {
                    conn.getRealConnection().rollback();
                }
                PooledConnection newConn = new PooledConnection(
                    conn.getRealConnection(), this
                );
                state.idleConnections.add(newConn);
                state.requestCount++;
                conn.invalidate();
                if (log.isDebugEnabled()) {
                    log.debug("Returned connection " + 
                        newConn.getRealHashCode() + " to pool.");
                }
                state.notifyAll();
            } else {
                //空闲连接池已满,关闭连接
                state.accumulatedCheckoutTime += conn.getCheckoutTime();
                if (!conn.getRealConnection().getAutoCommit()) {
                    conn.getRealConnection().rollback();
                }
                conn.getRealConnection().close();
                if (log.isDebugEnabled()) {
                    log.debug("Closed connection " + 
                        conn.getRealHashCode() + ".");
                }
                conn.invalidate();
            }
        } else {
            //连接无效
            if (log.isDebugEnabled()) {
                log.debug("A bad connection (" + 
                    conn.getRealHashCode() + ") was returned to the pool.");
            }
            state.badConnectionCount++;
        }
    }
}

4.5 PooledConnection代理连接

PooledConnection使用动态代理模式,拦截close()方法,将连接归还到池而不是真正关闭。

java 复制代码
class PooledConnection implements InvocationHandler {
    // 实际连接
    private final Connection realConnection;
    // 代理连接
    private final Connection proxyConnection;
    // 数据源
    private final PooledDataSource dataSource;
    // Checkout时间
    private long checkoutTimestamp;
    // 最后使用时间
    private long lastUsedTimestamp;
    // 连接类型
    private int connectionType;
    // 是否有效
    private boolean valid;

    public PooledConnection(Connection realConnection, 
        PooledDataSource dataSource) {
        this.realConnection = realConnection;
        this.dataSource = dataSource;
        this.valid = true;

        //创建动态代理
        this.proxyConnection = (Connection) Proxy.newProxyInstance(
            Connection.class.getClassLoader(),
            new Class[]{Connection.class},
            this
        );

        this.checkoutTimestamp = System.currentTimeMillis();
        this.lastUsedTimestamp = checkoutTimestamp;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
        throws Throwable {
        String methodName = method.getName();

        // close方法特殊处理
        if (CLOSE.equals(methodName)) {
            // 不真正关闭,而是归还到连接池
            dataSource.pushConnection(this);
            return null;
        }

        try {
            // 调用实际连接的方法
            if (!Object.class.equals(method.getDeclaringClass())) {
                checkConnection(); // 检查连接状态
            }
            return method.invoke(realConnection, args);
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
    }

    public Connection getProxyConnection() {
        return proxyConnection;
    }

    public Connection getRealConnection() {
        return realConnection;
    }

    private void checkConnection() throws SQLException {
        if (!valid) {
            throw new SQLException(
                "Error accessing PooledConnection. Connection is closed."
            );
        }
    }

    public boolean isValid() {
        return valid && realConnection != null;
    }

    public void invalidate() {
        valid = false;
    }

    public long getCheckoutTime() {
        return System.currentTimeMillis() - checkoutTimestamp;
    }

    // 连接有效性测试
    public boolean pingConnection(int pingInterval) {
        if (pingInterval > 0 && 
            System.currentTimeMillis() - lastUsedTimestamp > pingInterval) {
            try {
                if (realConnection.isClosed()) {
                    return false;
                }
                Statement stmt = realConnection.createStatement();
                if (poolPingQuery != null && !poolPingQuery.isEmpty()) {
                    stmt.execute(poolPingQuery);
                }
                stmt.close();
                return true;
            } catch (SQLException e) {
                return false;
            }
        }
        return true;
    }
}

五、数据源配置流程

MyBatis支持多种方式配置数据源。

5.1 XML配置方式

xml 复制代码
<?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>
    <!-- 配置环境 -->
    <environments default="development">
        <environment id="development">
            <!-- 事务管理器 -->
            <transactionManager type="JDBC"/>

            <!-- 数据源配置 -->
            <dataSource type="POOLED">
                <!-- JDBC驱动 -->
                <property name="driver" 
                    value="com.mysql.cj.jdbc.Driver"/>
                <!-- 数据库URL -->
                <property name="url" 
                    value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;serverTimezone=UTC"/>
                <!-- 用户名 -->
                <property name="username" value="root"/>
                <!-- 密码 -->
                <property name="password" value="123456"/>

                <!-- 连接池配置 -->
                <!-- 默认事务隔离级别 -->
                <property name="defaultTransactionIsolationLevel" value="2"/>
                <!-- 最大活动连接数 -->
                <property name="poolMaximumActiveConnections" value="20"/>
                <!-- 最大空闲连接数 -->
                <property name="poolMaximumIdleConnections" value="10"/>
                <!-- 最大Checkout时间(毫秒) -->
                <property name="poolMaximumCheckoutTime" value="20000"/>
                <!-- 等待时间(毫秒) -->
                <property name="poolTimeToWait" value="20000"/>
                <!-- 最大本地坏连接容忍度 -->
                <property name="poolMaximumLocalBadConnectionTolerance" 
                    value="3"/>

                <!-- 连接测试配置 -->
                <!-- 是否启用Ping查询 -->
                <property name="poolPingEnabled" value="true"/>
                <!-- Ping查询SQL -->
                <property name="poolPingQuery" value="SELECT 1"/>
                <!-- 未使用超过此时间(毫秒)才进行Ping测试 -->
                <property name="poolPingConnectionsNotUsedFor" value="60000"/>
            </dataSource>
        </environment>

        <!-- 生产环境配置 -->
        <environment id="production">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" 
                    value="jdbc:mysql://prod-server:3306/mybatis"/>
                <property name="username" value="prod_user"/>
                <property name="password" value="prod_password"/>

                <!-- 生产环境连接池配置 -->
                <property name="poolMaximumActiveConnections" value="50"/>
                <property name="poolMaximumIdleConnections" value="20"/>
                <property name="poolPingEnabled" value="true"/>
                <property name="poolPingQuery" value="SELECT 1"/>
                <property name="poolPingConnectionsNotUsedFor" value="30000"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

5.2 属性文件配置方式

创建jdbc.properties文件:

bash 复制代码
JDBC配置jdbc.driver=com.mysql.cj.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=UTCjdbc.username=rootjdbc.password=123456# 连接池配置jdbc.poolMaximumActiveConnections=20jdbc.poolMaximumIdleConnections=10jdbc.poolMaximumCheckoutTime=20000jdbc.poolTimeToWait=20000

在MyBatis配置中引用:

ini 复制代码
<configuration>
    <!-- 引入属性文件 -->
    <properties resource="jdbc.properties"/>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
                <property name="poolMaximumActiveConnections" 
                    value="${jdbc.poolMaximumActiveConnections}"/>
                <property name="poolMaximumIdleConnections" 
                    value="${jdbc.poolMaximumIdleConnections}"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

5.3 JNDI配置方式

ini 复制代码
<dataSource type="JNDI">
    <property name="data_source" value="java:comp/env/jdbc/MyDS"/>
</dataSource>

5.4 编程式配置

ini 复制代码
// 创建数据源
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
dataSource.setUsername("root");
dataSource.setPassword("123456");
// 配置连接池
dataSource.setPoolMaximumActiveConnections(20);
dataSource.setPoolMaximumIdleConnections(10);
dataSource.setPoolPingEnabled(true);
dataSource.setPoolPingQuery("SELECT 1");
// 创建SqlSessionFactory
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment(
    "development", transactionFactory, dataSource
);
Configuration configuration = new Configuration(environment);
configuration.addMapper(UserMapper.class);
SqlSessionFactory sqlSessionFactory = 
    new SqlSessionFactoryBuilder().build(configuration);

六、连接获取与释放流程

理解连接的获取和释放流程对于使用连接池至关重要。

6.1 连接获取流程

markdown 复制代码
1. 应用请求连接
   ↓
2. 调用dataSource.getConnection()
   ↓
3. 检查空闲连接池
   ├─ 有空闲连接 → 取出连接
   │                ↓
   │             测试连接有效性
   │                ├─ 有效 → 返回连接
   │                └─ 无效 → 创建新连接
   │
   └─ 无空闲连接
       ↓
      检查活动连接数
       ├─ 未达上限 → 创建新连接 → 返回
       └─ 已达上限 → 等待空闲连接

6.2 连接释放流程

markdown 复制代码
. 应用调用connection.close()
   ↓
2. PooledConnection拦截close调用
   ↓
3. 不真正关闭,而是执行归还逻辑
   ↓
4. 从活动连接池移除
   ↓
5. 检查空闲连接池
   ├─ 未满 → 归还到空闲连接池
   └─ 已满 → 真正关闭物理连接

6.3 连接使用示例

scss 复制代码
// 获取SqlSession
SqlSession session = sqlSessionFactory.openSession();
try {
    // 获取Mapper
    UserMapper mapper = session.getMapper(UserMapper.class);

    // 执行查询(内部自动获取连接)
    User user = mapper.selectById(1L);

    // 执行更新
    user.setName("张三");
    mapper.update(user);

    // 提交事务
    session.commit();
} catch (Exception e) {
    // 回滚事务
    session.rollback();
    throw e;
} finally {
    //关闭Session(归还连接到池)
    session.close();
}

七、连接池类型对比

MyBatis支持三种数据源类型,各有适用场景。

7.1 UNPOOLED(无池)

特点:

复制代码
每次请求创建新连接
使用完毕立即关闭
不维护连接池

配置:

ini 复制代码
<dataSource type="UNPOOLED">
    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</dataSource>

适用场景:

复制代码
测试环境 
简单应用  
不需要高并发的场景

7.2 POOLED(池化)

特点:

复制代码
维护连接池
连接复用
自动管理连接生命周期

配置:

xml 复制代码
<dataSource type="POOLED">
    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    <!-- 连接池配置 -->
    <property name="poolMaximumActiveConnections" value="20"/>
    <property name="poolMaximumIdleConnections" value="10"/>
</dataSource>
复制代码
适用场景:
生产环境
高并发应用
需要优化性能的场景

7.3 JNDI

特点:

复制代码
使用应用服务器管理的DataSource
通过JNDI查找获取
支持分布式事务

配置:

ini 复制代码
<dataSource type="JNDI">
    <property name="data_source" value="java:comp/env/jdbc/MyDS"/>
</dataSource>

适用场景:

复制代码
应用服务器环境(WebLogic、WebSphere等) 
需要分布式事务支持
集中管理数据源7.4 连接池配置参数说明
参数 默认值 说明
poolMaximumActiveConnections 10 最大活动连接数
poolMaximumIdleConnections 5 最大空闲连接数
poolMaximumCheckoutTime 20000 最大Checkout时间(毫秒)
poolTimeToWait 20000 等待连接时间(毫秒)
poolPingEnabled false 是否启用Ping测试
poolPingQuery "NO PING QUERY SET" Ping测试SQL
poolPingConnectionsNotUsedFor 0 未使用多久才Ping(毫秒)
poolMaximumLocalBadConnectionTolerance 3 最大本地坏连接容忍度

八、最佳实践

8.1 常见问题解决

xml 复制代码
// 错误示例:忘记关闭连接
SqlSession session = sqlSessionFactory.openSession();
User user = session.selectOne(
    "com.example.mapper.UserMapper.selectById", 1L
);
//没有关闭session,连接泄漏!

//正确示例:及时关闭
SqlSession session = sqlSessionFactory.openSession();
try {
    User user = session.selectOne(
        "com.example.mapper.UserMapper.selectById", 1L
    );
} finally {
    session.close(); // 确保连接归还到池
}
<!-- 解决方案1:增加连接池大小 -->
<property name="poolMaximumActiveConnections" value="50"/>

<!-- 解决方案2:减少Checkout时间 -->
<property name="poolMaximumCheckoutTime" value="10000"/>
<!-- 解决方案3:增加等待时间 -->
<property name="poolTimeToWait" value="30000"/>

8.2 连接池监控

scss 复制代码
// 获取连接池状态
PooledDataSource dataSource = (PooledDataSource) sqlSessionFactory
    .getConfiguration()
    .getEnvironment()
    .getDataSource();

// 获取PoolState
PoolState poolState = dataSource.getPoolState();

// 监控指标
System.out.println("连接池状态:");
System.out.println("空闲连接数:" + 
    poolState.getIdleConnections().size());
System.out.println("活动连接数:" + 
    poolState.getActiveConnections().size());
System.out.println("请求总数:" + 
    poolState.getRequestCount());
System.out.println("坏连接数:" + 
    poolState.getBadConnectionCount());

九、总结

MyBatis的数据源模块提供了灵活而强大的数据库连接管理能力。

arduino 复制代码
DataSourceFactory - 创建数据源的工厂接口
UnpooledDataSource - 不使用连接池的数据源
PooledDataSource - 使用连接池的数据源
PooledConnection - 代理连接,拦截close方法
PoolState - 维护连接池
相关推荐
林shir2 小时前
3.6-Web后端基础(java操作数据库)
spring·mybatis
super_lzb16 小时前
mybatis拦截器ParameterHandler详解
java·数据库·spring boot·spring·mybatis
CodeAmaz1 天前
MyBatis 分页插件实现原理(Interceptor 机制 + SQL 改写)
mybatis·分页插件
CodeAmaz1 天前
MyBatis 如何实现“面向接口”查询
mybatis·面向接口
此剑之势丶愈斩愈烈1 天前
mybatis-plus乐观锁
开发语言·python·mybatis
heartbeat..1 天前
Java 持久层框架 MyBatis 全面详解(附带Idea添加对应的XML文件模板教程)
java·数据库·intellij-idea·mybatis·持久化
Predestination王瀞潞1 天前
Java EE数据访问框架技术(第三章:Mybatis多表关系映射-下)
java·java-ee·mybatis
刘一说2 天前
2026年Java技术栈全景图:从Web容器到云原生的深度选型指南(附避坑指南)
java·前端·spring boot·后端·云原生·tomcat·mybatis
独自归家的兔2 天前
Spring Retryable 注解完全指南:从入门到精通,让接口容错更简单
java·spring·mybatis