用Java实现简单的数据库连接池

1 引言

对于数据库连接池,相信大家都不会陌生,当下流行的连接池也有很多(如:DBCP,C3P0等),使用连接池可以在程序中给我们带来极大的方便,也可以有效的减少创建连接给我们带来的开销,前段时间公司的项目中有用到DBCP连接池,所以在闲暇之余,自己也尝试着写了一个简易数据库连接池,当然如果对数据库连接池不是很懂的话,建议参考Connection Pooling with Connector/J ,详细的介绍了连接池这项技术。

2 概述

下面介绍的这个简易连接池主要包含了一下内容和功能:

  1. 获取连接
  2. 回收连接
  3. 验证连接的有效性(获取连接前验证)
  4. 定期维护连接
  5. 当前连接耗尽后自动增加连接数

3 源码

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.log4j.Logger;

public class DBPoolUtils {
    private static Logger logger = Logger.getLogger(DBPoolUtils.class);
    /*
     * Map<Connection, String("last_call_time"-"create_time")>
     * 
     * 当连接超过设定生命周期,或者大于设定的空闲时间则该链接自动销毁
     * 本例没有实现生命周期和空闲时间管理,但是数据结构已经构造好了,需要进行生命周期管理的话,可以在
     * release()方法回收时进行判断,if((当前时间-创建时间)>life_cycle_time){销毁连接} 如果不做生命周期管理,pool
     * 可设计为LinkedBlockingQueue<Connection> ,不需要borrowed_queue。
     */
    private static LinkedBlockingQueue<Map<Connection, String>> pool = new LinkedBlockingQueue<>();
    /*
     * Map<Connection,create_time>
     */
    private static Map<Connection, Long> borrowed_queue = new HashMap<>();
    private Integer init_pool_size = 10;
    private static Integer maximum_pool_size = 21;
    /*
     * 计数器,记录当前已生成连接数
     */
    private static AtomicInteger countor = new AtomicInteger(0);
    /*
     * 定期检查:最大空闲连接数,超过时,对多余连接进行回收
     */
    private Integer max_idel = 30;
    /*
     * 定期检查:当当前空闲连接数,小于最小连接数时,新增连接,保持最小空闲连接数
     */
    private Integer min_idel = 5;
    private String driver_class_name = "com.mysql.jdbc.Driver";
    private String driver_url = "jdbc:mysql://127.0.0.1:3306/dbname";
    private String driver_user = "user";
    private String driver_password = "password";
    /*
     * 使用ping可以进行快速验证
     */
    private static final String validation_Query = "/* ping */ SELECT 1";
    /*
     * 1小时 毫秒数,默认连接池检测周期
     */
    private long default_check_period = 60 * 60 * 1000;
    /*
     * 验证连接时超时时间,当连接超时时,自动生成新连接
     */
    private Integer validation_Query_Timeout = 10;
    // 在获取连接时验证
    private boolean test_on_borrow = false;
    // 每次新增连接条数
    private Integer increase_number = 5;

    private DBPoolUtils() {
        // 初始化之前先配置数据
        init();
        // 设置周期性检测空闲连接
        schedule_check();
    }

    private static class InstanceHolder {
        static DBPoolUtils instance = new DBPoolUtils();
    }

    public static DBPoolUtils getInstance() {
        return InstanceHolder.instance;
    }

    /**
     * 获取连接
     * 
     * @return
     */
    public Connection getConnection() {
        Map<Connection, String> connection_map = new HashMap<>();
        Connection connecton = null;
        Long create_time = 0L;
        try {
            connection_map = pool.poll();
            // 当连接池连接已经耗尽之后重新创建新的连接
            if (connection_map == null && countor.get() < maximum_pool_size) {
                // 多重检查
                if (pool.size() == 0 && countor.get() < maximum_pool_size) {
                    increase();
                }
                connection_map = pool.take();
            }

            connecton = (Connection) connection_map.keySet().toArray()[0];
            create_time = Long.valueOf(connection_map.get(connecton).split("-")[1]);
            borrowed_queue.put(connecton, create_time);
        } catch (Exception e) {
        }
        // 在借出连接时检查连接是否可用
        if (test_on_borrow) {
            boolean states = validation_Query(connecton);
            connecton = states ? connecton : getConnection();
        }
        return connecton;
    }

