对Druid德鲁伊连接池的分析及实现

1.为什么需要连接池

在这里插入图片描述
当前的jdbc程序每次访问数据库都需要创建一个新的连接,访问完毕之后,还需要释放资源。
注意:connection的创建与销毁所消耗的资源远远大于数据库执行sql消耗的资源。基于这种实际情况,当前jdbc实现方式有如下弊端:
1.一次访问,就创建一次connection,降低程序的性能
2.connection的创建与销毁所消耗的资源远远大于数据库执行sql消耗的资源
3.频繁的连接数据会导致资源消耗过多,导致数据库宕机

思考上面的结构,大部分的时间浪费在了创建和销毁connection上。那么我们能不能实现将这些connection回收利用呢?这样我们就不需要不停的对connection进行创建和销毁了。只需要创建一次,放在指定的地方。而我们使用的时候,直接从里面拿就行了。用完放回原来的地方。不去销毁,当我再次使用的时候,去拿就行了。这样的解决方案就是我们需要的。
在这里插入图片描述
实现原理:一次性创建多个连接,将多个连接缓存在内存中 ,形成数据库连接池,如果应用程序需要操作数据库,只需要从连接池中获取一个连接,使用后,并不需要关闭连接,只需要将连接放回到连接池中。

连接池的好处:节省创建连接与释放连接的性能消耗,连接池中连接起到复用的作用 ,提高程序性能。

2.自定义数据库连接池案例

package 自己的包名;

import JdbcUtil连接工具类;
import com.mysql.jdbc.ConnectionFeatureNotAvailableException;

import javax.sql.DataSource;
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.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.logging.Logger;

public class MyDatasource implements DataSource{

    //创建集合对象作为数据库连接池
    LinkedList<Connection> list=new LinkedList<Connection>();
    //定义一个构造方法,在创建这个类的对象时给连接池中存放连接
    public MyDatasource() throws SQLException {
        //循环决定连接池的连接数
        for (int i = 0; i < 5; i++) {
        //使用jdbc原始方法进行获取连接,将其封装为JDBCUtils工具类
            Connection conn =JDBCUtils.getConnection();
            //添加到集合中
            list.addLast(conn);
        }
        System.out.println("初始化连接池成功,连接池的大小是:"+list.size());
    }
    //从连接池获取连接
    @Override
    public Connection getConnection() throws SQLException {
        //从连接池即集合中获取连接,那么连接也不会存在于集合中了
        System.out.println("从池子中获取连接");
        final Connection conn = list.removeLast();
        /*Connection conn = list.removeFirst();
        System.out.println("从连接池中获取连接,连接池的大小是:"+list.size());*/
        //采用动态代理,将connection的close方法进行拦截,将其功能改为放回连接池,这是数据源的功能实现的核心。(关于动态代理,会有一篇博客进行介绍及简单实现)
        /*
        * 第一个参数:被代理对象的类加载器。
        * 第二个参数:被代理对象的实现的接口。
        * 第三个参数:代理类需要做的工作。
        * */
        Connection proxyConn= (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(),
                new Class[]{com.mysql.jdbc.Connection.class}, new InvocationHandler() {

                    /*
                    * 第一个参数:代理对象本身
                    * 第二个参数:被代理类的方法
                    * 第三个参数:arg1方法执行所需要的参数
                    * */
                    @Override
                    public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
                        if ("close".equals(arg1.getName())){
                            System.out.println("改写close方法");
                            //只改写close方法
                            //将conn放到连接池就行了。
                            list.add(conn);
                            System.out.println("此时池子中还有"+list.size()+"个连接");
                            return null;//close 方法被代理放回容器后,不应该继续被执行
                        }else{
                            //保持原有方法的功能
                            return arg1.invoke(conn,arg2);
                        }
                    }
                });
            return proxyConn;
    }

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

    //定义方法将连接还给连接池
    public void backPool(Connection conn){
        list.addLast(conn);
        System.out.println("将连接还给连接池,连接池的大小是:"+list.size());
    }


    @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;
    }
}

看实现可以知道,自定义数据连接池仅能实现很少一部分功能,所以我们可以使用当前市面上开源的连接池进行开发,功能更加强大,安全性能更加高效。

3. 什么是德鲁伊Druid连接池

3.1.DRUID简介

Druid是阿里巴巴开发的号称为监控而生的数据库连接池(可以监控访问数据库的性能),Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。如:一年一度的双十一活动,每年春运的抢火车票。
Druid的下载地址:https://github.com/alibaba/druid
DRUID连接池使用的jar包:druid-1.0.9.jar
在这里插入图片描述

