数据库连接池

时间:2021-09-07 13:19:57

1、什么是数据库连接池

传统的开发模式下,Servlet处理用户的请求,找Dao查询数据,dao会创建与数据库之间的链接,完成数据查询后会关闭数据库的链接。

这样的方式会导致用户每次请求都要向数据库建立链接而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、宕机。

解决方案: 就是数据库连接池

连接池就是数据库连接对象的一个缓冲池.

用池来管理Connection,这可以重复使用Connection。有了池,所以我们就不用自己来创建Connection,而是通过池来获取Connection对象。当使用完Connection后,

调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。池就可以再利用这个Connection对象了。

 

池参数(所有池参数都有默认值)

初始大小:10个

最小空闲连接数:3个

增量:一次创建的最小单位(5个)

最大空闲连接数:12个

最大连接数:20个

最大的等待时间:1000毫秒

 

四大连接参数

连接池也是使用四大连接参数来完成创建连接对象!

 

实现的接口

连接池必须实现:javax.sql.DataSource接口!

连接池返回的Connection对象,它的close()方法与众不同!调用它的close()不是关闭,而是把连接归还给池!

 

2、jdbc数据库连接池接口(DataSource)

Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商可以让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!

 

3、自定义连接池

   

如何对Connection对象,生成一个代理对象:

