手写Mybatis:第6章-数据源池化技术实现

文章目录

  • 一、目标:数据源池化技术实现
  • 二、设计:数据源池化技术实现
  • 三、实现:数据源池化技术实现
    • 3.1 工程结构
    • 3.2 数据源池化技术关系图
    • 3.3 无池化链接实现
    • 3.4 有池化链接实现
      • 3.4.1 有连接的数据源
      • 3.4.2 池化链接的代理
      • 3.4.3 池状态定义
      • 3.4.4 pushConnection 回收链接
      • 3.4.5 popConnection 获取链接
    • 3.5 数据源工厂
      • 3.5.1 无池化工厂
      • 3.5.2 有池化工厂
    • 3.6 新增类型别名注册器
  • 四、测试:数据源池化技术实现
    • 4.1 配置数据源
      • 4.1.1 无池化:UNPOOLED
      • 4.1.2 有池化:POOLED
    • 4.2 单元测试
      • 4.2.1 基础测试
      • 4.2.2 无池化测试结果:UNPOOLED
      • 4.2.3 有池化测试结果:POOLED
    • 4.3 连接池验证
  • 五、总结:数据源池化技术实现

一、目标:数据源池化技术实现

Mybatis 中自带的数据源实现?

  • 无池化 UnpooledDataSource 实现。
  • 有池化 pooledDataSource 实现,池化配置属性的理解:最大活跃连接数、空闲连接数、检测时长等。

二、设计:数据源池化技术实现

池化技术理解为亨元模式的具体实现方案:对一些需要较高创建成本且高频使用的资源,需要进行缓存或者也称预热处理。

  • 池化技术:把一些资源存放到一个预热池子中,需要用的时候从池子中获取,使用完毕在进行使用。
  • 通过池化可以非常有效的控制资源的使用成本,包括:资源数量、空闲时长、获取方式等进行统一控制和管理

手写Mybatis:第6章-数据源池化技术实现_第1张图片

  • 通过提供统一的数据池中心,存放数据源连接,并根据配置按照请求获取连接的操作,创建连接池的数据源连接数量。
    • 包括:最大空闲连接和最大活跃连接,都随着创建过程被控制。
  • 此外由于控制了连接池中连接的数量,所以当外部从连接池获取连接时,如果连接已满则会进行循环等待。
    • 案例:使用DB连接池,如果一个 SQL 操作引起了慢查询,则会导致整个服务进入瘫痪的阶段,各个和数据库相关的接口调用,都不能获得到连接,接口查询 TP99 徒然提高。
  • 那连接池可以配置的很大吗?
    • 不可以. 因为连接池要和数据所分配的连接池对应上,避免应用配置连接池超过数据库所提供的连接池数量,否则会出现 夯住不能分配链接 的问题,导致数据库拖垮从而引起连锁反应.

三、实现:数据源池化技术实现

3.1 工程结构

mybatis-step-05
|-src
	|-main
	|	|-java
	|		|-com.lino.mybatis
    |			|-binding
    |			|	|-MapperMethod.java
	|			|	|-MapperProxy.java
	|			|	|-MapperProxyFactory.java
    |			|	|-MapperRegistry.java
    |			|-builder
    |			|	|-xml
    |			|	|	|-XMLConfigBuilder.java
    |			|	|-BaseBuilder.java
	|			|-datasource
	|			|	|-druid
	|			|	|	|-DruidDataSourceFacroty.java
	|			|	|-pooled
	|			|	|	|-PooledConnection.java
	|			|	|	|-PooledDataSource.java
	|			|	|	|-PooledDataSourceFacroty.java
	|			|	|	|-PoolState.java
	|			|	|-unpooled
	|			|	|	|-UnpooledDataSource.java
	|			|	|	|-UnpooledDataSourceFacroty.java
	|			|	|-DataSourceFactory.java
    |			|-io
    |			|	|-Resources.java
    |			|-mapping
    |			|	|-BoundSql.java
    |			|	|-Environment.java
    |			|	|-MappedStatement.java
    |			|	|-ParameterMapping.java
    |			|	|-SqlCommandType.java
    |			|-session
    |			|	|-defaults
    |			|	|	|-DefaultSqlSession.java
    |			|	|	|-DefaultSqlSessionFactory.java
    |			|	|-Configuration.java
    |			|	|-SqlSession.java
    |			|	|-SqlSessionFactory.java
    |			|	|-SqlSessionFactoryBuilder.java
    |			|	|-TransactionIsolationLevel.java
    |			|-transaction
    |			|	|-jdbc
    |			|	|	|-JdbcTransaction.java
    |			|	|	|-JdbcTransactionFactory.java
    |			|	|-Transaction.java
    |			|	|-TransactionFactory.java
    |			|-type
    |			|	|-JdbcType.java
    |			|	|-TypeAliasRegistry.java
	|-test
		|-java
		|	|-com.lino.mybatis.test
		|	|-dao
		|	|	|-IUserDao.java
		|	|-po
		|	|	|-User.java
		|	|-ApiTest.java
        |-resources
        	|-mapper
        	|	|-User_Mapper.xml
        	|-mybatis-config-datasource.xml

3.2 数据源池化技术关系图