    /**
     * 检测连接是否可用
     * 
     * @param con
     * @return
     */
    private boolean validation_Query(Connection con) {
        boolean tag = false;
        PreparedStatement statement = null;
        try {
            statement = con.prepareCall(validation_Query);
            statement.setQueryTimeout(validation_Query_Timeout);
            statement.executeQuery();
            tag = true;
        } catch (SQLException e) {
            tag = false;
            countor.incrementAndGet();
            if (con != null)
                try {
                    con.close();
                } catch (SQLException e1) {
                    logger.error(e1.getMessage(), e1);
                }
            logger.error(e.getMessage(), e);
        } finally {
            if (statement != null)
                try {
                    statement.close();
                } catch (SQLException e) {
                    logger.error(e.getMessage(), e);
                }
        }
        return tag;
    }

    /**
     * 初始化连接
     */
    private synchronized void init() {
        Connection conn;
        try {
            Class.forName(driver_class_name);
            while (init_pool_size-- > 0) {
                conn = DriverManager.getConnection(driver_url, driver_user, driver_password);
                Map<Connection, String> map = new HashMap<>();
                String time_str = new StringBuilder(String.valueOf(System.currentTimeMillis())).append("-")
                        .append(String.valueOf(System.currentTimeMillis())).toString();
                map.put(conn, time_str);
                pool.put(map);
                countor.incrementAndGet();
            }
        } catch (ClassNotFoundException e) {
            logger.error("Failed to Load \"driver_class_name\" ,int init() method\n" + e.getMessage(), e);
        } catch (SQLException e) {
            logger.error(
                    "Failed to get connection to database ,please check configure file!if thest configure was correct configured!\n"
                            + e.getMessage(),
                    e);
        } catch (InterruptedException e) {
            logger.error("Failed to put connection to pool,please check data structure!\n" + e.getMessage(), e);
        }
    }

