关于java:Mybatis-手撸专栏第6章数据源池化技术实现

3次阅读

共计 17805 个字符,预计需要花费 45 分钟才能阅读完成。

作者:小傅哥
博客:https://bugstack.cn – 手写 Mybatis 系列文章

积淀、分享、成长,让本人和别人都能有所播种!😄

一、前言

码农,只会做不会说?

你有发现吗,其实很大一部分码农,都只是会写代码,不会讲货色。一遇到述职、问难、分享、汇报,就很难晦涩且有高度、有深度,并交融一部分引入入胜的趣味性来让观众更好的承受和了解你要传递的信息。

那为什么曾经做了还讲不进去呢?因为做只是在已确定指标和既定路线下的执行,但为什么确定这个指标、为什么制订这个路线、横向的参照比照、纵向的深度设计,都没有在一个执行者的头脑中造成过程的推演,更多的时候都只是执行。所以也就很难把一个事残缺的表述进去。

所以,只有当你经验的足够多,经历的足够丰盛,能力有更好的表述能力和临场应变技巧,也就更能更分明的传递出你要表白的信息。

二、指标

在上一章节咱们解析了 XML 中数据源配置信息,并应用 Druid 创立数据源实现数据库的操作。但其实在 Mybatis 中是有本人的数据源实现的,包含无池化的 UnpooledDataSource 实现形式和有池化的 PooledDataSource 实现形式。

那么本章节咱们就来实现一下对于池化数据源的解决,通过这些实现读者也能更好的了解在咱们日常开发中一些对于数据源的配置属性到底意欲何为,包含:最大沉闷连接数、闲暇连接数、检测时长等,在连接池中所起到的作用。

三、设计

首先你能够把池化技术了解为享元模式的具体实现计划,通常咱们对一些须要较高创立老本且高频应用的资源,须要进行缓存或者也称预热解决。并把这些资源寄存到一个预热池子中,须要用的时候从池子中获取,应用结束再进行应用。通过池化能够十分无效的管制资源的应用老本,包含;资源数量、闲暇时长、获取形式等进行对立管制和治理。如图 6-1 所示

  • 通过提供对立的连接池核心,存放数据源链接,并依据配置依照申请获取链接的操作,创立连接池的数据源链接数量。这里就包含了最大闲暇链接和最大沉闷链接,都随着创立过程被管制。
  • 此外因为管制了连接池中连贯的数量,所以当内部从连接池获取链接时,如果链接已满则会进行循环期待。这也是大家日常应用 DB 连接池,如果一个 SQL 操作引起了慢查问,则会导致整个服务进入瘫痪的阶段,各个和数据库相干的接口调用,都不能取得到链接,接口查问 TP99 陡然增高,零碎开始大量报警。那连接池能够配置的很大吗,也不能够,因为连接池要和数据库所调配的连接池对应上,防止利用配置连接池超过数据库所提供的连接池数量,否则会呈现夯住不能调配链接的问题,导致数据库拖垮从而引起连锁反应。

四、实现

1. 工程构造

mybatis-step-04
└── src
    ├── main
    │   └── java
    │       └── cn.bugstack.mybatis
    │           ├── binding
    │           ├── builder
    │           ├── datasource
    │           │   ├── druid
    │           │   │   └── DruidDataSourceFactory.java
    │           │   ├── pooled
    │           │   │   ├── PooledConnection.java
    │           │   │   ├── PooledDataSource.java
    │           │   │   ├── PooledDataSourceFactory.java
    │           │   │   └── PoolState.java
    │           │   ├── unpooled
    │           │   │   ├── UnpooledDataSource.java
    │           │   │   └── UnpooledDataSourceFactory.java
    │           │   └── DataSourceFactory.java
    │           ├── io
    │           ├── mapping
    │           ├── session
    │           │   ├── defaults
    │           │   │   ├── DefaultSqlSession.java
    │           │   │   └── DefaultSqlSessionFactory.java
    │           │   ├── Configuration.java
    │           │   ├── SqlSession.java
    │           │   ├── SqlSessionFactory.java
    │           │   ├── SqlSessionFactoryBuilder.java
    │           │   └── TransactionIsolationLevel.java  
    │           ├── transaction
    │           └── type
    └── test
        ├── java
        │   └── cn.bugstack.mybatis.test.dao
        │       ├── dao
        │       │   └── IUserDao.java
        │       ├── po
        │       │   └── User.java
        │       └── ApiTest.java
        └── resources
            ├── mapper
            │   └──User_Mapper.xml
            └── mybatis-config-datasource.xml