手写Mybatis:第6章-数据源池化技术实现_第2张图片

  • Mybatis 数据源的实现中,包括两部分:
    • 无池化的 UnpooledDataSource 实现类。
    • 有池化的 PooledDataSource 实现类,对无池化的 UnpooledDataSource 进行扩展处理。把创建出来的链接保存到内存中,记录为空闲链接和活跃链接,在不同的阶段使用。
  • PooledConnection 是对链接的代理操作,通过 invoke 方法的反射调用,对关闭的链接进行回收处理,并使用 notifyAll 通知正在等待链接的用户进行抢链接。
  • DataSourceFactory 数据源工厂接口的实现,由无池化工厂实现后,有池化工厂继承的方式进行处理。

3.3 无池化链接实现

UnpooledDataSource.java

package com.lino.mybatis.datasource.unpooled;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.*;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

/**
 * @description: 无池化数据源实现
 */
public class UnpooledDataSource implements DataSource {
    /**
     * 类加载器
     */
    private ClassLoader driverClassLoader;
    /**
     * 驱动配置,也可以扩展属性信息:driver.encoding=UTF8
     */
    private Properties driverProperties;
    /**
     * 驱动注册器
     */
    private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();
    /**
     * 驱动
     */
    private String driver;
    /**
     * DB连接地址
     */
    private String url;
    /**
     * 账号
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 是否自动提交
     */
    private Boolean autoCommit;
    /**
     * 事务隔离级别
     */
    private Integer defaultTransactionIsolationLevel;

