Java手写简单数据库连接池

  • Post author:
  • Post category:java




1. 来源

有时候我们在编写一些简单的需要操作数据库的工具时,可能就用不着复杂的spring、mybatis这样的框架,需要尽量的简单化。从下面的测试数据可以看出,创建数据库连接平均耗时1.8秒,释放连接平均耗时0.2秒,如果不使用连接池,那么每次数据库操作都会多花至少2秒的时间来创建和释放连接。当使用了连接池后,只需要在启动和结束的时候花点时间初始化和释放连接,在中途真正操作数据库时会节省大量的时间,提升系统的性能。

  • 连接耗时测试
==========================开始初始化【源库】连接池===================================
[1/10]加载数据库驱动开始
[1/10]加载数据库驱动完成
[1/10]获取数据库连接开始
[1/10]获取数据库连接完成,耗时:2658
[2/10]加载数据库驱动开始
[2/10]加载数据库驱动完成
[2/10]获取数据库连接开始
[2/10]获取数据库连接完成,耗时:1389
[3/10]加载数据库驱动开始
[3/10]加载数据库驱动完成
[3/10]获取数据库连接开始
[3/10]获取数据库连接完成,耗时:1401
[4/10]加载数据库驱动开始
[4/10]加载数据库驱动完成
[4/10]获取数据库连接开始
[4/10]获取数据库连接完成,耗时:1297
[5/10]加载数据库驱动开始
[5/10]加载数据库驱动完成
[5/10]获取数据库连接开始
[5/10]获取数据库连接完成,耗时:2598

......中间过程省略......

正在等待关闭连接池,释放数据库连接...
成功释放【源库】第1/10个连接,单步耗时:198ms,总耗时:198ms
成功释放【源库】第2/10个连接,单步耗时:175ms,总耗时:373ms
成功释放【源库】第3/10个连接,单步耗时:235ms,总耗时:608ms
成功释放【源库】第4/10个连接,单步耗时:187ms,总耗时:795ms
成功释放【源库】第5/10个连接,单步耗时:200ms,总耗时:995ms



3. 连接池源码

package com.open.data.migrate.source.to.target.pool;

import com.open.data.migrate.source.to.target.config.DbConfig;
import com.open.data.migrate.source.to.target.util.DbUtils;
import com.open.data.migrate.source.to.target.util.LogUtils;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.concurrent.ArrayBlockingQueue;

/**
 * @author 2810010108@qq.com
 * @project open-component-migrate-to-mysql
 * @package com.open.component.convert.column.encoding.pool
 * @date 2020/9/10 15:45
 * @description 简单连接池,源库和目标库分别创建各自的连接池,连接池使用阻塞队列实现
 **/
public final class ConnectionPool {

    /**
     * 源库连接池,默认容量为100,可以通过其他方式支持可配
     */
    private static ArrayBlockingQueue<Connection> SOURCE_CONNECTION_POOL = new ArrayBlockingQueue<Connection>(100);
    /**
     * 目标库连接池,默认容量为100,可以通过其他方式支持可配
     */
    private static ArrayBlockingQueue<Connection> TARGET_CONNECTION_POOL = new ArrayBlockingQueue<Connection>(100);

    static {
        // 类加载时初始化数据库连接池
        initConnectionPool();
    }

    /**
     * 初始化数据库连接池方法,通过DbUtils从配置文件读取数据库连接信息,创建N个数据库连接存入队列,N为配置的数据库连接数
     */
    private static void initConnectionPool() {
        DbConfig sourceDbConfig = DbUtils.getSourceDbConfig();
        LogUtils.printInfoLog("==========================开始初始化【源库】连接池===================================");
        for (int i = 0; i < sourceDbConfig.getConnections(); i++) {
            Connection connection = initConnection(sourceDbConfig, i + 1);
            SOURCE_CONNECTION_POOL.add(connection);
        }
        LogUtils.printInfoLog("==========================结束初始化【源库】连接池===================================");
        LogUtils.printInfoLog("==========================开始初始化【目标库】连接池===================================");
        DbConfig targetDbConfig = DbUtils.getTargetDbConfig();
        for (int i = 0; i < targetDbConfig.getConnections(); i++) {
            Connection connection = initConnection(targetDbConfig, i + 1);
            TARGET_CONNECTION_POOL.add(connection);
        }
        LogUtils.printInfoLog("==========================结束初始化【目标库】连接池===================================");
    }

