JDBC数据库连接池的简单实现

一、为什么使用数据库连接池

 用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源。所以我们需要使用连接池技术来进行改进。

二、数据库连接池的特点

 数据库连接池与普通的数据库连接的区别就是:普通的数据库在连接数据库是及时的重新创建一个连接,而数据库连接池是当一个服务器启动时,他就在启动的时候,创建了一些连接 (最小连接数) 保持与数据库的连接,当有用户访问服务器时,不是及时的重新创建一个新的连接,而是首先看我们创建的数据库连接池中是否还有连接,如果有的话,那么就直接那到这个连接来使用,当使用完之后,不是把这个连接关掉,而是把使用的连接,再放回到数据库连接池中。如果发现数据库连接池中没有连接了,那么才会申请去创建一个新的连接。当然我们不可能无止尽的创建连接,我们设置一个最大的连接数来控制,当达到最大连接数的时候,我们就必须等待直到有连接被返回到数据库连接池中。

数据库最大连接数与最小连接数的设置要考虑的因素:

  1. 最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会有大量的数据库连接资源被浪费.
  2. 最大连接数:是连接池能申请的最大连接数,如果数据库连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作
  3. 如果最小连接数与最大连接数相差很大:那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接.不过,这些大于最小连接数的数据库连接在使用完不会马上被释放,他将被放到连接池中等待重复使用或是空间超时后被释放.

三、编写数据库连接池:

1、编写MyDataSource
 

package datasource;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;

public class MyDataSource {
    private static String url="jdbc:mysql://localhost:3306/students?useSSL=false";//jdbc:mysql://localhost:3306/students?useSSL=false
    private static String user="root";
    private  static String password="158283";
    private static int currentCounts=0;//当前的连接数
    private static int minCounts=5;
    private static int maxCounts=10;
    //使用LinkedList的原因,由于要频繁的读写操作,所以这里使用LinkedList来操作,提高效率
    private static LinkedList<Connection> connectionsPool=new LinkedList<Connection>();
    public MyDataSource(){//构造函数中来向池中添加连接
        try{
        for(int i=0;i<minCounts;i++){
            currentCounts++;//创建一个就要自增当前连接数
            this.connectionsPool.addLast(this.creatConnection());
        }
        }catch(SQLException e){
            e.printStackTrace();
        }

    }

    public Connection getConnection() throws SQLException {
        //加锁为了防止获取连接的时候发生异常 但是这也会减低效率
        synchronized (this.getClass()) {
            if(connectionsPool.size()>0)
            return connectionsPool.removeFirst();

            if(currentCounts<maxCounts) {
                currentCounts++;
                return this.creatConnection();
            }
            throw new SQLException("没有链接了");
        }

    }
    public Connection creatConnection() throws SQLException {
        return DriverManager.getConnection(url,user,password);
    }
    public void free(Connection conn){
        connectionsPool.addLast(conn);//放回到连接池中
        currentCounts--;//当前连接数减一
    }
}
/*
使用这种模式,是用在网络上服务器开启的时候加载n个链接,在使用jdbc工具中获取链接的时候,使用这个类来创建数据库的链接,
这个类的设计思想主要是利用linkedlist<Connection>链表的结构来存取这些已经创建好的链接,并采用先进先出的方法来存取,
当预先创建的链接已经用完的时候,根据这个数据库同时连接的最大用户量来creatconnection()新的链接,当创建的链接数大于数据库
的最大连接数时,抛出异常,用这种模式,就不需要释放链接资源,而是把使用removerfrist()方法的获取链接资源通过add Last()方法
再重新放进来,记住在获取链接的时候要加上并发控制来控制异常的发生。其实就是吧创建资源的任务从jdbc中接过来。
*/

2、编写JdbcUtils

package JDBCUP;

import datasource.MyDataSource;

import java.sql.*;

public final class JDBCTools {
    private JDBCTools(){}

    private static String url="jdbc:mysql://localhost:3306/students?useSSL=false";//jdbc:mysql://localhost:3306/students?useSSL=false
    private static String user="root";
    private  static String password="158283";
    private static MyDataSource mds=null;
    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            mds=new MyDataSource();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static Connection getConn(){
        try {
            return mds.getConnection();//这里就不是直接创建数据库连接,而是去数据库连接池中获取链接欸
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void free(ResultSet rs, PreparedStatement ps,Connection conn){
        try {
            if(rs!=null)
                rs.close();
            }
            catch (SQLException e) {
                e.printStackTrace();
            }
            finally {
                try{
                    if(ps!=null)
                        ps.close();
                }
                catch (SQLException e){
                    e.printStackTrace();
                }
                finally {
                    try {
                        if(conn!=null)
                          mds.free(conn);//注意这里就不能关闭数据库连接了,而是应该把连接返回到连接池中
                    }
                    catch (Exception s){
                        s.printStackTrace();
                    }

                }

            }
    }
}

3、测试:

package JDBCUP;

import java.sql.Connection;

public class Base {
    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            Connection conn=JDBCTools.getConn();
            System.out.println(conn);
            JDBCTools.free(null,null,conn);
        }
    }
}

  测试结果。