    static {
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver driver = drivers.nextElement();
            registeredDrivers.put(driver.getClass().getName(), driver);
        }
    }

    /**
     * 驱动代理
     */
    private static class DriverProxy implements Driver {

        private Driver driver;

        DriverProxy(Driver driver) {
            this.driver = driver;
        }

        @Override
        public Connection connect(String url, Properties info) throws SQLException {
            return this.driver.connect(url, info);
        }

        @Override
        public boolean acceptsURL(String url) throws SQLException {
            return this.driver.acceptsURL(url);
        }

        @Override
        public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
            return this.driver.getPropertyInfo(url, info);
        }

        @Override
        public int getMajorVersion() {
            return this.driver.getMajorVersion();
        }

        @Override
        public int getMinorVersion() {
            return this.driver.getMinorVersion();
        }

        @Override
        public boolean jdbcCompliant() {
            return this.driver.jdbcCompliant();
        }

        @Override
        public Logger getParentLogger() throws SQLFeatureNotSupportedException {
            return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
        }
    }

    /**
     * 初始化驱动
     * 资料:https://www.kfu.com/~nsayer/Java/dyn-jdbc.html
     *
     * @throws SQLException SQL异常
     */
    private synchronized void initializerDriver() throws SQLException {
        if (!registeredDrivers.containsKey(driver)) {
            try {
                Class<?> driverType = Class.forName(driver, true, driverClassLoader);
                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 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 {
        initializerDriver();
        Connection connection = DriverManager.getConnection(url, properties);
        if (autoCommit != null && autoCommit != connection.getAutoCommit()) {
            connection.setAutoCommit(autoCommit);
        }
        if (defaultTransactionIsolationLevel != null) {
            connection.setTransactionIsolation(defaultTransactionIsolationLevel);
        }
        return connection;
    }

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

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

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        throw new SQLException(getClass().getName() + "is not a wrapper.");
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return DriverManager.getLogWriter();
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
        DriverManager.setLogWriter(out);
    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
        DriverManager.setLoginTimeout(seconds);
    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return DriverManager.getLoginTimeout();
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
    }

    public ClassLoader getDriverClassLoader() {
        return driverClassLoader;
    }

    public void setDriverClassLoader(ClassLoader driverClassLoader) {
        this.driverClassLoader = driverClassLoader;
    }

    public Properties getDriverProperties() {
        return driverProperties;
    }

    public void setDriverProperties(Properties driverProperties) {
        this.driverProperties = driverProperties;
    }

    public static Map<String, Driver> getRegisteredDrivers() {
        return registeredDrivers;
    }

    public static void setRegisteredDrivers(Map<String, Driver> registeredDrivers) {
        UnpooledDataSource.registeredDrivers = registeredDrivers;
    }

    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Boolean getAutoCommit() {
        return autoCommit;
    }

    public void setAutoCommit(Boolean autoCommit) {
        this.autoCommit = autoCommit;
    }

    public Integer getDefaultTransactionIsolationLevel() {
        return defaultTransactionIsolationLevel;
    }

    public void setDefaultTransactionIsolationLevel(Integer defaultTransactionIsolationLevel) {
        this.defaultTransactionIsolationLevel = defaultTransactionIsolationLevel;
    }
}
  • 无池化的数据源连接实现:核心在于 initializerDriver 初始化驱动中使用 Class.forNamenewInstance 的方式创建了数据源连接操作。
  • 在创建完成链接之后,把链接存放到驱动注册器中,方便后续使用中可以直接获取链接,避免重复创建所带来的资源消耗。

3.4 有池化链接实现

有池化的数据源链接,核心在于对无池化链接的包装,同时提供了相应的池化技术实现。

  • 包括:pushConnection、popConnection、forceCloseAll、pingConnection 的操作处理。
  • 当用户想要获取链接时,则会从连接池中获取链接,同时判断是否有空闲链接、最大活跃链接多少,以及是否需要等待处理或是最终抛出异常。

3.4.1 有连接的数据源

PooledDataSource.java

package com.lino.mybatis.datasource.pooled;

import com.lino.mybatis.datasource.unpooled.UnpooledDataSource;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.sql.*;
import java.util.logging.Logger;

/**
 * @description: 有连接池的数据源
 */
public class PooledDataSource implements DataSource {

    private org.slf4j.Logger logger = LoggerFactory.getLogger(PooledDataSource.class);
    /**
     * 池状态
     */
    private final PoolState state = new PoolState(this);
    /**
     * 无池化数据源
     */
    private final UnpooledDataSource dataSource;
    /**
     * 活跃连接数
     */
    protected int poolMaximumActiveConnections = 10;
    /**
     * 空闲连接数
     */
    protected int poolMaximumIdleConnections = 5;
    /**
     * 在被强制返回之前,池中连接被检查的时间
     */
    protected int poolMaximumCheckoutTime = 20000;
    /**
     * 这是给连接池一个打印日志状态机会的低层次设置,还有重新尝试获得连接,这些情况下往往需要很长时间,为了避免连接池没有配置时静默失败
     */
    protected int poolTimeToWait = 20000;
    /**
     * 发送到数据的侦测查询,用来验证连接是否正常工作,并且准备接受请求。
     * 默认是 “NO PING QUERY SET”,这回引起许多数据库驱动连接由一个错误信息而导致失败
     */
    protected String poolPingQuery = "NO PING QUERY SET";
    /**
     * 开启或禁用侦测查询
     */
    protected boolean poolPingEnabled = false;
    /**
     * 用来配置 poolPingQuery 多长时间被用一次
     */
    protected int poolPingConnectionsNotUsedFor = 0;

    private int expectedConnectionTypeCode;

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

    protected void pushConnection(PooledConnection connection) throws SQLException {
        synchronized (state) {
            state.activeConnections.remove(connection);
            // 判断连接是否有效
            if (connection.isValid()) {
                // 如果空闲连接小于设定数量,也就是太少时
                if (state.idleConnections.size() < poolMaximumIdleConnections && connection.getConnectionTypeCode() == expectedConnectionTypeCode) {
                    state.accumulatedCheckoutTime += connection.getCheckOutTime();
                    if (!connection.getRealConnection().getAutoCommit()) {
                        connection.getRealConnection().rollback();
                    }
                    // 实例化一个新的DB连接,加入到idle列表
                    PooledConnection newConnection = new PooledConnection(connection.getRealConnection(), this);
                    state.idleConnections.add(newConnection);
                    newConnection.setCreatedTimestamp(connection.getCreatedTimestamp());
                    newConnection.setLastUsedTimestamp(connection.getLastUsedTimestamp());
                    connection.invalidate();
                    logger.info("Returned connection " + newConnection.getRealHashCode() + " to pool.");

                    // 通知其他线程可以来抢DB连接了
                    state.notifyAll();
                }
                // 否则,空闲连接还比较充足
                else {
                    state.accumulatedCheckoutTime += connection.getCheckOutTime();
                    if (!connection.getRealConnection().getAutoCommit()) {
                        connection.getRealConnection().rollback();
                    }
                    // 将connection关闭
                    connection.getRealConnection().close();
                    logger.info("Closed connection " + connection.getRealHashCode() + ".");
                    connection.invalidate();
                }
            } else {
                logger.info("A bad connection (" + connection.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
                state.badConnectionCount++;
            }
        }
    }

    private PooledConnection popConnection(String username, String password) throws SQLException {
        boolean countteWait = 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);
                    logger.info("Check out connention " + conn.getRealHashCode() + " form pool.");
                }
                // 如果无空闲连接:创建新的连接
                else {
                    // 活跃连接数不足
                    if (state.activeConnections.size() < poolMaximumActiveConnections) {
                        conn = new PooledConnection(dataSource.getConnection(), this);
                        logger.info("Created connention " + conn.getRealHashCode() + ".");
                    }
                    // 活跃连接数已满
                    else {
                        // 取得活跃连接列表的第一个,也就是最老的一个连接
                        PooledConnection oldestActiveConnection = state.activeConnections.get(0);
                        long longestCheckoutTime = oldestActiveConnection.getCheckOutTime();
                        // 如果checkout时间过长,则这个连接标记为过期
                        if (longestCheckoutTime > poolMaximumCheckoutTime) {
                            state.claimedOverdueConnectionCount++;
                            state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                            state.accumulatedCheckoutTime += longestCheckoutTime;
                            state.activeConnections.remove(oldestActiveConnection);
                            if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                                oldestActiveConnection.getRealConnection().rollback();
                            }
                            // 删掉最老的连接,然后重新实例化一个新的连接
                            conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                            oldestActiveConnection.invalidate();
                            logger.info("Claimed overdue connention " + conn.getRealHashCode() + ".");
                        }
                        // 如果checkout超时时间不够长,则等待
                        else {
                            try {
                                if (!countteWait) {
                                    state.hadToWaitCount++;
                                    countteWait = true;
                                }
                                logger.info("Waiting as long as " + poolTimeToWait + " millisecond for connection.");
                                long wt = System.currentTimeMillis();
                                state.wait(poolTimeToWait);
                                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
                            } catch (InterruptedException e) {
                                break;
                            }
                        }
                    }
                }
                // 获得到连接
                if (conn != null) {
                    if (conn.isValid()) {
                        if (!conn.getRealConnection().getAutoCommit()) {
                            conn.getRealConnection().rollback();
                        }
                        conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
                        // 记录checkout时间
                        conn.setCheckOutTimestamp(System.currentTimeMillis());
                        conn.setLastUsedTimestamp(System.currentTimeMillis());
                        state.activeConnections.add(conn);
                        state.requestCount++;
                        state.accumulatedCheckoutTime += System.currentTimeMillis() - t;
                    } else {
                        logger.info("A bad connection (" + conn.getRealHashCode() + ") was returned form the pool, getting another connection.");
                        // 如果没拿到,统计信息:失败连接 +1
                        state.badConnectionCount++;
                        localBadConnectionCount++;
                        conn = null;
                        // 失败次数较多:抛异常
                        if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
                            logger.debug("PooledDataSource: Could not get a good connection to the database.");
                            throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
                        }
                    }
                }
            }
        }

        if (conn == null) {
            logger.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
            throw new SQLException("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
        }

        return conn;
    }

    public void forceCloseAll() {
        synchronized (state) {
            expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
            // 关闭活跃连接
            for (int i = state.activeConnections.size(); i > 0; i--) {
                try {
                    PooledConnection conn = state.activeConnections.remove(i - 1);
                    conn.invalidate();

                    Connection realConn = conn.getRealConnection();
                    if (!realConn.getAutoCommit()) {
                        realConn.rollback();
                    }
                    realConn.close();
                } catch (Exception ignore) {

                }
            }
            // 关闭空闲连接
            for (int i = state.idleConnections.size(); i > 0; i--) {
                try {
                    PooledConnection conn = state.idleConnections.remove(i - 1);
                    conn.invalidate();

                    Connection realConn = conn.getRealConnection();
                    if (!realConn.getAutoCommit()) {
                        realConn.rollback();
                    }
                    realConn.close();
                } catch (Exception ignore) {

                }
            }
            logger.info("PooledDataSource forcefully closed/removed all connections.");
        }
    }

    protected boolean pingConnection(PooledConnection conn) {
        boolean result = true;

        try {
            result = !conn.getRealConnection().isClosed();
        } catch (SQLException e) {
            logger.info("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
            result = false;
        }

        if (result) {
            if (poolPingEnabled) {
                if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
                    try {
                        logger.info("Testing connection " + conn.getRealHashCode() + " ...");
                        Connection realConn = conn.getRealConnection();
                        Statement statement = realConn.createStatement();
                        ResultSet resultSet = statement.executeQuery(poolPingQuery);
                        resultSet.close();
                        if (!realConn.getAutoCommit()) {
                            realConn.rollback();
                        }
                        result = true;
                        logger.info("Connection " + conn.getRealHashCode() + " is GOOD!");
                    } catch (Exception e) {
                        logger.info("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
                        try {
                            conn.getRealConnection().close();
                        } catch (SQLException ignore) {

                        }
                        result = false;
                        logger.info("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
                    }

                }
            }
        }

        return result;
    }

    public static Connection unwrapConnection(Connection conn) {
        if (Proxy.isProxyClass(conn.getClass())) {
            InvocationHandler handler = Proxy.getInvocationHandler(conn);
            if (handler instanceof javax.sql.PooledConnection) {
                return ((PooledConnection) handler).getRealConnection();
            }
        }
        return conn;
    }

    private int assembleConnectionTypeCode(String url, String username, String password) {
        return ("" + url + username + password).hashCode();
    }

    @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();
    }

    protected void finalize() throws Throwable {
        forceCloseAll();
        super.finalize();
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        throw new SQLException(getClass().getName() + " is not a wrapper.");
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return DriverManager.getLogWriter();
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
        DriverManager.setLogWriter(out);
    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
        DriverManager.setLoginTimeout(seconds);
    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return DriverManager.getLoginTimeout();
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
    }

    public void setDriver(String driver) {
        dataSource.setDriver(driver);
        forceCloseAll();
    }

    public void setUrl(String url) {
        dataSource.setUrl(url);
        forceCloseAll();
    }

    public void setUsername(String username) {
        dataSource.setUsername(username);
        forceCloseAll();
    }

    public void setPassword(String password) {
        dataSource.setPassword(password);
        forceCloseAll();
    }

    public void setDefaultAutoCommit(boolean defaultAutoCommit) {
        dataSource.setAutoCommit(defaultAutoCommit);
        forceCloseAll();
    }

    public int getPoolMaximumActiveConnections() {
        return poolMaximumActiveConnections;
    }

    public void setPoolMaximumActiveConnections(int poolMaximumActiveConnections) {
        this.poolMaximumActiveConnections = poolMaximumActiveConnections;
    }

    public int getPoolMaximumIdleConnections() {
        return poolMaximumIdleConnections;
    }

    public void setPoolMaximumIdleConnections(int poolMaximumIdleConnections) {
        this.poolMaximumIdleConnections = poolMaximumIdleConnections;
    }

    public int getPoolMaximumCheckoutTime() {
        return poolMaximumCheckoutTime;
    }

    public void setPoolMaximumCheckoutTime(int poolMaximumCheckoutTime) {
        this.poolMaximumCheckoutTime = poolMaximumCheckoutTime;
    }

    public int getPoolTimeToWait() {
        return poolTimeToWait;
    }

    public void setPoolTimeToWait(int poolTimeToWait) {
        this.poolTimeToWait = poolTimeToWait;
    }

    public String getPoolPingQuery() {
        return poolPingQuery;
    }

    public void setPoolPingQuery(String poolPingQuery) {
        this.poolPingQuery = poolPingQuery;
    }

    public boolean isPoolPingEnabled() {
        return poolPingEnabled;
    }

    public void setPoolPingEnabled(boolean poolPingEnabled) {
        this.poolPingEnabled = poolPingEnabled;
    }

    public int getPoolPingConnectionsNotUsedFor() {
        return poolPingConnectionsNotUsedFor;
    }

    public void setPoolPingConnectionsNotUsedFor(int poolPingConnectionsNotUsedFor) {
        this.poolPingConnectionsNotUsedFor = poolPingConnectionsNotUsedFor;
    }

    public int getExpectedConnectionTypeCode() {
        return expectedConnectionTypeCode;
    }

    public void setExpectedConnectionTypeCode(int expectedConnectionTypeCode) {
        this.expectedConnectionTypeCode = expectedConnectionTypeCode;
    }
}

