数据库连接池

142 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情

一、数据库连接池概念

提出的背景: 数据库连接是一种关键的、有限的、昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标。数据库连接池正是针对这个问题提出来的。

作用: 数据库连接伺候负责分配、管理和释放数据库连接,应用程序可以重复使用一个现有的数据库连接。释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而产生的数据库连接遗漏,大幅的提高了对数据路操作的性能。

二、归还连接的方式

  • 自定义数据连接池:装饰器方式适配器方式动态代理方式
  • 开源数据连接池:C3P0连接池Druid连接池

装饰器设计模式

通过装饰器设计模式自定义Connection类,,实现和mysql驱动包中Connection相同的功能。

package com.work;

import java.sql.*;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

/*
    1.定义实现Connection接口的类
    2.定义成员变量如连接对象和连接池容器对象
    3.使用有参构造方法为连接对象和连接池容器对象赋值
    4.重写close方法将释放资源改为归还连接
    5.剩余方法实现可以调用连接对象即可。
 */
//1.定义实现Connection接口的类
public class MyConnection1 implements Connection{
    //2. 定义成员变量如连接对象和连接池容器对象
    private Connection con;
    private List<Connection> pool;

    //3.使用有参构造方法为连接对象和连接池容器对象赋值
    public MyConnection1(Connection con,List<Connection> pool) {
        this.con = con;
        this.pool = pool;
    }

    //4.重写close方法将释放资源改为归还连接
    @Override
    public void close() throws SQLException {
        pool.add(con);
    }
}
  • 自定义连接池类
package com.work;

import javax.sql.DataSource;
import javax.sql.rowset.RowSetWarning;
import java.io.PrintWriter;
import java.sql.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;


/*
   自定义连接池类
*/
public class MyDataSoure implements DataSource {

    // 定义集合容器
    private static List<Connection> pool = Collections.synchronizedList(new ArrayList<Connection>());

    // 静态代码块, 生成数据库连接保存到集合中
    static {
        for (int i = 0; i < 5; i++) {
            Connection connection = JDBCUtils.getConnection();
            pool.add(connection);
        }
    }

    //返回连接池的大小
    public int getSize() {
        return  pool.size();
    }

    @Override
    public Connection getConnection() throws SQLException {
        if (pool.size() > 0) {
            Connection con = pool.remove(0);
            MyConnection1 myConnection1 = new MyConnection1(con, pool);
            return myConnection1;
        } else {
            throw new RuntimeException("连接数量已用尽");
        }
    }

    @Override
    .....
}

  • 测试类
package com.work;

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

public class MyDataSoureTest {
    public static void main(String[] args) throws SQLException {
        // 连接池对象
        MyDataSoure dataSource = new MyDataSoure();

        System.out.println("使用前数据连接池的数量" + dataSource.getSize());

        // 获取数据路连接对象
        Connection connection = dataSource.getConnection();

        // 查询数据库
        String sql = "SELECT * FROM student";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        ResultSet resultSet = preparedStatement.executeQuery();

        // 结果输出
        while (resultSet.next()) {
            System.out.println(resultSet.getInt("sid"));
        }

        // 关闭资源
        resultSet.close();
        preparedStatement.close();
        connection.close();

        System.out.println("使用之后连接池数量:" + dataSource.getSize());

    }
}
  • 运行结果

image.png

适配器设计模式

  • 定义Connection接口的抽象适配器
package com.work;

import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

/*
* 1. 定义一个实现Connection接口的抽象适配器类
* 2. 定义连接对象Connection的变量
* 3. 使用有参构造为变量赋值
*
* */
// 定义一个实现Connection接口的抽象适配器类

public abstract class MyAdapter implements Connection {

//   2. 定义连接对象Connection的变量
    private Connection con;

//  3. 使用有参构造为变量赋值
    public MyAdapter(Connection con) {
        this.con = con;
    }
    
    
    @Override
    ......

}

  • 定义MyConnection2继承适配器类