    /**
     * 从源库连接池中阻塞的取得一个连接,队列的take()方法为阻塞式出队
     *
     * @return 源库连接
     */
    public static Connection borrowSource() {
        try {
            return SOURCE_CONNECTION_POOL.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 数据库操作完毕,阻塞的归还源库连接到连接池,队列的put操作为阻塞式入队
     *
     * @param connection 要归还的源库连接
     */
    public static void returnSource(Connection connection) {
        try {
            SOURCE_CONNECTION_POOL.put(connection);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 从目标库连接池中阻塞的取得一个连接,队列的take()方法为阻塞式出队
     *
     * @return 源库连接
     */
    public static Connection borrowTarget() {
        try {
            return TARGET_CONNECTION_POOL.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 数据库操作完毕,阻塞的归还目标库连接到连接池,队列的put操作为阻塞式入队
     *
     * @param connection 要归还的源库连接
     */
    public static void returnTarget(Connection connection) {
        try {
            TARGET_CONNECTION_POOL.put(connection);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * @param config 数据库连接信息对象
     * @param number 序号,用于打印正在初始化的数据库连接序号
     * @return 创建完成的数据库连接
     */
    private static Connection initConnection(DbConfig config, int number) {
        Connection connection = null;
        try {
            //加载数据驱动
            LogUtils.printInfoLog(String.format("[%s/%s]加载数据库驱动开始", number, config.getConnections()));
            Class.forName(config.getDriver());
            LogUtils.printInfoLog(String.format("[%s/%s]加载数据库驱动完成", number, config.getConnections()));
            // 连接数据库
            LogUtils.printInfoLog(String.format("[%s/%s]获取数据库连接开始", number, config.getConnections()));
            connection = DriverManager.getConnection(config.getUrl(), config.getUsername(), config.getPassword());
            LogUtils.printInfoLog(String.format("[%s/%s]获取数据库连接完成", number, config.getConnections()));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            LogUtils.printErrorLog(String.format("[%s/%s]加载数据库驱动失败", number, config.getConnections()));
        } catch (Exception e) {
            e.printStackTrace();
            LogUtils.printErrorLog(String.format("[%s/%s]连接数据库失败", number, config.getConnections()));
        }
        return connection;
    }

    /**
     * 关闭所有的源库和目标库的连接
     */
    public static void closeAllConnections() {
        close(SOURCE_CONNECTION_POOL, "源库");
        close(TARGET_CONNECTION_POOL, "目标库");

    }

    /**
     * 释放传入连接池中所有的连接,避免数据库连接被恶意占用
     *
     * @param pool 要关闭的连接池
     * @param type 库类别标识,用于打印日志
     */
    public static void close(ArrayBlockingQueue<Connection> pool, String type) {
        int size = pool.size();
        int counter = 0;
        long start = System.currentTimeMillis();
        while (true) {
            long startInner = System.currentTimeMillis();
            Connection connection = pool.poll();
            if (connection == null) {
                break;
            }
            try {
                connection.close();
                LogUtils.printInfoLog(String.format("成功释放【%s】第%s/%s个连接,单步耗时:%sms,总耗时:%sms", type, ++counter, size, System.currentTimeMillis() - startInner, System.currentTimeMillis() - start));
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}




3. 出处

该连接池来自于数据库迁移工具,该工具无任何的框架,纯JDK实现,jar运行。可以实现的数据流向有:

  • mysql->mysql
  • mysql->oracle
  • oracle->oracle
  • oracle->mysql

    还可以通过引入驱动包,扩展SourceDbEnums和TargetDbEnums枚举类,可以实现其他数据库支持

项目地址:

https://gitee.com/mengbaoyuxuan/open-data-migrate-source-to-target.git

交流QQ群:566654343



版权声明:本文为musuny原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。