工程源码:https://t.zsxq.com/bmqNFQ7

池化数据源外围类关系,如图 6-2 所示

  • 在 Mybatis 数据源的实现中,包含两局部分为无池化的 UnpooledDataSource 实现类和有池化的 PooledDataSource 实现类,池化的实现类 PooledDataSource 以对无池化的 UnpooledDataSource 进行扩大解决。把创立进去的链接保留到内存中,记录为闲暇链接和沉闷链接,在不同的阶段进行应用。
  • PooledConnection 是对链接的代理操作,通过 invoke 办法的反射调用,对敞开的链接进行回收解决,并应用 notifyAll 告诉正在期待链接的用户进行抢链接。
  • 另外是对 DataSourceFactory 数据源工厂接口的实现,由无池化工厂实现后,有池化工厂继承的形式进行解决,这里没有太多的简单操作,池化的解决次要集中在 PooledDataSource 类中进行解决。

2. 无池化链接实现

对于数据库连接池的实现,不肯定非得提供池化技术,对于某些场景能够只应用无池化的连接池。那么在实现的过程中,能够把无池化的实现和池化实现拆合成耦,在须要的时候只须要配置对应的数据源即可。

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 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;
    }

    /**
     * 初始化驱动
     */
    private synchronized void initializerDriver() throws SQLException {if (!registeredDrivers.containsKey(driver)) {
            try {Class<?> driverType = Class.forName(driver, true, driverClassLoader);
                // https://www.kfu.com/~nsayer/Java/dyn-jdbc.html
                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);
            }
        }
    }
    
}
  • 无池化的数据源链接实现比较简单,外围在于 initializerDriver 初始化驱动中应用了 Class.forName 和 newInstance 的形式创立了数据源链接操作。
  • 在创立实现连贯当前,把链接寄存到驱动注册器中,不便后续应用中能够间接获取链接,防止反复创立所带来的资源耗费。

3. 有池化链接实现

有池化的数据源链接,外围在于对无池化链接的包装,同时提供了相应的池化技术实现,包含:pushConnection、popConnection、forceCloseAll、pingConnection 的操作解决。

这样当用户想要获取链接的时候,则会从连接池中进行获取,同时判断是否有闲暇链接、最大沉闷链接多少,以及是否须要期待解决或是最终抛出异样。

3.1 池化连贯的代理

因为咱们须要对连贯进行池化解决,所以当链接调用一些 CLOSE 办法的时候,也须要把链接从池中敞开和复原可用,容许其余用户获取到链接。那么这里就须要对连贯类进行代理包装,解决 CLOSE 办法。

源码详见cn.bugstack.mybatis.datasource.pooled.PooledConnection

public class PooledConnection implements InvocationHandler {

    @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 是否还是非法的, 不非法要抛出 SQLException
                checkConnection();}
            // 其余办法交给 connection 去调用
            return method.invoke(realConnection, args);
        }
    }
    
}
  • 通过 PooledConnection 实现 InvocationHandler#invoke 办法,包装代理链接,这样就能够对具体的调用办法进行管制了。
  • 在 invoke 办法中解决对 CLOSE 办法管制以外,排除 toString 等 Object 的办法后,则是其余真正须要被 DB 链接解决的办法了。
  • 那么这里有一个对于 CLOSE 办法的数据源回收操作 dataSource.pushConnection(this); 有一个具体的实现办法,在池化实现类 PooledDataSource 中进行解决。

3.2 pushConnection 回收链接

源码详见cn.bugstack.mybatis.datasource.pooled.PooledDataSource

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++;
        }
    }
}
  • 在 PooledDataSource#pushConnection 数据源回收的解决中,外围在于判断链接是否无效,以及进行相干的闲暇链接校验,判断是否把连贯回收到 idle 闲暇链接列表中,并告诉其余线程来抢占。
  • 如果当初闲暇链接短缺,那么这个回收的链接则会进行回滚和敞开的解决中。connection.getRealConnection().close();

3.3 popConnection 获取链接

