一条大河波浪宽 -- 数据库连接池实现

阅读文本大概需要 8 分钟。

度过了一个轻松的周末,让我们收拾好行囊,继续数据库航行之路 。

先简单回顾一下上次我们提到的JDBCUtil 工具类,通过抽取的 getConnection 和 release 方法实现,我们现在可以方便的获取以及释放数据库连接资源了。

可是 这还够,还不够…

新的问题:

创建一个 Connection,发出一个查询,处理完 ResultSet 后,立刻就把Connection 给关掉了。这在我们简单的单用户 CS 程序,是没有问题的。可现在的 Java 程序往往是 BS 的 Web 应用,需要处理的是大量来自不同用户的请求。

用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。

假设网站一天10万访问量,数据库服务器就需要创建关闭10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、拓机

那么最好的做法是,准备出一个空间,此空间里专门保存着数据库连接。以后用户要用数据库操作的时候,不用再重新加载驱动、连接数据库之类的,而直接从此空间中获取 Connection 连接。关闭的时候,直接把连接放回到此空间之中。

这个空间就可以称为连接池,而连接池存在的意义,就是提高获取链接的效率。

模拟连接池

准备一个存放对象的池子(容器) 最开始初始化一些连接到池子中 获取已有的连接 用完后换回池中

简单实现:

public class SimpleConnectionPool {
    //存放链接对象的池
    private static List<Connection> pool = Collections.synchronizedList(new LinkedList<Connection>());
    
    //最开始初始化一些链接到池中
    static{
        for(int i=0;i<10;i++){
            Connection conn = JdbcUtil.getConnection();
            pool.add(conn);
        }
    }
    
    //从池中获取一个链接
    public static Connection getConnection(){
        if(pool.size()>0){
            return pool.remove(0);
        }else{
            throw new RuntimeException("数据库连接用完了...");
        }
    }
    
    //用完后还回池中
    public static void release(Connection conn){
        pool.add(conn);
    }
}

使用连接池:

// 从连接池获取连接
Connection conn = SimpleConnectionPool.getConnection();

// ... 数据库操作 ....


// 还回连接池
SimpleConnectionPool.release(conn);

标准的数据源

现在我们,有了保存所有的数据库连接的连接池,但是如果要想真正有效的使用数据库连接池空间的话,还有一些问题要考虑

1、 如果没有任何一个用户使用连接,那么那么应该维持一定数量的连接,等待用户使用。 2、 如果连接已经满了,则必须打开新的连接,供更多用户使用。 3、 如果一个服务器就只能有60个连接,那么如果有第61个人过来呢?应该等待其他用户释放连接 4、 如果一个用户等待时间太长了,则应该告诉用户,操作是失败的。

如果直接用程序实现以上功能 会比较麻烦,所以 java 为数据库连接池提供了公共的接口: javax.sql.DataSource 要求各个厂商的连接池必须实现该接口,这样应用程序就能方便的切换不同厂商的连接池。

每一个连接通过 DataSource 获取,并规定清楚了:

初始连接数 最小空闲连接数 创建连接的最小增量 最大空闲连接数 最大连接数 最长等待时间 … …

DataSource 被绑定在了JNDI 上(为每一个DataSource提供一个名字)客户端通过名称找到在 JNDI 上绑定的DataSource,再由DataSource找到一个连接。

还有一个小问题

从池中获取一个链接后,用户用完自觉的调用 conn.close() 方法。应该达到的效果:不要关闭,而应该还回池中

这里获取到的 com.mysql.jdbc.Connection.close() 只会直接被关闭了,是无法还回到 连接池中的。

解决方案:

1、静态代理

自己编写一个类,实现 Connection 接口。对于不需要改写的方法,调用被装饰对象的。( 装饰者 l 默认适配器)。对于要改写的 close() 方法,改写即可。

public class MyConnection extends ConnectionWrapper{
    private Connection conn;
    private List<Connection> pool;
    
    public MyConnection(Connection conn,List<Connection> pool) {
        super(conn);
        this.conn = conn;
        this.pool = pool;
    }
    