3.4.2 池化链接的代理

PooledConnection.java

package com.lino.mybatis.datasource.pooled;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Objects;

/**
 * @description: 池化代理的链接
 */
public class PooledConnection implements InvocationHandler {

    private static final String CLOSE = "close";
    private static final Class<?>[] IFACES = new Class<?>[]{Connection.class};

    private int hashCode = 0;
    private PooledDataSource dataSource;

    /**
     * 真实的连接
     */
    private Connection realConnection;
    /**
     * 代理的连接
     */
    private Connection proxyConnection;

    private long checkOutTimestamp;
    private long createdTimestamp;
    private long lastUsedTimestamp;
    private int connectionTypeCode;
    private boolean valid;

    public PooledConnection(Connection connection, PooledDataSource dataSource) {
        this.hashCode = connection.hashCode();
        this.realConnection = connection;
        this.dataSource = dataSource;
        this.createdTimestamp = System.currentTimeMillis();
        this.lastUsedTimestamp = System.currentTimeMillis();
        this.valid = true;
        this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        // 如果是调用 CLOSE 关闭连接方法,则将连接加入连接池中,并返回null
        if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
            dataSource.pushConnection(this);
            return null;
        } else {
            if (!Object.class.equals(method.getDeclaringClass())) {
                // 除了toString()方法,其他方法调用之前要检查connection是否还是合法的,不合法要抛出SQL异常
                checkConnection();
            }
            // 其他方法交给connection去调用
            return method.invoke(realConnection, args);
        }
    }

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

    public void invalidate() {
        valid = false;
    }

    public boolean isValid() {
        return valid && realConnection != null && dataSource.pingConnection(this);
    }

    public Connection getRealConnection() {
        return realConnection;
    }

    public Connection getProxyConnection() {
        return proxyConnection;
    }

    public int getRealHashCode() {
        return realConnection == null ? 0 : realConnection.hashCode();
    }

    public int getConnectionTypeCode() {
        return connectionTypeCode;
    }

    public void setConnectionTypeCode(int connectionTypeCode) {
        this.connectionTypeCode = connectionTypeCode;
    }

    public long getCreatedTimestamp() {
        return createdTimestamp;
    }

    public void setCreatedTimestamp(long createdTimestamp) {
        this.createdTimestamp = createdTimestamp;
    }

    public long getLastUsedTimestamp() {
        return lastUsedTimestamp;
    }

    public void setLastUsedTimestamp(long lastUsedTimestamp) {
        this.lastUsedTimestamp = lastUsedTimestamp;
    }

    public long getTimeElapsedSinceLastUse() {
        return System.currentTimeMillis() - lastUsedTimestamp;
    }

    public long getAge() {
        return System.currentTimeMillis() - createdTimestamp;
    }

    public long getCheckOutTimestamp() {
        return checkOutTimestamp;
    }

    public void setCheckOutTimestamp(long checkOutTimestamp) {
        this.checkOutTimestamp = checkOutTimestamp;
    }

    public long getCheckOutTime() {
        return System.currentTimeMillis() - checkOutTimestamp;
    }

    @Override
    public int hashCode() {
        return hashCode;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof PooledConnection) {
            return realConnection.hashCode() == ((PooledConnection) obj).realConnection.hashCode();
        } else if (obj instanceof Connection) {
            return hashCode == obj.hashCode();
        } else {
            return false;
        }
    }
}
  • 当我们需要对链接进行池化处理,当链接调用一些 CLOSE 方法时,也需要把链接从池中关闭和恢复可用,允许其他用户获取到链接。
  • 那么这里就需要对连接类进行代理包装,处理 CLOSE 方法。
  • 通过 PooledConnection 实现 InvocationHandle#invoke 方法,包装代理链接,这样就可以对具体的调用方法进行控制。
  • invoke 方法中处理对 CLOSE 方法控制以外,排除 toStringObject 的方法后,则是其他真正需要被DB链接处理的方法。
  • 对于 CLOSE 方法的数据源回收操作 dataSource.pushConnection(this);有一个具体的实现方法,在池化实现类 PooledDataSource 中进行处理。