源码详见cn.bugstack.mybatis.datasource.pooled.PooledDataSource

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);
                logger.info("Checked out connection" + conn.getRealHashCode() + "from pool.");
            }
            // 如果无闲暇链接:创立新的链接
            else {
                // 沉闷连接数有余
                if (state.activeConnections.size() < poolMaximumActiveConnections) {conn = new PooledConnection(dataSource.getConnection(), this);
                    logger.info("Created connection" + 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 connection" + conn.getRealHashCode() + ".");
                    }
                    // 如果 checkout 超时工夫不够长,则期待
                    else {
                        try {if (!countedWait) {
                                state.hadToWaitCount++;
                                countedWait = true;
                            }
                            logger.info("Waiting as long as" + poolTimeToWait + "milliseconds 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.accumulatedRequestTime += System.currentTimeMillis() - t;} else {logger.info("A bad connection (" + conn.getRealHashCode() + ") was returned from 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.");
                    }
                }
            }
        }
    }

    return conn;
}
  • popConnection 获取链接是一个 while 死循环操作,只有获取到链接抛异样才会退出循环,如果仔细阅读这些异样代码,是不是也是你在做一些开发的时候所遇到的异样呢。
  • 获取链接的过程会应用 synchronized 进行加锁,因为所有线程在资源竞争的状况下,都须要进行加锁解决。在加锁的代码块中通过判断是否还有闲暇链接进行返回,如果没有则会判断沉闷连接数是否短缺,不短缺则进行创立后返回。在这里也会遇到沉闷链接曾经进行循环期待的过程,最初再不能获取则抛出异样。

4. 数据源工厂

数据源工厂包含两局部,别离是无池化和有池化,有池化的工程继承无池化工厂,因为在 Mybatis 源码的实现类中,这样就能够缩小对 Properties 对立包装的反射形式的属性解决。因为咱们临时没有对这块逻辑进行开发,只是简略的获取属性传参,所以还不能体现出这样的继承有多便捷,读者能够参考源码进行了解。源码类:UnpooledDataSourceFactory

4.1 无池化工厂

源码详见cn.bugstack.mybatis.datasource.unpooled.UnpooledDataSourceFactory

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 获取数据源解决,把必要的参数进行传递过来。在 Mybatis 源码中这部分则是进行了大量的反射字段解决的形式进行寄存和获取的。

4.2 有池化工厂

源码详见cn.bugstack.mybatis.datasource.pooled.PooledDataSourceFactory

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 共用获取属性的能力,以及实例化出池化数据源即可。

5. 新增类型别名注册器

当咱们新开发了两个数据源和对应的工厂实现类当前,则须要把它们配置到 Configuration 中,这样能力在解析 XML 时候依据不同的数据源类型获取和实例化对应的实现类。

源码详见cn.bugstack.mybatis.session.Configuration

public class Configuration {

    // 类型别名注册机
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

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

        typeAliasRegistry.registerAlias("DRUID", DruidDataSourceFactory.class);
        typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
        typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    }

}
  • 在构造方法 Configuration 增加 UNPOOLED、POOLED 两个数据源注册到类型注册器中,不便后续应用 XMLConfigBuilder#environmentsElement 办法解析 XML 解决数据源时候进行应用。

五、测试

1. 当时筹备

1.1 创立库表

创立一个数据库名称为 mybatis 并在库中创立表 user 以及增加测试数据,如下:

CREATE TABLE
    USER
    (
        id bigint NOT NULL AUTO_INCREMENT COMMENT '自增 ID',
        userId VARCHAR(9) COMMENT '用户 ID',
        userHead VARCHAR(16) COMMENT '用户头像',
        createTime TIMESTAMP NULL COMMENT '创立工夫',
        updateTime TIMESTAMP NULL COMMENT '更新工夫',
        userName VARCHAR(64),
        PRIMARY KEY (id)
    )
    ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
insert into user (id, userId, userHead, createTime, updateTime, userName) values (1, '10001', '1_04', '2022-04-13 00:00:00', '2022-04-13 00:00:00', '小傅哥');    

1.2 配置数据源

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="DRUID">
            <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>
  • 通过 mybatis-config-datasource.xml 配置数据源信息,包含:driver、url、username、password
  • 在这里 dataSource 的配置又上一章节的 DRUID 批改为,UNPOOLED 和 POOLED 进行测试验证。这两个数据源也就是咱们本章节本人实现的数据源。