在这个数据库连接池中,有一个地方让我们容易犯错误,没错就是我们关闭数据库连接的时候,这里是调用MyDataSource中的free()来进行连接的放回,但是我们可能习惯性的使用close()方法,这样就关闭了数据库连接,所以我们需要使用动态代理的方法来防止这样的问题发生。

四、使用动态代理模式来实现昂数据库连接池.

在上面的代码中我们可以发现,在我们把数据库连接资源返回到池中,我们可能因为不知道规范,导致我们使用了close()的方法来关闭掉了数据库资源,而非放回到数据库连接池。所以这里我们使用动态代理的设计模式来完成。

1、编写我们的MyDataSourceProxy

package datasource;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;

public class MyDataSourceProxy {
    private static String url="jdbc:mysql://localhost:3306/students?useSSL=false";//jdbc:mysql://localhost:3306/students?useSSL=false
    private static String user="root";
    private  static String password="158283";
    int currentCounts=0;
    private static int maxCounts=10;
     LinkedList<Connection> connectionsPool=new LinkedList<Connection>();//理解为什么这里的泛型是Connection。
    public MyDataSourceProxy(){
        try{
            for(int i=0;i<5;i++){
                currentCounts++;
                this.connectionsPool.addLast(this.creatConnection());
            }
        }catch(SQLException e){
            e.printStackTrace();
        }

    }

    public Connection getConnection() throws SQLException {
        synchronized (this.getClass()) {
            if(connectionsPool.size()>0)
                return connectionsPool.removeFirst();

            if(currentCounts<maxCounts) {
                currentCounts++;
                return this.creatConnection();
            }
            throw new SQLException("没有链接了");
        }

    }
    public Connection creatConnection() throws SQLException {//只有这里和上面的代码不同
       Connection realConn=DriverManager.getConnection(url,user,password);
       MyConnectionHandler myConnectionHandler=new MyConnectionHandler(this);
       return myConnectionHandler.bind(realConn);
    }

}

2、编写我们的动态代理的代码

package datasource;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;

public class MyConnectionHandler implements InvocationHandler {
    private Connection realConn;
    private Connection wrapedConnection;
    private MyDataSourceProxy dataSourceProxy;
    public MyConnectionHandler(MyDataSourceProxy dataSourceProxy){
        this.dataSourceProxy=dataSourceProxy;

    }
    Connection bind(Connection realConn){
        this.realConn=realConn;
        this.wrapedConnection=(Connection)Proxy.newProxyInstance(this.getClass().getClassLoader(),new Class[]{Connection.class},this);
        return wrapedConnection;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(method.getName().equals("close")){
            dataSourceProxy.connectionsPool.addLast(wrapedConnection);
            dataSourceProxy.currentCounts--;
        }
        return method.invoke(realConn,args);
    }
}

3、JDBCUtils的改变

package JDBCUP;

import datasource.MyDataSourceProxy;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public final class JDBCTools {
    private JDBCTools(){}

    private static String url="jdbc:mysql://localhost:3306/students?useSSL=false";
    
    private static String user="root";
    private  static String password="158283";
    private static MyDataSourceProxy mds=null;
    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            mds=new MyDataSourceProxy();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static Connection getConn(){
        try {
            return mds.getConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void free(ResultSet rs, PreparedStatement ps,Connection conn){
        try {
            if(rs!=null)
                rs.close();
            }
            catch (SQLException e) {
                e.printStackTrace();
            }
            finally {
                try{
                    if(ps!=null)
                        ps.close();
                }
                catch (SQLException e){
                    e.printStackTrace();
                }
                finally {
                    try {
                        if(conn!=null)
                          conn.close();//这里我们就可以直接使用close()来关闭了,因为我们在调用这个方法后,会先走MyConnectionHandler中的invoke()方法,我们对close的方法进行拦截,更改close的方法
                    catch (Exception s){
                        s.printStackTrace();
                    }

                }

            }
    }
}

tips:这里主要使用了JDK动态代理的设计模式,简化了我们的代码。我们如果不适用动态代理设计模式,而是是哟个静态代理设计模式的话,那么它的问题就是我们必须自己俩编写调用connection的方法,动态代理我们可以理解为,java在帮我们写了代码,而不是我们写代码,因为有很多时候,我们只需要修改其中的某几个方法。这种设计模式在Spring中的AOP特别常见。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值