3.4.3 池状态定义

PoolState.java

package com.lino.mybatis.datasource.pooled;

import java.util.ArrayList;
import java.util.List;

/**
 * @description: 池状态
 */
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;
    protected long claimedOverdueConnectionCount = 0;
    protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
    /**
     * 总等待时间
     */
    protected long accumulatedWaitTime = 0;
    /**
     * 要等待的次数
     */
    protected long hadToWaitCount = 0;
    /**
     * 失败连接次数
     */
    protected long badConnectionCount = 0;

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

    public synchronized long getRequestCount() {
        return requestCount;
    }

    public synchronized long getAverageRequestTime() {
        return requestCount == 0 ? 0 : accumulatedRequestTime / requestCount;
    }

    public synchronized long getAverageWaitTime() {
        return hadToWaitCount == 0 ? 0 : accumulatedWaitTime / hadToWaitCount;
    }

    public synchronized long getHadToWaitCount() {
        return hadToWaitCount;
    }

    public synchronized long getBadConnectionCount() {
        return badConnectionCount;
    }

    public synchronized long getClaimedOverdueConnectionCount() {
        return claimedOverdueConnectionCount;
    }

    public synchronized long getAverageOverdueCheckoutTime() {
        return claimedOverdueConnectionCount == 0 ? 0 : accumulatedCheckoutTimeOfOverdueConnections / claimedOverdueConnectionCount;
    }

    public synchronized long getAverageCheckoutTime() {
        return requestCount == 0 ? 0 : accumulatedCheckoutTime / requestCount;
    }

    public synchronized int getIdleConnectionCount() {
        return idleConnections.size();
    }

    public synchronized int getActiveConnectionCount() {
        return activeConnections.size();
    }
}
  • 定义连接池状态。包括:空闲连接、活跃连接、请求次数、总请求时间、总等待时间、要等待的数次、失败连接次数等。
  • 给连接池状态添加 synchronized 锁,避免并发出现的问题。