    /**
     * 连接回收
     * 
     * @param connection
     */
    public void release(Connection connection) {
        /*
         * 使用一条线程来处理回收,主要是考虑到回收的效率问题,但这样可能会存在另外一个问题
         * 已经释放掉的连接,未被及时放入线程池中
         * 但是这样的处理方式,可以加快程序的反应速度,具体影响待测,当然理论上只要逻辑没有问
         * 题,是不会产生什么实际影响。
         */
        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    if (connection != null) {
                        long create_time = borrowed_queue.remove(connection);

                        String time_str = new StringBuilder(String.valueOf(System.currentTimeMillis())).append("-")
                                .append(String.valueOf(create_time)).toString();
                        Map<Connection, String> map = new HashMap<>();
                        map.put(connection, time_str);
                        pool.put(map);
                        System.out.println("重入连接池....");
                    }
                } catch (InterruptedException e) {
                    logger.info(e.getMessage(), e);
                }
            }
        }).start();
    }

    /**
     * 连接池耗尽时,若未达最大连接数,则增加连接。
     */
    private synchronized void increase() {
        // 检测,如果前一个进入该方法的线程已经产生了新连接,则不做处理,否则产生因连接
        if (pool.size() == 0) {
            // 增加新连接
            Integer current_number = 0;
            /*
             * 用于记录可增加的数量,如果该数量大于增长量,则用while增加,否则使用指定数量的另一个方法增加
             */
            Integer tag_number = maximum_pool_size - countor.get();
            if (tag_number >= increase_number)
                while (countor.get() <= maximum_pool_size && current_number++ < increase_number) {
                    try {
                        Connection conn;
                        conn = DriverManager.getConnection(driver_url, driver_user, driver_password);
                        Map<Connection, String> map = new HashMap<>();
                        String time_str = new StringBuilder(String.valueOf(System.currentTimeMillis())).append("-")
                                .append(String.valueOf(System.currentTimeMillis())).toString();
                        map.put(conn, time_str);
                        pool.put(map);
                        countor.incrementAndGet();
                    } catch (SQLException e) {
                        logger.error("Failed to increase connections:\t" + e.getMessage(), e);
                    } catch (InterruptedException e) {
                        logger.error("Failed to put connection into pool:\t" + e.getMessage(), e);
                    }
                }
            else
                increase(tag_number - 1);

        }
    }

    /**
     * 新增指定条数的连接
     * 
     * @param increase_number
     */
    private synchronized void increase(Integer increase_number) {
        // 增加新连接
        while (increase_number-- > 0) {
            Connection conn;
            try {
                conn = DriverManager.getConnection(driver_url, driver_user, driver_password);
                Map<Connection, String> map = new HashMap<>();
                String time_str = new StringBuilder(String.valueOf(System.currentTimeMillis())).append("-")
                        .append(String.valueOf(System.currentTimeMillis())).toString();
                map.put(conn, time_str);
                pool.put(map);
                countor.incrementAndGet();
            } catch (SQLException e) {
                logger.error("Failed to increase connections:\t" + e.getMessage(), e);
            } catch (InterruptedException e) {
                logger.error("Failed to put connection into pool:\t" + e.getMessage(), e);
            }
        }
    }

    /**
     * 销毁所有连接 Close all connections
     */
    public void close_All_Connections() {
        Map<Connection, String> connection_map = new HashMap<>();
        connection_map = pool.poll();
        int i = 0;
        System.out.println(pool.size());
        while (connection_map != null) {
            Connection con = (Connection) connection_map.keySet().toArray()[0];
            try {
                if (con != null)
                    con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            connection_map = pool.poll();
            i++;
        }
        System.out.println("回收完毕" + i);
    }

    /**
     * 检测空闲连接
     */
    private void check_idel_connection() {
        //当前大小即是空闲连接数量
        Integer current_idel = pool.size();
        Map<Connection, String> connection_map = new HashMap<>();
        // 若当前空闲连接数小于最小空闲连接数--新增连接
        if (current_idel < min_idel) {
            int j = min_idel - current_idel;
            increase(j);
        } else if (current_idel > max_idel) {
            // 若当前空闲连接数大于最大空闲连接数--关闭连接
            int i = current_idel - max_idel;
            connection_map = pool.poll();
            while (i-- > 0) {
                if (connection_map != null) {
                    Connection con = (Connection) connection_map.keySet().toArray()[0];
                    try {
                        if (con != null) {
                            con.close();
                            countor.incrementAndGet();
                        }
                    } catch (SQLException e) {
                        logger.error("Failed to close connection!/t" + e.getMessage(), e);
                    }
                } else
                    break;
            }

        }
    }

    /**
     * 在指定的时间间隔内检查连接状态
     */
    private void schedule_check() {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {

            @Override
            public void run() {
                check_idel_connection();
            }
        }, default_check_period, default_check_period);
    }
}

测试类

public class Ntest {
    public static Integer counter=0;
    static double start=System.currentTimeMillis();
    static double end=0;
    public static void main(String[] args) {
        int i=100000;
        while(i-->0){
            new Thread(new Runnable() {
                Connection con=null;
                @Override
                public void run() {
                    System.out.println(this.con=DBPoolUtils.getInstance().getConnection());
                    DBPoolUtils.getInstance().release(this.con);
                    System.out.println("current:"+(++counter));
                    if(counter>=9999){
                        end=System.currentTimeMillis();
                        System.out.println((end-start)/1000.0);
                    }
                }
            }).start();
        }
    }
}

4 测试结果

测试数据:初始化连接数:10条,最大连接数:20条,请求线程数:10W,运行时间:11-14秒(不开启获取连接时进行连接测试)
测试数据:初始化连接数:10条,最大连接数:20条,请求线程数:10W,运行时间:14-20秒 (开启获取连接时验证)

5 总结

这个简易线程池只实现了最基本的提供连接管理的功能(创建回收),稍微改善程序可以在此基础之上实现连接生命周期管理,对于验证连接的有效性,采取的是在获取连接前验证的方法,该方法可能会导致额外的开销,可以采用空闲时检测,或者重入池回收时检测,另外还需测试长时间运行下是否能正确管理连接池,保证连接池正确运行。More important :如果发现有什么问题,或者有建议,希望大家能告诉我,谢谢。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值