|--Proxy

         static Object newProxyInstance(

      ClassLoader loader,    当前使用的类加载器

      Class<?>[] interfaces,   目标对象(Connection)实现的接口类型

      InvocationHandler h    事件处理器:当执行上面接口中的方法的时候,就会自动触发事件处理器代码,把当前执行的方法(method)作为参数传入。

/**
* 自定义连接池, 管理连接
* 代码实现:
1. MyPool.java 连接池类,
2. 指定全局参数: 初始化数目、最大连接数、当前连接、 连接池集合
3. 构造函数:循环创建3个连接
4. 写一个创建连接的方法
5. 获取连接
------> 判断: 池中有连接, 直接拿
------> 池中没有连接,
------> 判断,是否达到最大连接数; 达到,抛出异常;没有达到最大连接数,
创建新的连接
6. 释放连接
-------> 连接放回集合中(..)
*
*/
public class MyPool {

private int init_count = 3; // 初始化连接数目
private int max_count = 6; // 最大连接数
private int current_count = 0; // 记录当前使用连接数
// 连接池 (存放所有的初始化连接)
private LinkedList<Connection> pool = new LinkedList<Connection>();


//1. 构造函数中,初始化连接放入连接池
public MyPool() {
// 初始化连接
for (int i=0; i<init_count; i++){
// 记录当前连接数目
current_count++;
// 创建原始的连接对象
Connection con = createConnection();
// 把连接加入连接池
pool.addLast(con);
}
}

//2. 创建一个新的连接的方法
private Connection createConnection(){
try {
Class.forName(
"com.mysql.jdbc.Driver");
// 原始的目标对象
final Connection con = DriverManager.getConnection("jdbc:mysql:///jdbc_demo", "root", "root");

/**********对con对象代理**************/

// 对con创建其代理对象
Connection proxy = (Connection) Proxy.newProxyInstance(

con.getClass().getClassLoader(),
// 类加载器
//con.getClass().getInterfaces(), // 当目标对象是一个具体的类的时候
new Class[]{Connection.class}, // 目标对象实现的接口

new InvocationHandler() { // 当调用con对象方法的时候, 自动触发事务处理器
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 方法返回值
Object result = null;
// 当前执行的方法的方法名
String methodName = method.getName();

// 判断当执行了close方法的时候,把连接放入连接池
if ("close".equals(methodName)) {
System.out.println(
"begin:当前执行close方法开始!");
// 连接放入连接池
pool.addLast(con);
System.out.println(
"end: 当前连接已经放入连接池了!");
}
else {
// 调用目标对象方法
result = method.invoke(con, args);
}
return result;
}
}
);
return proxy;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}

//3. 获取连接
public Connection getConnection(){

// 3.1 判断连接池中是否有连接, 如果有连接,就直接从连接池取出
if (pool.size() > 0){
return pool.removeFirst();
}

// 3.2 连接池中没有连接: 判断,如果没有达到最大连接数,创建;
if (current_count < max_count) {
// 记录当前使用的连接数
current_count++;
// 创建连接
return createConnection();
}

// 3.3 如果当前已经达到最大连接数,抛出异常
throw new RuntimeException("当前连接已经达到最大连接数目 !");
}


//4. 释放连接
public void realeaseConnection(Connection con) {
// 4.1 判断: 池的数目如果小于初始化连接,就放入池中
if (pool.size() < init_count){
pool.addLast(con);
}
else {
try {
// 4.2 关闭
current_count--;
con.close();
}
catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

public static void main(String[] args) throws SQLException {
MyPool pool
= new MyPool();
System.out.println(
"当前连接: " + pool.current_count); // 3

// 使用连接
pool.getConnection();
pool.getConnection();
Connection con4
= pool.getConnection();
Connection con3
= pool.getConnection();
Connection con2
= pool.getConnection();
Connection con1
= pool.getConnection();

// 释放连接, 连接放回连接池
// pool.realeaseConnection(con1);
/*
* 希望:当关闭连接的时候,要把连接放入连接池!【当调用Connection接口的close方法时候,希望触发pool.addLast(con);操作】
* 把连接放入连接池
* 解决1:实现Connection接口,重写close方法
* 解决2:动态代理
*/
con1.close();

// 再获取
pool.getConnection();

System.out.println(
"连接池:" + pool.pool.size()); // 0
System.out.println("当前连接: " + pool.current_count); // 3
}

}

 

 

 

 DBCP连接池:

   DBCP 是 Apache 软件基金组织下的开源连接池实现,使用DBCP数据源,应用程序应在系统中增加如下两个 jar 文件:

    •    Commons-dbcp.jar:连接池的实现

    •    Commons-pool.jar:连接池实现的依赖库

   Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。

    l  核心类:BasicDataSource

    l  使用步骤

    •    引入jar文件

      l  commons-dbcp-1.4.jar

      l  commons-pool-1.5.6.jar

public class App_DBCP {

// 1. 硬编码方式实现连接池
@Test
public void testDbcp() throws Exception {
// DBCP连接池核心类
BasicDataSource dataSouce = new BasicDataSource();
// 连接池参数配置:初始化连接数、最大连接数 / 连接字符串、驱动、用户、密码
dataSouce.setUrl("jdbc:mysql:///jdbc_demo"); //数据库连接字符串
dataSouce.setDriverClassName("com.mysql.jdbc.Driver"); //数据库驱动
dataSouce.setUsername("root"); //数据库连接用户
dataSouce.setPassword("root"); //数据库连接密码
dataSouce.setInitialSize(3); // 初始化连接
dataSouce.setMaxActive(6); // 最大连接
dataSouce.setMaxIdle(3000); // 最大空闲时间

// 获取连接
Connection con = dataSouce.getConnection();
con.prepareStatement(
"delete from admin where id=3").executeUpdate();
// 关闭
con.close();
}

@Test
// 2. 【推荐】配置方式实现连接池 , 便于维护
public void testProp() throws Exception {
// 加载prop配置文件
Properties prop = new Properties();
// 获取文件流
InputStream inStream = App_DBCP.class.getResourceAsStream("db.properties");
// 加载属性配置文件
prop.load(inStream);
// 根据prop配置,直接创建数据源对象
DataSource dataSouce = BasicDataSourceFactory.createDataSource(prop);

// 获取连接
Connection con = dataSouce.getConnection();
con.prepareStatement(
"delete from admin where id=4").executeUpdate();
// 关闭
con.close();
}
}

 

配置方式实现DBCP连接池,  配置文件中的key与BaseDataSouce中的属性一样:

 

#基本配置
driverClassName
=com.mysql.jdbc.Driver
url
=jdbc:mysql://localhost:3306/mydb1
username=root
password
=123

#初始化池大小,即一开始池中就会有10个连接对象
默认值为0
initialSize
=0

#最大连接数,如果设置maxActive
=50时,池中最多可以有50个连接,当然这50个连接中包含被使用的和没被使用的(空闲)
#你是一个包工头,你一共有50个工人,但这50个工人有的当前正在工作,有的正在空闲
#默认值为8,如果设置为非正数,表示没有限制!即无限大
maxActive
=8

#最大空闲连接
#当设置maxIdle
=30时,你是包工头,你允许最多有20个工人空闲,如果现在有30个空闲工人,那么要开除10个
#默认值为8,如果设置为负数,表示没有限制!即无限大
maxIdle
=8

#最小空闲连接
#如果设置minIdel
=5时,如果你的工人只有3个空闲,那么你需要再去招2个回来,保证有5个空闲工人
#默认值为0
minIdle
=0

#最大等待时间
#当设置maxWait
=5000时,现在你的工作都出去工作了,又来了一个工作,需要一个工人。
#这时就要等待有工人回来,如果等待5000毫秒还没回来,那就抛出异常
#没有工人的原因:最多工人数为50,已经有50个工人了,不能再招了,但50人都出去工作了。
#默认值为
-1,表示无限期等待,不会抛出异常。
maxWait
=-1

#连接属性
#就是原来放在url后面的参数,可以使用connectionProperties来指定
#如果已经在url后面指定了,那么就不用在这里指定了。
#useServerPrepStmts
=true,MySQL开启预编译功能
#cachePrepStmts
=true,MySQL开启缓存PreparedStatement功能,
#prepStmtCacheSize
=50,缓存PreparedStatement的上限
#prepStmtCacheSqlLimit
=300,当SQL模板长度大于300时,就不再缓存它
connectionProperties
=useUnicode=true;characterEncoding=UTF8;useServerPrepStmts=true;cachePrepStmts=true;prepStmtCacheSize=50;prepStmtCacheSqlLimit=300

#连接的默认提交方式
#默认值为true
defaultAutoCommit
=true

#连接是否为只读连接
#Connection有一对方法:setReadOnly(boolean)和isReadOnly()
#如果是只读连接,那么你只能用这个连接来做查询
#指定连接为只读是为了优化!这个优化与并发事务相关!
#如果两个并发事务,对同一行记录做增、删、改操作,是不是一定要隔离它们啊?
#如果两个并发事务,对同一行记录只做查询操作,那么是不是就不用隔离它们了?
#如果没有指定这个属性值,那么是否为只读连接,这就由驱动自己来决定了。即Connection的实现类自己来决定!
defaultReadOnly
=false

#指定事务的事务隔离级别
#可选值:NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
#如果没有指定,那么由驱动中的Connection实现类自己来决定
defaultTransactionIsolation
=REPEATABLE_READ

 

  

  C3P0连接池:

 

  C3P0连接池:

 

           最常用的连接池技术!Spring框架,默认支持C3P0连接池技术!

 

  C3P0连接池,核心类:

 

           CombopooledDataSource ds;

 

  使用:

 

    1.       下载,引入jar文件:  c3p0-0.9.1.2.jar

 

    2.       使用连接池,创建连接

 

      a)         硬编码方式

 

      b)        配置方式(xml)

            配置文件要求:

              l  文件名称:必须叫c3p0-config.xml

              l  文件位置:必须在src下

        c3p0也可以指定配置文件,而且配置文件可以是properties,也可以  xml的。当然xml的高级一些了。但是c3p0的配置文件名必须为c3p0-config.xml,并且必须放在类路径下。

下面是源码

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">123</property>
<property name="acquireIncrement">3</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">2</property>
<property name="maxPoolSize">10</property>
</default-config>
<named-config name="oracle-config">
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">123</property>
<property name="acquireIncrement">3</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">2</property>
<property name="maxPoolSize">10</property>
</named-config>
</c3p0-config>

 

public class App {

@Test
//1. 硬编码方式,使用C3P0连接池管理连接
public void testCode() throws Exception {
// 创建连接池核心工具类
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 设置连接参数:url、驱动、用户密码、初始连接数、最大连接数
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc_demo");
dataSource.setDriverClass(
"com.mysql.jdbc.Driver");
dataSource.setUser(
"root");
dataSource.setPassword(
"root");
dataSource.setInitialPoolSize(
3);
dataSource.setMaxPoolSize(
6);
dataSource.setMaxIdleTime(
1000);

// ---> 从连接池对象中,获取连接对象
Connection con = dataSource.getConnection();
// 执行更新
con.prepareStatement("delete from admin where id=7").executeUpdate();
// 关闭
con.close();
}

@Test
//2. XML配置方式,使用C3P0连接池管理连接
public void testXML() throws Exception {
// 创建c3p0连接池核心工具类
// 自动加载src下c3p0的配置文件【c3p0-config.xml】
ComboPooledDataSource dataSource = new ComboPooledDataSource();// 使用默认的配置

// 获取连接
Connection con = dataSource.getConnection();
// 执行更新
con.prepareStatement("delete from admin where id=5").executeUpdate();
// 关闭
con.close();
}
  //获取配置文件中“orcale-cofig"的配置信息
  

  public void fun2() throws PropertyVetoException, SQLException {


       ComboPooledDataSource ds = new ComboPooledDataSource("orcale-config");


       Connection con = ds.getConnection();


       System.out.println(con);


       con.close();

    }
}