3.4.4 pushConnection 回收链接

protected void pushConnection(PooledConnection connection) throws SQLException {
    synchronized (state) {
        state.activeConnections.remove(connection);
        // 判断连接是否有效
        if (connection.isValid()) {
            // 如果空闲连接小于设定数量,也就是太少时
            if (state.idleConnections.size() < poolMaximumIdleConnections && connection.getConnectionTypeCode() == expectedConnectionTypeCode) {
                state.accumulatedCheckoutTime += connection.getCheckOutTime();
                if (!connection.getRealConnection().getAutoCommit()) {
                    connection.getRealConnection().rollback();
                }
                // 实例化一个新的DB连接,加入到idle列表
                PooledConnection newConnection = new PooledConnection(connection.getRealConnection(), this);
                state.idleConnections.add(newConnection);
                newConnection.setCreatedTimestamp(connection.getCreatedTimestamp());
                newConnection.setLastUsedTimestamp(connection.getLastUsedTimestamp());
                connection.invalidate();
                logger.info("Returned connection " + newConnection.getRealHashCode() + " to pool.");

                // 通知其他线程可以来抢DB连接了
                state.notifyAll();
            }
            // 否则,空闲连接还比较充足
            else {
                state.accumulatedCheckoutTime += connection.getCheckOutTime();
                if (!connection.getRealConnection().getAutoCommit()) {
                    connection.getRealConnection().rollback();
                }
                // 将connection关闭
                connection.getRealConnection().close();
                logger.info("Closed connection " + connection.getRealHashCode() + ".");
                connection.invalidate();
            }
        } else {
            logger.info("A bad connection (" + connection.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
            state.badConnectionCount++;
        }
    }
}
  • pooleadDataSource#pushConnection 数据源回收的处理中,核心在于 判断链接是否有效,以及进行相关的 空闲链接校验,判断是否把链接回到到 idle 空闲链接列表中,并通知其他线程来抢占。
  • 如果现在有空闲链接充足,那么这个回收的链接则会进行回滚和关闭的处理。
    • 回滚connection.getRealConnection().rollbak()
    • 关闭connection.getRealConnection().close()

3.4.5 popConnection 获取链接

private PooledConnection popConnection(String username, String password) throws SQLException {
    boolean countteWait = 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);
                logger.info("Check out connention " + conn.getRealHashCode() + " form pool.");
            }
            // 如果无空闲连接:创建新的连接
            else {
                // 活跃连接数不足
                if (state.activeConnections.size() < poolMaximumActiveConnections) {
                    conn = new PooledConnection(dataSource.getConnection(), this);
                    logger.info("Created connention " + conn.getRealHashCode() + ".");
                }
                // 活跃连接数已满
                else {
                    // 取得活跃连接列表的第一个,也就是最老的一个连接
                    PooledConnection oldestActiveConnection = state.activeConnections.get(0);
                    long longestCheckoutTime = oldestActiveConnection.getCheckOutTime();
                    // 如果checkout时间过长,则这个连接标记为过期
                    if (longestCheckoutTime > poolMaximumCheckoutTime) {
                        state.claimedOverdueConnectionCount++;
                        state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
                        state.accumulatedCheckoutTime += longestCheckoutTime;
                        state.activeConnections.remove(oldestActiveConnection);
                        if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                            oldestActiveConnection.getRealConnection().rollback();
                        }
                        // 删掉最老的连接,然后重新实例化一个新的连接
                        conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
                        oldestActiveConnection.invalidate();
                        logger.info("Claimed overdue connention " + conn.getRealHashCode() + ".");
                    }
                    // 如果checkout超时时间不够长,则等待
                    else {
                        try {
                            if (!countteWait) {
                                state.hadToWaitCount++;
                                countteWait = true;
                            }
                            logger.info("Waiting as long as " + poolTimeToWait + " millisecond for connection.");
                            long wt = System.currentTimeMillis();
                            state.wait(poolTimeToWait);
                            state.accumulatedWaitTime += System.currentTimeMillis() - wt;
                        } catch (InterruptedException e) {
                            break;
                        }
                    }
                }
            }
            // 获得到连接
            if (conn != null) {
                if (conn.isValid()) {
                    if (!conn.getRealConnection().getAutoCommit()) {
                        conn.getRealConnection().rollback();
                    }
                    conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
                    // 记录checkout时间
                    conn.setCheckOutTimestamp(System.currentTimeMillis());
                    conn.setLastUsedTimestamp(System.currentTimeMillis());
                    state.activeConnections.add(conn);
                    state.requestCount++;
                    state.accumulatedCheckoutTime += System.currentTimeMillis() - t;
                } else {
                    logger.info("A bad connection (" + conn.getRealHashCode() + ") was returned form the pool, getting another connection.");
                    // 如果没拿到,统计信息:失败连接 +1
                    state.badConnectionCount++;
                    localBadConnectionCount++;
                    conn = null;
                    // 失败次数较多:抛异常
                    if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
                        logger.debug("PooledDataSource: Could not get a good connection to the database.");
                        throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
                    }
                }
            }
        }
    }

    if (conn == null) {
        logger.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
        throw new SQLException("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
    }

    return conn;
}
  • popConnection 获取链接是一个 while 死循环操作,只有获取到链接抛异常才会退出循环。
  • 获取链接的过程会使用 synchronized 进行加锁,因为所有线程在资源竞争的情况下,都需要进行加锁处理。
  • 在加锁的代码块中通过判断是否还有空闲链接进行返回,如果没有则会判断活跃连接数是否充足,不充足则进行创建后返回。
  • 在这里也会遇到活跃链接已经进行循环等待的过程,最后再不能获取则抛出异常。

