自定义数据库连接池实现方式 MySQL

应用程序直接获取数据库连接缺点

用户每次请求都会建立一次数据库连接,并且数据库创建连接会消耗相对大的资源和时间。
如果针对于个别的工具或者是大量的代码测试甚至系统运行,对数据库操作次数频繁,极大的占用数据库资源,有可能会发生宕机或者内存溢出的现象。

而在大多的项目中,常常用到阿里巴巴开源的数据库连接池框架,准确来说它不仅仅包括数据库连接池,
原因其实很简单,在Spring框架的配置文件中仅仅一个配置datasource就可以使用Druid了
我们对Druid不加探讨,实现自定义的数据库连接池,来对数据进行处理。

在pom文件中引人数据库所需要的jar包,这里不一一介绍。
建立JdbcConnectionsPool类实现数据源接口DataSource重写其getConnection方法

package com.herbert.test.db;
 
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.Properties;
import java.util.logging.Logger;
 
 
/**
 * 实现数据库连接池
 */
public class JdbcConnectionsPool implements DataSource {
    //
 
    /*
     * 使用静态块代码,初始化连接池,创建连接池的中最小链接数量连接,
     * 创建linkedlist集合,将这些连接放入集合中
     */
    //创建linkedlist集合
    private static LinkedList<Connection> linkedlist1 = new LinkedList<Connection>();
    public static String GENERATOR_JDBCDRIVER = "";
 
    public static String GENERATOR_JDBCURL = "";
 
    public static String GENERATOR_JDBCUSERNAME = "";
 
    public static String GENERATOR_JDBCPASSWORD = "";
 
    private static int jdbcConnectionInitSize;//最小连接数量
 
    private static int max = 1; //当前最大连接数量=max*jdbcConnectionInitSize
 
    private static Properties prop = new Properties();
 
    static {
        //通过反射机制获取访问db.properties文件
        InputStream in = Object.class.getResourceAsStream("/generator.properties");
 
        try {
            //加载db.properties文件
            prop.load(in);
            //获取db.properties文件中的数据库连接信息
            GENERATOR_JDBCDRIVER = prop.getProperty("generator.jdbc.driver").trim();
            GENERATOR_JDBCURL = prop.getProperty("generator.jdbc.url").trim();
            GENERATOR_JDBCUSERNAME = prop.getProperty("generator.jdbc.username").trim();
            GENERATOR_JDBCPASSWORD = prop.getProperty("generator.jdbc.password").trim();
            jdbcConnectionInitSize = Integer.parseInt(prop.getProperty("jdbcConnectionInitSize"));
 
            Class.forName(GENERATOR_JDBCDRIVER);
 
            //创建最小连接数个数据库连接对象以备使用
            for (int i = 0; i < jdbcConnectionInitSize; i++) {
                Connection conn = DriverManager.getConnection(GENERATOR_JDBCURL, GENERATOR_JDBCUSERNAME, GENERATOR_JDBCPASSWORD);
                System.out.println("获取到了链接" + conn);
                //将创建好的数据库连接对象添加到Linkedlist集合中
                linkedlist1.add(conn);
            }
 
 
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
    }
 
    @Override
    public Connection getConnection() throws SQLException {
        //如果集合中没有数据库连接对象了,且创建的数据库连接对象没有达到最大连接数量,可以再创建一组数据库连接对象以备使用
        if (linkedlist1.size() == 0 && max <= jdbcConnectionInitSize) {
            try {
                Class.forName("com.mysql.jdbc.Driver");
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            for (int i = 0; i < jdbcConnectionInitSize; i++) {
                Connection conn = DriverManager.getConnection(GENERATOR_JDBCURL, GENERATOR_JDBCUSERNAME, GENERATOR_JDBCPASSWORD);
                System.out.println("获取到了链接" + conn);
                //将创建好的数据库连接对象添加到Linkedlist集合中
                linkedlist1.add(conn);
            }
            max++;
        }
        if (linkedlist1.size() > 0) {
            //从linkedlist集合中取出一个数据库链接对象Connection使用
            final Connection conn1 = linkedlist1.removeFirst();
            System.out.println("linkedlist1数据库连接池大小是" + linkedlist1.size());
                        /*返回一个Connection对象,并且设置Connection对象方法调用的限制,
            *当调用connection类对象的close()方法时会将Connection对象重新收集放入linkedlist集合中。
            */
            return
 
          (Connection) Proxy.newProxyInstance(JdbcConnectionsPool.class.getClassLoader(),//这里换成JdbcConnectionsPool.class.getClassLoader();也行
                  new Class[] { Connection.class }, new InvocationHandler() {
 
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            if (!method.getName().equalsIgnoreCase("close")) {
                                return method.invoke(conn1, args);
                            } else {
                                linkedlist1.add(conn1);
                                System.out.println(conn1 + "对象被释放,重新放回linkedlist集合中!");
                                System.out.println("此时Linkedlist集合中有" + linkedlist1.size() + "个数据库连接对象!");
                                return null;
                            }
 
                        }
                    });
 
        } else {
            System.out.println("连接数据库失败!");
        }
        return null;
    }
 
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }
 
    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }
 
    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
 
    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }
 
    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
 
    }
 
    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
 
    }
 
    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }
 
    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}
