手把手教你写一个数据库连接池!

业余草

共 9016字,需浏览 19分钟

 ·

2022-03-05 21:05

你知道的越多,不知道的就越多,业余的像一棵小草!

你来,我们一起精进!你不来,我和你的竞争对手一起精进!

编辑:业余草

juejin.cn/post/6951343274946723870

推荐:https://www.xttblog.com/?p=5319

文章目录

  • 术语介绍
  • 实现原理
  • 代码实现
    • 连接池配置文件
    • 连接池管理
    • 连接池接口
    • 连接池实现
    • 演示代码
    • 演示效果说明

关于数据库连接池原理,什么杂七杂八的,本文不再重复啰嗦。有什么不理解的参考如下文章。

面试官:数据库连接池为什么都要用Threadlocal呢?

Http 持久连接与 HttpClient 连接池

真相让你吃惊,数据库连接池到底应该设多大?

阅读本文手写一套数据库连接池,您可能需要了解如下几个知识点:

  1. 数据库连接池的原理及作用
  2. 并发队列介绍及使用
  3. 配置文件properties信息映射到Java对象

在我们配置连接池的时候,会配置一些数据,比如最小空闲连接数,最大空闲连接数等等,本文中,您需要理解如下几个概念。

术语介绍

「空闲连接池」:用来存放已经被创建,但是未被使用的连接的容器。
「活动连接池」:用来存放已经被创建,并且被使用的连接的容器。
「最大空闲数」:空闲连接池中,最多存在的空闲连接数量。
「初始化连接数」:第一次加载的时候,需要创建的连接数量,一般大于最小空闲数,小于最大空闲数。
「最大连接数」:空闲连接和活动连接之和。

实现原理

「1、初始化」:第一次加载的时候,根据配置的初始化连接数,创建连接,将创建的连接放入到空闲连接池;
「2、获取连接」:优先从空闲池获取,如果空闲池没有,就创建一个新的连接

优先从空闲池获取

3、「释放连接」:将需要释放的连接从活动连接池中移除,如果空闲连接池没有满,放将移除的连接放入到空闲连接池,如果空闲连接池已经满了,则关闭此链接。

代码实现

代码结构如下

代码结构

连接池配置文件

##驱动名称:不推荐com.mysql.jdbc.Driver,而是推荐使用com.mysql.cj.jdbc.Driver
driverName = com.mysql.cj.jdbc.Driver
##数据库连接地址
url = jdbc:mysql://127.0.0.1:3306/test
userName = root
passWord = root
##连接池名字
poolName = Hutao Connection Pool
##空闲池,最小连接数
minFreeConnections = 1
##初始化空闲池连接数
initFreeConnections = 3
##空闲池,最大连接数
maxFreeConnections = 5
##最大允许的连接数,一般小于数据库总连接数
maxConnections = 15
##重试获得连接的频率  一秒
retryConnectionTimeOut = 1000
##连接超时时间,默认20分钟  1000 * 60 * 20
connectionTimeOut = 1200000
/**
 * @Description:数据库连接池属性信息
 */

public class DbProperties {

    /* 链接属性 */
    private String driverName;

    private String url;

    private String userName;

    private String passWord;

    private String poolName;

    /**
     * 空闲池,最小连接数
     */

    private int minFreeConnections;

    /**
     * 空闲池,最大连接数
     */

    private int maxFreeConnections;

    /**
     * 初始连接数
     */

    private int initFreeConnections;

    /**
     * 重试获得连接的频率  毫秒
     */

    private long retryConnectionTimeOut;

    /**
     * 最大允许的连接数
     */

    private int maxConnections;

    /**
     * 连接超时时间
     */

    private long connectionTimeOut;

    public String getDriverName() {
            return driverName;
    }

    public void setDriverName(String driverName) {
            this.driverName = driverName;
    }

    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 String getPoolName() {
            return poolName;
    }

    public void setPoolName(String poolName) {
            this.poolName = poolName;
    }

    public int getMinFreeConnections() {
            return minFreeConnections;
    }

    public void setMinFreeConnections(int minFreeConnections) {
            this.minFreeConnections = minFreeConnections;
    }

    public int getMaxFreeConnections() {
            return maxFreeConnections;
    }

    public void setMaxFreeConnections(int maxFreeConnections) {
            this.maxFreeConnections = maxFreeConnections;
    }

    public int getInitFreeConnections() {
            return initFreeConnections;
    }

    public void setInitFreeConnections(int initFreeConnections) {
            this.initFreeConnections = initFreeConnections;
    }

    public long getRetryConnectionTimeOut() {
            return retryConnectionTimeOut;
    }

    public void setRetryConnectionTimeOut(long retryConnectionTimeOut) {
            this.retryConnectionTimeOut = retryConnectionTimeOut;
    }

    public int getMaxConnections() {
            return maxConnections;
    }

    public void setMaxConnections(int maxConnections) {
            this.maxConnections = maxConnections;
    }

