自定义数据库连接池

时间很快就到周末了,学习计划也已经进行了五天了,既然是周末的话,那当然要多学习一点知识,毕竟拥有这么充裕的时间。

今天的学习内容是数据库连接池。
那什么是数据库连接池,它有什么作用是我们首先会想到的问题。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。

应用程序直接获取连接的缺点:
用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机。

缺点显而易见,应用程序在创建连接和销毁连接的时候是极其消耗资源的,而使用数据库连接池则能够优化程序性能。

连接池原理:
在服务器端一次性地创建多个连接,将多个连接保存在一个连接池对象中,当请求需要操作数据库时,不会为请求创建新的连接,而是直接从连接池中获得一个连接。当操作数据库结束,并不需要真正的去关闭连接,而是将连接放回到连接池中。

了解了数据库连接池的优点后,我们关心的是该如何去实现数据库连接池呢?
在Java中提供了javax.sql.Datasource接口用于实现数据库连接池。
老话说得好,光说不练假把式,只练不打无用功,现在我们就来写一个程序感受一下。

在MyEclipse中新建一个web项目,取名demo。
新建一个类,取名MyDataSource,然后实现DataSource接口,要实现的方法非常多,但是不用紧张,我们只关注两个方法。

public Connection getConnection() throws SQLException {
    return null;
}

public Connection getConnection(String username, String password)
        throws SQLException {
    return null;
}

方便起见,我们只看无参的getConnection()方法。自定义的连接池需要有如下功能

  • 一次性地创建多个连接
  • 实现getConnection方法,从连接池获得一个连接
  • 当用户使用连接后,提供方法将连接放回到连接池中
    代码如下:
/**
 * 自定义连接池
 * 
 *  一次性地创建多个连接
 *  
 *  实现getConnection方法,从连接池获得一个连接
 *  
 *  当用户使用连接后,提供方法将连接放回到连接池中
 *  
 * @author Administrator
 * 
 */
public class MyDataSource implements DataSource {
    
    private LinkedList<Connection> dataSources = new LinkedList<Connection>();
    
    public MyDataSource(){
        //一次性创建10个连接
        for(int i = 0;i < 10;i++){
            try {
                Connection connection = JDBCUtils.getConnection();
                //将连接加入到连接池中
                dataSources.add(connection);
            } catch (Exception e) {
                e.printStackTrace();
            }
            
        }
    }

    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    public void setLoginTimeout(int seconds) throws SQLException {

    }

    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    public Connection getConnection() throws SQLException {
        //取出连接池中的一个连接
        Connection connection = dataSources.removeFirst();//删除第一个连接并返回
        return connection;
    }

    public Connection getConnection(String username, String password)
            throws SQLException {
        return null;
    }
}

代码中的JDBCUtils是我编写的一个工具类,在之前的博客中也都有提及,为了方便大家,我就再贴一次。

/**
 * JDBC 工具类,抽取公共方法
 * 
 * @author seawind
 * 
 */
public class JDBCUtils {
    private static final String DRIVERCLASS;
    private static final String URL;
    private static final String USER;
    private static final String PWD;

    static {
        ResourceBundle bundle = ResourceBundle.getBundle("dbconfig");
        DRIVERCLASS = bundle.getString("DRIVERCLASS");
        URL = bundle.getString("URL");
        USER = bundle.getString("USER");
        PWD = bundle.getString("PWD");
    }

    // 建立连接
    public static Connection getConnection() throws Exception {
        loadDriver();
        return DriverManager.getConnection(URL, USER, PWD);
    }

    // 装载驱动
    private static void loadDriver() throws ClassNotFoundException {
        Class.forName(DRIVERCLASS);
    }

    // 释放资源
    public static void release(ResultSet rs, Statement stmt, Connection conn) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            rs = null;
        }

        release(stmt, conn);
    }

    public static void release(Statement stmt, Connection conn) {
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            stmt = null;
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            conn = null;
        }
    }
}

现在新建一个测试类,取名MyDataSourceTest

public class MyDataSourceTest {
    public static void main(String[] args) throws SQLException {
        //创建连接池
        MyDataSource dataSource = new MyDataSource();
        //从连接池中获得一个连接
        Connection connection = dataSource.getConnection();
        //操作数据库
        String sql = "select * from account";
        PreparedStatement stmt = connection.prepareStatement(sql);
        ResultSet rs = stmt.executeQuery();
        while (rs.next()) {
            System.out.println(rs.getString("name"));
        }
        JDBCUtils.release(stmt, connection);
    }
}

运行测试类


在这里插入图片描述

控制台成功输出用户姓名。
但是这段程序是有问题的,因为JDBCUtils工具类中的release()方法会将连接关闭,而我们的想法是将连接归还到连接池而不是关闭它。实现方法有很多种,你可以在自定义连接池MyDataSource中添加一个方法用于归还连接。
重新修改MyDataSource类,代码如下