1.3 配置 Mapper

<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="cn.bugstack.mybatis.test.po.User">
    SELECT id, userId, userName, userHead
    FROM user
    where id = #{id}
</select>
  • Mapper 的配置内容在上一章节的解析学习中曾经做了配置,本章节做了简略的调整。

2. 单元测试

@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.xml 中 dataSource 数据源类型的调整 dataSource type="POOLED/UNPOOLED"

2.1 无池化测试

<dataSource type="UNPOOLED"></dataSource>

测试后果

11:27:48.604 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:27:48.618 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:27:48.622 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:27:48.632 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:27:48.637 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:27:48.642 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:27:48.649 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
...
  • 无池化的连接池操作,会一直的与数据库建设新的链接并执行 SQL 操作,这个过程中只有数据库还有链接能够被链接,就能够创立链接。

2.2 有池化测试

<dataSource type="POOLED"></dataSource>

测试后果

11:30:22.536 [main] INFO  c.b.m.d.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
11:30:22.541 [main] INFO  c.b.m.d.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
11:30:22.541 [main] INFO  c.b.m.d.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
11:30:22.541 [main] INFO  c.b.m.d.pooled.PooledDataSource - PooledDataSource forcefully closed/removed all connections.
11:30:22.860 [main] INFO  c.b.m.d.pooled.PooledDataSource - Created connection 540642172.
11:30:22.996 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:30:23.009 [main] INFO  c.b.m.d.pooled.PooledDataSource - Created connection 140799417.
11:30:23.011 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:30:23.018 [main] INFO  c.b.m.d.pooled.PooledDataSource - Created connection 110431793.
11:30:23.019 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:30:23.032 [main] INFO  c.b.m.d.pooled.PooledDataSource - Created connection 1053631449.
11:30:23.033 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:30:23.041 [main] INFO  c.b.m.d.pooled.PooledDataSource - Created connection 1693847660.
11:30:23.042 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:30:23.047 [main] INFO  c.b.m.d.pooled.PooledDataSource - Created connection 212921632.
11:30:23.048 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:30:23.055 [main] INFO  c.b.m.d.pooled.PooledDataSource - Created connection 682376643.
11:30:23.056 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:30:23.060 [main] INFO  c.b.m.d.pooled.PooledDataSource - Created connection 334203599.
11:30:23.062 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:30:23.067 [main] INFO  c.b.m.d.pooled.PooledDataSource - Created connection 1971851377.
11:30:23.068 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:30:23.073 [main] INFO  c.b.m.d.pooled.PooledDataSource - Created connection 399534175.
11:30:23.074 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}
11:30:23.074 [main] INFO  c.b.m.d.pooled.PooledDataSource - Waiting as long as 20000 milliseconds for connection.
11:30:43.078 [main] INFO  c.b.m.d.pooled.PooledDataSource - Claimed overdue connection 540642172.
11:30:43.079 [main] INFO  cn.bugstack.mybatis.test.ApiTest - 测试后果:{"id":1,"userHead":"1_04","userId":"10001","userName":"小傅哥"}

...
  • 通过应用连接池的配置能够看到,在调用和获取连贯的过程中,当调用次数打到 10 次当前,连接池中就有了 10 个沉闷的链接,再调用的时候则须要期待连贯开释后能力应用并执行 SQL 操作。
  • 测试的过程中还包含了连贯的闲暇数量、沉闷数量、敞开、异样等,读者搭档也能够在学习的过程中进行验证解决。

六、总结

  • 本章节咱们实现了 Mybatis 数据源池化的设计和实现,也能通过这样的剖析、实现、验证的过程让大家更好的了解咱们平时应用的连接池所遇到的一些实在问题都是怎么产生的,做到知其然知其所以然。
  • 另外对于连接池的实现重点能够随着调试验证的过程中进行学习,包含:synchronized 加锁、创立连贯、沉闷数量管制、休眠期待时长,抛异样逻辑等,这些都与咱们日常应用连接池时的配置非亲非故。
  • 这一章节的内容能够算作是 Mybatis 外围性能实现过程上的重要分支,尽管能够应用 Druid 代替数据源的解决,但只有入手本人实现一遍数据源连接池能力更好的了解池化技术的落地计划,也能为当前做此类性能时,有一个可落地的具体计划。
正文完
 0