    public void close() throws SQLException {
        pool.add(conn);
    }
}

2、动态代理

基于接口的动态代理:被代理对象的类,实现了至少一个接口

通过 Proxy 类反射,重新提供 close 方法。 ClassLoadder loader:类加载器。固定写法:和被代理人用相同的类加载器即可。 Interface[] interfaces:代理对象要实现的接口。固定写法:和被代理人相同即可。 InvocationHandler h:接口。如何代理。策略设计模式。

public Connection getConnection() throws SQLException {

        if(pool.size()>0){
            final Connection conn =  pool.remove(0);//数据库驱动的
            Connection proxyConn = (Connection)Proxy.newProxyInstance(conn.getClass().getClassLoader(), 
                    conn.getClass().getInterfaces(), new InvocationHandler() {
                        public Object invoke(Object proxy, Method method, Object[] args)
                                throws Throwable {
                        
                            if("close".equals(method.getName())){                            
                                //用户调用的是close方法:还回池中
                                return pool.add(conn);
                                
                            }else{            
                                //调用原来对象的对应方法
                                return method.invoke(conn, args);
                            }
                        }
                    });
            return proxyConn;
        }else{
            throw new RuntimeException("数据库连接用完了...");
        }
        
    }

今日总结:

连接池 是用来管理 Connection 以便于 Connection 能够复用。 有了连接池,我们就不用每次都来创建 Connection,而是通过连接池来获取 Connection 对象。 当使用完 Connection 之后,调用 close() 方法,也不会真正的关闭连接,而是将 Connection ”归还”给连接池。 连接池就能够再次利用这个 Connection. 而无需重新创建,从而节省了系统资源、优化了性能。

原文发布于微信公众号 - 程序员阿凯(AKBC159)

原文发表时间:2018-06-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java学习

数据库连接池C3P0,DBCP教程详解示例

连接池 实际开发中“获得连接”或“释放资源”是非常消耗系统资源的两个过程,为了解决此类性能问题,通常情况我们采用连接池技术,来共享连接Connection。...

4566
来自专栏Java学习123

IBM WebSphere MQ 系列(四) 使用MQ命令

3946
来自专栏我是攻城师

从App的角度看进程和线程

在现在人人都有一部手机或电脑的年代,我们几乎天天都在使用各种app,如微信,QQ,抖音,优酷等等软件,表面上我们是与各种app交互,但如果站在操作系统的角度来看...

422
来自专栏dotnet core相关

WCF入门(10)

公司是做乙方的,工资还凑合,主要是项目基本都已完成,进去就是干维护,体会不到那种从头彻尾的成就感。项目中具体用了EF+Ado.net+WCF+WPF+(VB.n...

482
来自专栏Android知识点总结

1--安卓网络编程之获取IP地址

742
来自专栏JetpropelledSnake

Django学习笔记之利用Form和Ajax实现注册功能

1265
来自专栏Golang语言社区

Gotorch - 多机定时任务管理系统

题图 by wahno from Instagram 前言 最近在学习 Go 语言,遵循着 “学一门语言最好的方式是使用它” 的理念,想着用 Go 来实现些什么...

3598
来自专栏Golang语言社区

居于H5的多文件、大文件、多线程上传解决方案

文件上传在web应用中是比较常见的功能,前段时间做了一个多文件、大文件、多线程文件上传的功能,使用效果还不错,总结分享下。 一、 功能性需求与非功能性需求 要求...

3778
来自专栏JackieZheng

探秘Tomcat——启动篇

tomcat作为一款web服务器本身很复杂,代码量也很大,但是模块化很强,最核心的模块还是连接器Connector和容器Container。具体请看下图: ? ...

3747
来自专栏SDNLAB

Open vSwitch源码阅读笔记(下)

引言 本文主要对OpenvSwitch(基于2.3.90版本)重点模块的源码实现流程做了简要的阅读记录,Open vSwitch源码阅读笔记(上)已提供,此篇是...

3096

扫码关注云+社区