3.5 数据源工厂

数据源工厂包括两部分:分别是无池化和有池化,有池化的工厂继承无池化工厂。
mybatis 源码的实现类中,这样可以减少对 Properties 统一包装的反射方式的属性处理。

3.5.1 无池化工厂

UnpooledDataSourceFactory.java

package com.lino.mybatis.datasource.unpooled;

import com.lino.mybatis.datasource.DataSourceFactory;
import javax.sql.DataSource;
import java.util.Properties;

/**
 * @description: 无池化数据源工厂
 */
public class UnpooledDataSourceFactory implements DataSourceFactory {

    protected Properties props;

    @Override
    public void setProperties(Properties props) {
        this.props = props;
    }

    @Override
    public DataSource getDataSource() {
        UnpooledDataSource unpooledDataSource = new UnpooledDataSource();
        unpooledDataSource.setDriver(props.getProperty("driver"));
        unpooledDataSource.setUrl(props.getProperty("url"));
        unpooledDataSource.setUsername(props.getProperty("username"));
        unpooledDataSource.setPassword(props.getProperty("password"));
        return unpooledDataSource;
    }
}
  • 简单包装 getDataSource 获取数据源处理,把必要的参数进行传递。

3.5.2 有池化工厂

PooledDataSourceFactory.java

package com.lino.mybatis.datasource.pooled;

import com.lino.mybatis.datasource.unpooled.UnpooledDataSourceFactory;
import javax.sql.DataSource;

/**
 * @description: 有连接池的数据源工厂
 */
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {

    @Override
    public DataSource getDataSource() {
        PooledDataSource pooledDataSource = new PooledDataSource();
        pooledDataSource.setDriver(props.getProperty("driver"));
        pooledDataSource.setUrl(props.getProperty("url"));
        pooledDataSource.setUsername(props.getProperty("username"));
        pooledDataSource.setPassword(props.getProperty("password"));
        return pooledDataSource;
    }
}
  • 有池化的数据源工厂实现的也比较简单,只是继承 UnpooledDataSourceFactory 共用获取属性的能力,以及实例化出池化数据源。

3.6 新增类型别名注册器

Configuration.java

public Configuration() {
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);

    typeAliasRegistry.registerAlias("DRUID", DruidDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
}
  • 将两个数据源和对应的工厂实现类配置到 Configuration 配置类中,这样在解析 XML 时根据不同的数据源类型获取和实例化对应的实现类。
  • 在构造方法 Configuration 添加 UNPOOLED、POOLED 两个数据源注册到类型注册器中,方便后续使用 XMLConfigBuilder#envirenmentElement 方法解析 XML 处理数据源时进行使用。

四、测试:数据源池化技术实现

4.1 配置数据源

4.1.1 无池化:UNPOOLED

mybatis-config-datasource.xml

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="UNPOOLED">
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        dataSource>
    environment>
environments>

4.1.2 有池化:POOLED

mybatis-config-datasource.xml

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        dataSource>
    environment>
environments>

4.2 单元测试