3.2.DRUID常用的配置参数

参数 说明
url 数据库连接字符串 jdbc:mysql://localhost:3306/数据库名
username 数据库的用户名
password 数据库的密码
driverClassName 驱动类名。根据url自动识别,这一项可配可不配,如果不配置druid会根据url自动识别数据库的类型,然后选择相应的数据库驱动名
initialSize 初始化时建立的物理连接的个数。初始化发生在显式调用init方法,或者第一次获取连接对象时
maxActive 连接池中最大连接数
maxWait 获取连接时最长等待时间,单位是毫秒。

3.3.DRUID连接池基本使用

3.3.1.API介绍

核心类:DruidDataSourceFactory
获取数据源的方法:使用DruidDataSourceFactory类中的静态方法:
public static DataSource createDataSource(Properties properties):读取属性配置文件,创建一个数据源对象。
配置信息在properties属性对象中。

3.3.2.使用步骤

1)导入核心包druid-1.0.9.jar
在这里插入图片描述
2)在项目下创建一个properties文件,文件名随意,设置对应参数
在这里插入图片描述
3)加载properties文件的内容到Properties对象中
4)创建DRUID连接池,使用配置文件中的参数
5)从DRUID连接池中取出连接
6)执行SQL语句
7)关闭资源

3.3.3 配置文件

属性文件:在项目下新建一个druid配置文件,命名为:druid.properties

数据库连接参数

url=jdbc:mysql://localhost:3306/day05
username=root
password=123
driverClassName=com.mysql.jdbc.Driver

连接池的参数

initialSize=3
maxActive=10
maxWait=2000

4.德鲁伊Druid手动实现案例

package com.c3p0;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.jdbc_01.JDBCUtils;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

public class Demo_druid {
    public static void main(String[] args) throws Exception {
        //加载properties 文件的内容到Properties 对象中
        Properties info = new Properties();
        //加载项目下的属性文件
        FileInputStream fis = new FileInputStream("druiddb.properties");
        //从输入流中加载属性
        info.load(fis);
        //System.out.println(info);
        //创建DRUID连接池,使用配置文件中的参数
        DataSource dataSource = DruidDataSourceFactory.createDataSource(info);
        //从DRUID连接池中取出连接
        //Connection conn=dataSource.getConnection();
        //System.out.println("conn = " + conn);
        //需求:根据用户名和密码 查询用户信息
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs=null;

        try{
            //获得连接
            conn=dataSource.getConnection();
            //获得发送sql的对象
            String sql="select * from user where username=? and password=?";
            pstmt=conn.prepareStatement(sql);
            //如果有问号,需要设置参数,注意:下标从1开始
            pstmt.setString(1,"zhangsan");
            pstmt.setString(2,"abcdef");
            //执行sql获取结果
            rs=pstmt.executeQuery();
            //处理结果
            if (rs.next()){
                int id=rs.getInt("id");
                String username=rs.getString("username");
                String pwd=rs.getString("password");

                System.out.println(id+":::"+username+"==="+pwd);
            }else {
                System.out.println("没有查到对应的用户信息!");
            }
        }catch (Exception e){
            throw new RuntimeException(e);
        }finally {
            JDBCUtils.release(conn,pstmt,rs);
        }
    }
}

依据自身情况,对应实现。

5.封装DruidUtil工具类

package com.c3p0;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class DruidJdbcUtil {
    static DruidDataSource dataSource;
    static {
        Properties properties = new Properties();
        try{
            properties.load(new FileInputStream("druiddb.properties"));
            dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //获取数据源
    public static DataSource getDataSource(){
        return dataSource;
    }

    //获取连接
    public static Connection getConnection(){

        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

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

小结:对于C3P0与Druid连接池的比较分析:

1.从配置文件上讲:
C3P0配置文件:c3p0-config.xml 规定命名,而且指定了路径存放,如果路径有误则配置失效。优势:可以自动读取,无需配置
Druid配置文件:druid.properties ,除了需要.properties文件类型外,对命名没有要求,对于配置文件存放位置也没有要求。可以随意放置。 不足之处:需要开发人员去指定文件路径,并进行加载,将加载内容直接丢给德鲁伊框架,框架会自动解析。
2. 从执行效率上讲:
个人认为Druid数据库连接池执行效率会略微高一些,故本文以介绍druid连接池为主,C3P0连接池实现方式类似。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值