mybatis之数据源连接池

本篇来介绍一下 mybatis 的数据源连接池相关内容,我们平时在项目中使用的数据源连接池有Hikaricp、Druid、c3p0等等,大多数情况下我们使用 mybatis 的时候都是集成外部数据源连接池来使用,其实 mybatis 自身也实现了简单的数据源连接池。

知识点

  • 什么是连接池
  • 如何使用 mybatis 连接池
  • mybatis 连接池的实现原理

什么是连接池

关于连接池的概念,我相信大家都比较清楚,对于很新很新的新人,这里还是简单的介绍一下。简单的说就是一个容器里放了很多个连接,我们需要连接就去容器里拿,用完了就还回去,容器负责对所有连接的管理。画了个图,可以感受一下:

image.png

如果还不能理解,就把连接池想象成一个外包公司,里面有很多员工,外面有项目需要就派人出去,项目结束了人就回来,公司就是一个池子,员工就是每个连接。

如何使用 mybatis 连接池

mybatis原生方式

对于 mybatis 连接池的使用,官方文档有介绍

image.png

在 dataSource 中指定 type 即可,目前支持三种内置的实现,分别为 UNPOOLED、POOLED、JNDI,这三种都是 mybatis 的内置定义的假名,会去找到实际的类型,当然我们也可以自定义数据源工厂类来指定其他的数据源,具体实现可以参考官网

集成 spring 方式

通常我们都是集成 srping 来使用 mybatis 的,集成了 spring 之后,以上方式就不起作用了,原因是 spring 框架中会去替换配置里的环境信息(也就是 environment 节点),此时我们就得通过 spring 的配置方式来指定数据源,大致需要以下两步:

1、配置数据源必填属性

    <bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
        <property name="driver" value="${driver}" />
        <property name="url" value="${url}" />
        <property name="username" value="${username}" />
        <property name="password" value="${password}" />
    </bean>
复制代码

2、指定 spring 数据源

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
    </bean>
复制代码

使用 springboot 方式

随着 springboot 使用地越来越多,mybatis 对应的 srpingboot 集成包也面世了,项目中引入以下包即可使用

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
复制代码

当然 mybatis 的 springboot 方式在配置方面还有很多局限性,也包括我们这次要说的使用 mybatis 自带的连接池。如果要使用 mybatis 自带的连接池,一定要自定义一个连接池类来进行包装,也是两步:

1、自定义一个连接池包装类

package com.example.mybatisanalyze.datasource;
​
import org.apache.ibatis.datasource.pooled.PooledDataSource;
​
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
​
/**
 * @author Don
 * @date 2022/2/7.
 */
public class MyDataSource implements DataSource {
​
    private final PooledDataSource dataSource = new PooledDataSource();
​
    public void setDriverClassName(String driver) {
        dataSource.setDriver(driver);
    }
​
    public void setUrl(String url) {
        dataSource.setUrl(url);
    }
​
    public void setUsername(String username) {
        dataSource.setUsername(username);
    }
​
    public void setPassword(String password) {
        dataSource.setPassword(password);
    }
​
    @Override
    public Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
​
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return dataSource.getConnection();
    }
​
    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return dataSource.unwrap(iface);
    }
​
    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return dataSource.isWrapperFor(iface);
    }
​
    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return dataSource.getLogWriter();
    }
​
    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
        dataSource.setLogWriter(out);
    }
​
    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
        dataSource.setLoginTimeout(seconds);
    }
​
    @Override
    public int getLoginTimeout() throws SQLException {
        return dataSource.getLoginTimeout();
    }
​
    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return dataSource.getParentLogger();
    }
}
复制代码

2、配置文件中指定数据源

spring.datasource.type=com.example.mybatisanalyze.datasource.MyDataSource
spring.datasource.username=${username}
spring.datasource.password=${password}
spring.datasource.url=${url}
spring.datasource.driver-class-name=${driverclass}
复制代码

为什么不能直接指定数据源类型为org.apache.ibatis.datasource.pooled.PooledDataSource呢,仔细看代码的朋友估计已经发现了,封装的方法有一处和实际类型的接口名称是不一致的

image.png

因为 springboot 是基于driverClassName来进行注入的,但是 mybatis 的连接池只提供了 driver 的接口注入。

mybatis 连接池的实现原理

概念和使用方面都介绍完了,最后来看一下 mybatis 的连接池是如何实现的,先看代码

image.png

mybatis 连接池相关的代码都在这个包下面。从这里就可以看出有三种实现:jndi、pooled、unpooled,和官方介绍的配置相符。同时也能看出它使用的是工厂模式来创建数据源。jndi 就不做具体介绍了,大概就是通过配置文件的方式进行配置,然后在代码中可以根据名称取得具体的数据源对象进行使用,tomcat就是这么做的,参考JNDI介绍。主要来看下 pooled 方式,顺带也会了解 unpooled 方式。

在介绍原生方式使用的时候说过,通过配置 type 成 POOLED,我们就可以使用池化的数据源,首先是因为 mybatis 内置了对应的假名和具体实现类的映射

image.png

这样就找到具体实现类PooledDataSourceFactory,看下具体代码

image.png

可以看到继承了UnpooledDataSourceFactory,主要是为了对配置的属性进行赋值

image.png

另外就是生成了一个PooledDataSource。这是池化的数据源对象,我这里把注释加了一下,方便理解

image.png

可以看到其中有一个UnpooledDataSource类型的变量,它就是负责新连接创建的。

image.png

所有的用户名密码等属性都是在这里,包括数据库驱动包也是在这里加载,说白了这个类就是配置数据库信息以及获取新连接用的。再回过头来看下PooledDataSource,我们去获取连接的时候,它会进行一次 pop 操作

image.png

这个pop操作是线程安全的,因为用了一个锁,这个锁对象就是PoolStatePoolState是整个池子下唯一的,它用于对连接池信息进行管理,管理哪些信息呢,我这里也在代码中做了注释

image.png

这样就很清晰了,有了PoolState,我们就能够更好的了解连接池的使用情况。再次回过头看PooledDataSource的 popConnection 方法

image.png

这里分解出来就是以下逻辑:

1、空闲连接存在,则直接检出;

2、空闲连接不存在,则先判断活跃连接是否达到最大活跃数

1)否,则新建连接

2)是,则取出最早检出的活跃连接,判断检出时间是否大于最大逾期时间

  • 是,则声明该连接失效,并新建连接
  • 否,则等待配置的等待时间(2s)

3、取到连接则进行一次统计并返回,取不到则再次循环获取

再来看一下 pushConnection 方法

image.png

顾名思义,pop是取出连接,push就是把连接放回去,和pop用的一把锁,同样是线程安全的。什么时候触发连接放回操作呢?看下这个类PooledConnection

image.png

我在这里同样加了注释,这个类的作用大概就是对真实连接的代理,而整个代理操作就是对于close操作放回连接到连接池

image.png

在每次 pop 和 push 连接的时候会检测一下连接的有效性,这时候就用到了 pingConnection 方法

image.png

这个方法也比较简单,就是周期到了会去检测一次连接有效性。

总结

连接池这一块实现还是比较简单的,我们自己在开发设计的时候也可以参考它是怎么实现的,有哪些好处,对于其他连接池也是异曲同工。

参考资料

这篇写得不错:blog.csdn.net/xb_workspac…

猜你喜欢

转载自juejin.im/post/7061903973619335205