package com.work;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/*
* 1. 定义MyConnection2继承适配器类
* 2. 定义连接对象Connection和连接池容器List的变量
* 3. 给连接对象和连接池容器赋值
* 4. 重写close方法
*
* */
//1. 定义MyConnection2继承适配器类
public class MyConnection2 extends MyAdapter {
    // 2. 定义连接对象Connection和连接池容器List的变量
    private Connection con;
    private List<Connection> pool;

    public MyConnection2(Connection con, List<Connection> pool) {
        super(con);
        this.con = con;
        this.pool = pool;
    }

    //4. 重写close方法
    @Override
    public void close() {
        pool.add(con);
    }
}

  • 连接池修改
    @Override
    public Connection getConnection() throws SQLException {
        if (pool.size() > 0) {
            Connection con = pool.remove(0);
            MyConnection2 myConnection2 = new MyConnection2(con, pool);
            return myConnection2;
        } else {
            throw new RuntimeException("连接数量已用尽");
        }
    }

动态代理方式

动态代理主要用来做方法的增强,在不修改源码的前提下增强一些方法。在invocationHandlerinvoke方法中,可以直接获取正在调用方法的对应Method对象。

-newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 用于动态生成代理类。

参数:
loader: 类加载器
interfaces: 动态代理类需要实现的接口
h: 动态代理执行时,具体的方法调用

// InvocationHandler(Object proxy, Method method, Object[] args)
proxy: 返回对象
method:调用的方法
args:方法中的参数

/*
动态代理方式
*/
@Override
public Connection getConnection() throws SQLException {
        if(pool.size() > 0) {
            Connection con = pool.remove(0);
            Connection proxyCon = (Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(), new Class[]{Connection.class}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    if(method.getName().equals("close")) {
                        pool.add(con);
                        return null;
                    }else {
                        return method.invoke(con,args);
                    }
                }
            });
            return proxyCon;
        } else {
            throw new RuntimeException("连接的数据已经用尽");
        }
    }

C3P0使用

C3P0地址

  • 导入c3p0-0.9.5.2.jar和 mchange-commons-java-0.2.11.jar两个jar包
  • src目录下配置c3p0-config.xml
// 配置内容有连接参数的设置如driverClass、jdbcUrl、user、password
// 连接池参数:initialPoolSize(初始化连接池数量)maxPoolSize(最大连接池数量) checkoutTimeout(连接时长)
<c3p0-config>
  <!-- 使用默认的配置读取连接池对象 -->
  <default-config>
   <!--  连接参数 -->
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://192.168.xx.xxx:3306/db</property>
    <property name="user">root</property>
    <property name="password">xxxxx</property>
    
    <!-- 连接池参数 -->
    <property name="initialPoolSize">5</property>
    <property name="maxPoolSize">10</property>
    <property name="checkoutTimeout">3000</property>
  </default-config>
   
   // 自定义配置
  <named-config name="otherc3p0"> 
    <!--  连接参数 -->
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/db1</property>
    <property name="user">root</property>
    <property name="password">xxxxx</property>
    
    <!-- 连接池参数 -->
    <property name="initialPoolSize">5</property>
    <property name="maxPoolSize">8</property>
    <property name="checkoutTimeout">1000</property>
  </named-config>
</c3p0-config>
  • 使用方式
import com.mchange.v2.c3p0.ComboPooledDataSource;

ComboPooledDataSource dataSource = new ComboPooledDataSource();

Druid连接池

Druid连接池是阿里巴巴开源的数据库连接池项目。Druid连接池为监控而生,内置强大的监控功能,监控特性不影响性能。功能强大,能防SQL注入,内置Loging能诊断Hack应用行为。

  • 带入druid-1.0.9.jar并使用
  • src目录下配置文件druid.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.xx.xxx:3306/db
username=root
password=xxxx
initialSize=5
maxActive=10
maxWait=3000
  • 使用
//获取配置文件的流对象
InputStream is = DruidTest1.class.getClassLoader().getResourceAsStream("druid.properties");

//通过Properties集合,加载配置文件
Properties prop = new Properties();
prop.load(is);

//通过Druid连接池工厂类获取数据库连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);

//通过连接池对象获取数据库连接进行使用
Connection con = dataSource.getConnection();