    public long getConnectionTimeOut() {
            return connectionTimeOut;
    }

    public void setConnectionTimeOut(long connectionTimeOut) {
            this.connectionTimeOut = connectionTimeOut;
    }

    @Override
    public String toString() {
            return "DbProperties [driverName=" + driverName + ", url=" + url + ", userName=" + userName + ", passWord="
                            + passWord + ", poolName=" + poolName + ", minFreeConnections=" + minFreeConnections
                            + ", maxFreeConnections=" + maxFreeConnections + ", initFreeConnections=" + initFreeConnections
                            + ", retryConnectionTimeOut=" + retryConnectionTimeOut + ", maxConnections=" + maxConnections
                            + ", connectionTimeOut=" + connectionTimeOut + "]";
    }
}

连接池管理

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.util.Enumeration;
import java.util.ResourceBundle;

import com.hutao.pool.database.pojo.DbProperties;
import com.hutao.pool.database.service.DbPoolService;
import com.hutao.pool.database.service.impl.DbPoolServiceImpl;

/**
 * @Description:数据库连接池管理
 */

public class DbPoolManager {

    private static String sourcePath = "com/hutao/resources/database";

    /**
     * 数据库连接池配置属性
     */

    private static DbProperties properties = null;

    /**
     * 数据库连接池接口
     */

    private static DbPoolService connectionPool = null;


    /**
     * 双重检查机制静态加载连接池
     */