/**
 * 自定义连接池
 * 
 *  一次性地创建多个连接
 *  
 *  实现getConnection方法,从连接池获得一个连接
 *  
 *  当用户使用连接后,提供方法将连接放回到连接池中
 *  
 * @author Administrator
 * 
 */
public class MyDataSource implements DataSource {
    
    private LinkedList<Connection> dataSources = new LinkedList<Connection>();
    
    public MyDataSource(){
        //一次性创建10个连接
        for(int i = 0;i < 10;i++){
            try {
                Connection connection = JDBCUtils.getConnection();
                //将连接加入到连接池中
                dataSources.add(connection);
            } catch (Exception e) {
                e.printStackTrace();
            }
            
        }
    }

    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    public void setLoginTimeout(int seconds) throws SQLException {

    }

    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
    
    /**
     * 添加一个方法,用于归还连接
     * @param conn
     */
    public void releaseConnection(Connection conn){
        dataSources.add(conn);
        System.out.println("将连放回到连接池中,数量" + dataSources.size());
    }

    public Connection getConnection() throws SQLException {
        //取出连接池中的一个连接
        Connection connection = dataSources.removeFirst();//删除第一个连接并返回
        System.out.println("取出一个连接,剩余" + dataSources.size() + "个连接");
        return connection;
    }

    public Connection getConnection(String username, String password)
            throws SQLException {
        return null;
    }
}

测试类中最后就应该调用你刚刚添加的方法,运行测试类


在这里插入图片描述

结果很明显,目的达到了。
这样就实现了一个简易的数据库连接池,但该程序其实有一个很不好的地方,因为如果想要将连接放回连接池而不是关闭它,就得调用在MyDataSource类中自己添加的方法,而很多人或许已经习惯了JDBC的编程,会很习惯地去调用close()方法,而一旦调用了close()方法,连接就被关闭了,不会放回连接池了。所以你就需要通知用户去主动使用你添加的API,这种方法其实是不可取的。那有什么办法能够让调用者随着自己的编程习惯去调用close()方法的同时,还能够将连接放回连接池而不是关闭它呢?
我们可以去修改close()方法原来的逻辑,怎么修改呢?
在Java中有三种方法可以增强原有的方法

  • 类继承 、方法覆盖
    必须控制对象创建,才能使用该方式

  • 装饰者模式方法加强
    必须和目标对象实现相同接口或继续相同父类,特殊构造器(传入被包装对象)

  • 动态代理

有关方法增强的问题,可以参考我的这篇博客。
理解Java方法增强
熟悉方法增强的小伙伴们可以忽略哈。(手动滑稽)

我们使用动态代理来对Connection接口的run()方法进行增强。
修改MyDataSource类中的代码

/**
 * 自定义连接池
 * 
 * 一次性地创建多个连接
 * 
 * 实现getConnection方法,从连接池获得一个连接
 * 
 * 当用户使用连接后,提供方法将连接放回到连接池中
 * 
 * @author Administrator
 * 
 */
public class MyDataSource implements DataSource {

    private LinkedList<Connection> dataSources = new LinkedList<Connection>();

    public MyDataSource() {
        // 一次性创建10个连接
        for (int i = 0; i < 10; i++) {
            try {
                Connection connection = JDBCUtils.getConnection();
                // 将连接加入到连接池中
                dataSources.add(connection);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }

    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    public void setLoginTimeout(int seconds) throws SQLException {

    }

    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }

    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    /**
     * 添加一个方法,用于归还连接
     * 
     * @param conn
     */
    public void releaseConnection(Connection conn) {
        dataSources.add(conn);
        System.out.println("将连放回到连接池中,数量" + dataSources.size());
    }

    public Connection getConnection() throws SQLException {
        // 取出连接池中的一个连接
        final Connection connection = dataSources.removeFirst();// 删除第一个连接并返回
        System.out.println("取出一个连接,剩余" + dataSources.size() + "个连接");
        // 将目标connection对象进行增强
        Connection connProxy = (Connection) Proxy.newProxyInstance(connection
                .getClass().getClassLoader(), connection.getClass()
                .getInterfaces(), new InvocationHandler() {

            //执行代理对象的任何方法都将执行invoke方法
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                //只需要增强close方法
                if(method.getName().equals("close")){
                    //需要增强的方法
                    //不将连接真正关闭,将连接放回连接池
                    releaseConnection(connection);
                    return null;
                }else{
                    //不需要增强的方法,保持方法原有的功能即可
                    return method.invoke(connection, args);
                }
            }
        });
        return connProxy;
    }

    public Connection getConnection(String username, String password)
            throws SQLException {
        return null;
    }
}

回到测试类,此时调用Connection接口中的close()方法,然后执行


在这里插入图片描述

方法成功增强了。调用者在调用close()方法后并不会把连接关闭了,而是放回连接池中。由此我们的想法便实现了。

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

推荐阅读更多精彩内容