对方法进行进一步封装
package com.herbert.test.db;
 
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
 
/**
 * Created by Herbert on 2018/7/23.
 */
public class JdbcConnectionPoolUtil {
    /**
     * @Field: pool
     * 数据库连接池
     */
    private static JdbcConnectionsPool pool = new JdbcConnectionsPool();
 
    /**
     * @return Connection数据库连接对象
     * @throws SQLException
     * @Method: getConnection
     * @Description: 从数据库连接池中获取数据库连接对象
     */
    public static Connection getConnection() throws SQLException {
        return pool.getConnection();
    }
 
    /**
     * @param conn
     * @param st
     * @param rs
     * @Method: release
     * @Description: 释放资源,
     * 释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
     */
    public static void release(Connection conn, Statement st, ResultSet rs) {
        if (rs != null) {
            try {
                //关闭存储查询结果的ResultSet对象
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            rs = null;
        }
        if (st != null) {
            try {
                //关闭负责执行SQL命令的Statement对象
                st.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
 
        if (conn != null) {
            try {
                //关闭Connection数据库连接对象
                conn.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
 
}
测试方法
 public static void main(String[] args) throws SQLException {
 
        JdbcConnectionPoolUtil jcpt = new JdbcConnectionPoolUtil();
        List list = new ArrayList();
        Connection c = null;
        Statement stmt = null;
        try {
            c = jcpt.getConnection();
            c.setAutoCommit(false);
            stmt = c.createStatement();
            String sql = "SELECT id FROM test";
            PreparedStatement preState = c.prepareStatement(sql);
 
            ResultSet rs = preState.executeQuery();
 
            while (rs.next()) {
                String id = rs.getString("id");
                list.add(id);
            }
            jcpt.release(c, stmt, rs);
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
            System.exit(0);
        }
        System.out.println("测试:"+list.toString());
 
    }
输出结果:
获取到了链接com.mysql.jdbc.JDBC4Connection@b1a58a3
获取到了链接com.mysql.jdbc.JDBC4Connection@46ee7fe8
获取到了链接com.mysql.jdbc.JDBC4Connection@722c41f4
获取到了链接com.mysql.jdbc.JDBC4Connection@2957fcb0
获取到了链接com.mysql.jdbc.JDBC4Connection@f2a0b8e
获取到了链接com.mysql.jdbc.JDBC4Connection@64a294a6
获取到了链接com.mysql.jdbc.JDBC4Connection@61e717c2
获取到了链接com.mysql.jdbc.JDBC4Connection@504bae78
获取到了链接com.mysql.jdbc.JDBC4Connection@5fcfe4b2
获取到了链接com.mysql.jdbc.JDBC4Connection@3f8f9dd6
linkedlist1数据库连接池大小是9
com.mysql.jdbc.JDBC4Connection@b1a58a3对象被释放,重新放回linkedlist集合中!
此时Linkedlist集合中有10个数据库连接对象!
测试:[1, 2]
异常处理

java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to java.sql.Connection异常问题解决
代码如下

Connection proxy = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(),
                Connection.class.getInterfaces(), new InvocationHandler() {
 
                    @Override
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        if ("close".equals(method.getName())) {
                            returnConn(conn);
                            return null;
                        } else {
                            return method.invoke(conn, args);
                        }
                    }
                });

在使用动态代理增强Connection连接对象的close方法时,我碰到了如题所示的异常。通过搜索我发现这个异常出现的原因在于我使用的mysql数据库驱动的问题,由于数据库驱动不同,Connection.class.getInterfaces()返回的结果也不同,它返回的是一个Class[]数组,然而此数组的第一个元素必须是Connection才能把创建的代理类转为Connection对象,否则就会报错。

所以这里我们可以采取一个替代方式替换Connection.class.getInterfaces(),即new Class[] { Connection.class },这样无论数据库驱动是什么版本的驱动,都能保证这个类型转换不出错。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容