4.2.1 基础测试

ApiTest.java

@Test
public void test_SqlSessionFactory() throws IOException {
    // 1.从SqlSessionFactory中获取SqlSession
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 2.获取映射器对象
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);

    // 3.测试验证
    for (int i = 0; i < 50; i++) {
        User user = userDao.queryUserInfoById(1L);
        logger.info("测试结果:{}", JSON.toJSONString(user));
    }
}
  • 在无池化和有池化的测试中,基础的测试单元不需要改变,仍是通过 SqlSessionFactory 中获取 SqlSession 并获得映射对象和执行方法调用。
  • 另外添加了50次的查询调用,便于验证连接池的创建和获取以及等待。
  • 变化的在于 mybatis-config-datasource.xmldataSource 数据源类型的调整 dataSource type="UNPOOLED/POOLED"

4.2.2 无池化测试结果:UNPOOLED

10:42:29.188 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:42:29.205 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:42:29.236 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:42:29.252 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:42:29.268 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:42:29.299 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:42:29.315 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
...
  • 无池化的连接池操作,会不断的与数据库建立新的链接并执行 SQL 操作,这个过程中只要数据库还有链接可以被链接,就可以创建链接。

4.2.3 有池化测试结果:POOLED

10:46:58.765 [main] INFO  c.l.m.d.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
10:46:58.765 [main] INFO  c.l.m.d.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
10:46:58.765 [main] INFO  c.l.m.d.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
10:46:58.765 [main] INFO  c.l.m.d.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
10:46:59.444 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 1436664465.
10:46:59.507 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:46:59.533 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 1205406622.
10:46:59.533 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:46:59.543 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 796667727.
10:46:59.554 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:46:59.570 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 1541857308.
10:46:59.570 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:46:59.586 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 2095303566.
10:46:59.586 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:46:59.602 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 581318631.
10:46:59.602 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:46:59.633 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 1989184704.
10:46:59.633 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:46:59.649 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 199640888.
10:46:59.649 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:46:59.665 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 1243806178.
10:46:59.665 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:46:59.690 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 1007880005.
10:46:59.690 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
10:46:59.690 [main] INFO  c.l.m.d.pooled.PooledDataSource - Waiting as long as 20000 millisecond for connection.
10:47:19.690 [main] INFO  c.l.m.d.pooled.PooledDataSource - Claimed overdue connention 1436664465.
10:47:19.690 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小灵哥"}
...
  • 通过使用连接池的配置可以看到,在调用和获取链接的过程中,当调用次数达到10此以后,连接池中就有了10个活跃链接,再调用时则需要等待连接释放后才能使用并执行 SQL 操作。

4.3 连接池验证

test_pooled:连接池验证

@Test
public void test_pooled() throws IOException, SQLException, InterruptedException {
    PooledDataSource pooledDataSource = new PooledDataSource();
    pooledDataSource.setDriver("com.mysql.jdbc.Driver");
    pooledDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true");
    pooledDataSource.setUsername("root");
    pooledDataSource.setPassword("123456");
    // 持续获取连接
    while (true) {
        Connection connection = pooledDataSource.getConnection();
        System.out.println(connection);
        Thread.sleep(1000);
        connection.close();
    }
}

测试结果

10:52:54.704 [main] INFO  c.l.m.d.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
10:52:54.704 [main] INFO  c.l.m.d.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
10:52:54.704 [main] INFO  c.l.m.d.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
10:52:54.704 [main] INFO  c.l.m.d.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
10:52:55.386 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 103536485.
com.mysql.jdbc.JDBC4Connection@62bd765
10:52:56.401 [main] INFO  c.l.m.d.pooled.PooledDataSource - Returned connection 103536485 to pool.
10:52:56.401 [main] INFO  c.l.m.d.pooled.PooledDataSource - Check out connention 103536485 form pool.
com.mysql.jdbc.JDBC4Connection@62bd765
10:52:57.404 [main] INFO  c.l.m.d.pooled.PooledDataSource - Returned connection 103536485 to pool.
10:52:57.404 [main] INFO  c.l.m.d.pooled.PooledDataSource - Check out connention 103536485 form pool.
com.mysql.jdbc.JDBC4Connection@62bd765
10:52:58.412 [main] INFO  c.l.m.d.pooled.PooledDataSource - Returned connection 103536485 to pool.
10:52:58.412 [main] INFO  c.l.m.d.pooled.PooledDataSource - Check out connention 103536485 form pool.
com.mysql.jdbc.JDBC4Connection@62bd765
10:52:59.416 [main] INFO  c.l.m.d.pooled.PooledDataSource - Returned connection 103536485 to pool.
10:52:59.416 [main] INFO  c.l.m.d.pooled.PooledDataSource - Check out connention 103536485 form pool.
  • 从连接的 hashCode 的值 @62bd765,可以看出数据库链接已经被缓存了,只要有空闲链接,就会调用数据库中同一个链接,节约资源。

!https://img-blog.csdnimg.cn/0332e7801f6b410daf16a5d4d5393a4e.jpeg

五、总结:数据源池化技术实现

  • 完成了 Mybatis 数据源池化的设计和实现,连接池的实现重点包括:synchronized 加锁、创建链接、活跃数量控制、休眠等待时长、抛异常逻辑等。

你可能感兴趣的:(手写mybatis,mybatis,java)