    static {
            try {
                    if(properties == null) {
                            synchronized(DbPoolManager.class{
                                    if(properties == null) {
                                            properites2Object();
                                            connectionPool = new DbPoolServiceImpl(properties);
                                    }
                            }
                    }
            } catch (Exception e) {
                    e.printStackTrace();
            }

    }

    /**
     * @Description:数据库连接池database配置文件映射到java对象
     */

    private static void properites2Object() throws NoSuchFieldException, IllegalAccessException {
            properties = new DbProperties();
            ResourceBundle resourceBundle = ResourceBundle.getBundle(sourcePath);
            //获取资源文件中所有的key
            Enumeration keys = resourceBundle.getKeys();
            while (keys.hasMoreElements()) {
                    String key = (String) keys.nextElement();
                    //反射获取类中的属性字段
                    Field field= DbProperties.class.getDeclaredField(key);
                    //属性字段的类型
                    Type genericType = field.getGenericType();
                    //属性设置可访问
                    field.setAccessible(true);
                    //根据key读取对应的value值
                    String value = resourceBundle.getString(key);
                    if("int".equals(genericType.getTypeName())) {
                            //反射给属性赋值
                            field.set(properties, Integer.parseInt(value));
                    }else if("long".equals(genericType.getTypeName())) {
                            field.set(properties, Long.parseLong(value));
                    }else if("java.lang.String".equals(genericType.getTypeName())) {
                            field.set(properties,value);
                    }
            }
    }

    /**
     * @Description:获取连接
     */

    public static Connection getConnection() {
            return connectionPool.getConnection();
    }

    /**
     * @Description:释放连接
     */

    public static void releaseConnection(Connection connection) {
            connectionPool.releaseConnection(connection);
    }

连接池接口

import java.sql.Connection;

/**
 * @Description:数据库连接池
 */

public interface DbPoolService {
 
    /**
     * @Description:判断连接是否可用,可用返回true
     */

    public boolean isAvailable(Connection connection);

    /**
     * @Description:使用重复利用机制获取连接
     */

    public Connection getConnection();

    /**
     * @Description:使用可回收机制释放连接
     */

    public void releaseConnection(Connection connection);
}

连接池实现

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import com.hutao.pool.database.pojo.DbProperties;
import com.hutao.pool.database.service.DbPoolService;

/**
 * @Description:数据库连接池实现
 */

public class DbPoolServiceImpl implements DbPoolService {
 
    /**
     * 存放空闲连接的容器,除了可以使用并发队列,也可以使用线程安全的集合Vector
     */

    private BlockingQueue freeConnection = null;
    /**
     * 存放活动连接的容器,除了可以使用并发队列,也可以使用线程安全的集合Vector
     */

    private BlockingQueue activeConnection = null;

    /**
     * 存放映射的属性配置文件
     */

    private DbProperties dDbProperties;


    public DbPoolServiceImpl(DbProperties dDbProperties) throws Exception {
        // 获取配置文件信息
        this.dDbProperties = dDbProperties;
        freeConnection =  new LinkedBlockingQueue<>(dDbProperties.getMaxFreeConnections());
        activeConnection = new LinkedBlockingQueue<>(dDbProperties.getMaxConnections());
        init();
    }

    /**
     * @Description:初始化空闲线程池
     */

    private void init() throws Exception {
        System.out.println("初始化线程池开始,线程池配置属性:"+dDbProperties);
        if (dDbProperties == null) {
                throw new  Exception("连接池配置属性对象不能为空");
        }
        //获取连接池配置文件中初始化连接数
        for (int i = 0; i < dDbProperties.getInitFreeConnections(); i++) {
                //创建Connection连接
                Connection newConnection = newConnection();
                if (newConnection != null) {
                        //将创建的新连接放入到空闲池中
                        freeConnection.add(newConnection);
                }
        }
        System.out.println("初始化线程池结束,初始化线程数:"+dDbProperties.getInitFreeConnections());
    }

    private synchronized Connection newConnection() {
        try {
                Class.forName(dDbProperties.getDriverName());
                return DriverManager.getConnection(dDbProperties.getUrl(), dDbProperties.getUserName(),dDbProperties.getPassWord());
        } catch (Exception e) {
                e.printStackTrace();
                return null;
        }
    }

    /**
     * @Description:判断连接是否可用,可用返回true
     */

    @Override
    public boolean isAvailable(Connection connection) {
        try {
                if (connection == null || connection.isClosed()) {
                        return false;
                }
        } catch (Exception e) {
                e.printStackTrace();
        }
        return true;
    }

    /**
     * @Description:使用重复利用机制获取连接:如果总连接未超过最大连接,则从空闲连接池获取连接或者创建一个新的连接,如果超过最大连接,则等待一段时间以后,继续获取连接
     */

    @Override
    public synchronized Connection getConnection() {
        Connection connection = null;
        //空闲连接和活动连接的总数加起来 小于 最大配置连接
        System.out.println("当前空闲连接总数:"+freeConnection.size()+" 当前活动连接总数"+activeConnection.size()+", 配置最大连接数:"+ dDbProperties.getMaxConnections());
        if (freeConnection.size()+activeConnection.size() < dDbProperties.getMaxConnections()) {
                //空闲连接池,是否还有还有连接,有就取出来,没有就创建一个新的。
                if (freeConnection.size() > 0) {
                        connection = freeConnection.poll();
                        System.out.println("从空闲线程池取出线程:"+connection+"当前空闲线程总数:"+freeConnection.size());
                } else {
                        connection = newConnection();
                        System.out.println("空闲连接池没有连接,创建连接"+connection);
                }
                //拿到的连接可用,就添加活动连接池,否则就递归继续找下一个
                boolean available = isAvailable(connection);
                if (available) {
                        activeConnection.add(connection);
                } else {
                        connection = getConnection();
                }

        } else {
                System.out.println("当前连接数已达到最大连接数,等待"+dDbProperties.getRetryConnectionTimeOut()+"ms以后再试");
                try {
                        wait(dDbProperties.getRetryConnectionTimeOut());
                } catch (InterruptedException e) {
                        e.printStackTrace();
                }
                connection = getConnection();
        }
        return connection;
    }

    /**
     * @Description:使用可回收机制释放连接:如果连接可用,并且空闲连接池没有满,则把连接归还到空闲连接池,否则关闭连接
     */

    @Override
    public synchronized void releaseConnection(Connection connection) {
        try {
            if (isAvailable(connection) && freeConnection.size() < dDbProperties.getMaxFreeConnections()) {
                    freeConnection.add(connection);
                    System.out.println("空闲线程池未满,归还连接"+connection);

            } else {
                    connection.close();
                    System.out.println("空闲线程池已满,关闭连接"+connection);
            }
            activeConnection.remove(connection);
            notifyAll();
        } catch (Exception e) {
                e.printStackTrace();
        }
    }
}

演示代码

创建20个线程,每个线程操作数据库20次,每次操作往数据库写入一条数据。

创建表结构。

CREATE TABLE `test` (
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
public class Test001 {
    public static void main(String[] args) {
        ThreadConnection threadConnection = new ThreadConnection();
        for (int i = 0; i < 20; i++) {
                Thread thread = new Thread(threadConnection, "线程:" + i);
                thread.start();
        }
    }
}

class ThreadConnection implements Runnable {

    public void run() {
        for (int i = 0; i < 20; i++) {
            Connection connection = DbPoolManager.getConnection();
            System.out.println(Thread.currentThread().getName() + ",connection:" + connection);
            Statement statement;
            try {
                    statement = connection.createStatement();
                    String selectsql = "select * from test";
                    statement.execute(selectsql);

                    String insertsql = "insert into test(name) VALUES('"+Thread.currentThread().getName()+connection+"')";
                    statement.execute(insertsql);
                    statement.close();
            } catch (SQLException e) {
                    e.printStackTrace();
            }
            DbPoolManager.releaseConnection(connection);
        }
    }
}

演示效果说明

如果每个连接都能正常工作,总共20个并发线程,每个线程执行20次数据库查询,插入操作,执行完毕后,数据库应该有400条数据。

手写线程池
手写线程池

「初始化阶段」

手写线程池

「达到最大连接数,进入重试阶段」

手写线程池

「连接的获取和归还到空闲池」

手写数据库连接池

「空闲连接池已满,关闭连接」

手写数据库连接池

至此,完毕!

浏览 34
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报