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

业余草

共 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条数据。

手写线程池
手写线程池

「初始化阶段」

手写线程池

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

手写线程池

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

手写数据库连接池

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

手写数据库连接池

至此,完毕!

浏览 33
点赞
评论
收藏
分享

手机扫一扫分享

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

手机扫一扫分享

举报