一种技术的出现,要么是解决实际问题,要么是优化现有技术。数据库连接池技术的出现,是为了优化数据库连接操作的性能。
在使用JDBC进行数据库开发的时候,一般经历这样一个过程:
1)加载数据库的驱动
2)建立数据库的连接(Connection)
3)创建SQL语句声明(Statement)
4)执行更新(executeUpdate)或查询(executeQuery)
本文中讲的数据库连接池,只是针对Connection的部分的优化。
学习连接池
a. 自定义一个连接池
b. 学习优秀的连接池组件
1)DBCP
2)C3P0
1、引入
思考:程序中Connection连接是如何管理的?
数据库的连接(Connection)涉及到的操作有:a)数据库操作开始,创建连接,b)操作结束,关闭连接。
我们知道连接资源十分宝贵,因此需要对它进行管理。如果频繁的打开和关闭连接,会影响程序的运行效率!
连接管理的思路:预先创建一组连接,用的时候每次取出一个;用完之后,将连接放回去。
2、自定义连接池
第一个版本
package com.rk.pool;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
public class MyConnectionPool
{
private final static String url = "jdbc:mysql:///testdb";
private final static String driverClassName = "com.mysql.jdbc.Driver";
private final static String user = "root";
private final static String password = "root";
private LinkedList<Connection> freeConnections = null;//// 连接池 (存放所有的初始化连接)
private final int init_count = 3;//初始的连接(Connection)数量(最小值)
private final int max_count = 6;//最大的连接数量(最大值)
private int current_count = 0;//当前拥有的连接数量(当前值)
static
{
try
{
Class.forName(driverClassName);
}
catch (ClassNotFoundException e)
{
throw new RuntimeException(e);
}
}
//1.初始化
public MyConnectionPool() throws SQLException
{
//初始化连接池
freeConnections = new LinkedList<Connection>();
//将指定数量(init_cont)加入到连接池中
for(int i=0;i<init_count;i++)
{
// 记录当前连接数目
current_count++;
// 创建连接对象
Connection con = createConnection();
// 把连接加入连接池
freeConnections.add(con);
}
}
public int getCurrentCount()
{
return current_count;
}
private static Connection createConnection() throws SQLException
{
return DriverManager.getConnection(url, user, password);
}
//2.获取数据库的连接
public Connection getConnection() throws SQLException
{
//判断连接池中是否有连接, 如果有连接,就直接从连接池取出
if(freeConnections.size()>0)
{
return freeConnections.removeFirst();
}
else if(current_count<max_count)//连接池中没有连接;并且如果没有达到最大连接数,创建新连接;
{
current_count++;
return createConnection();
}
else//当前已经达到最大连接数
{
System.out.println("已经达到连接最大数量限制,无法获得新的连接");
return null;
}
}
//3.释放连接
public void releaseConnection(Connection conn) throws SQLException
{
//池的数目如果小于初始化连接,就放入池中
if(freeConnections.size() < init_count)
{
freeConnections.addLast(conn);
}
else
{//关闭
current_count--;
conn.close();
}
}
public static void main(String[] args) throws SQLException
{
MyConnectionPool pool = new MyConnectionPool();
System.out.println("0-->" + pool.getCurrentCount());
Connection conn1 = pool.getConnection();
Connection conn2 = pool.getConnection();
Connection conn3 = pool.getConnection();
System.out.println("3-->" + pool.getCurrentCount());
pool.releaseConnection(conn1);
Connection conn4 = pool.getConnection();
System.out.println("4-->" + pool.getCurrentCount());
}
}
第二个版本(在创建Connection时,增加了动态代理)
如果开发人员得到Connection对象时,并调用它的close方法,并不会关闭连接,而是将Connection对象放回到连接池中,这就是通过动态代理来实现的。
如果对某个接口中的某个指定的方法的功能进行扩展,而不想实现接口里所有方法,可以使用(动态)代理模式! 使用动态代理,可以监测接口中方法的执行!
Java中代理模式:静态/动态/Cglib代理(spring)
如何对Connection对象,生成一个代理对象:
|--Proxy
static Object newProxyInstance(
ClassLoader loader, 当前使用的类加载器
Class<?>[] interfaces, 目标对象(Connection)实现的接口类型
InvocationHandler h 事件处理器:当执行上面接口中的方法的时候,就会自动触发事件处理器代码,把当前执行的方法(method)作为参数传入。
)
package com.rk.pool;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
public class MyConnectionPoolUpdate
{
private final static String url = "jdbc:mysql:///testdb";
private final static String driverClassName = "com.mysql.jdbc.Driver";
private final static String user = "root";
private final static String password = "root";
private LinkedList<Connection> freeConnections = null;//// 连接池 (存放所有的初始化连接)
private final int init_count = 3;//初始的连接(Connection)数量(最小值)
private final int max_count = 6;//最大的连接数量(最大值)
private int current_count = 0;//当前拥有的连接数量(当前值)
static
{
try
{
Class.forName(driverClassName);
}
catch (ClassNotFoundException e)
{
throw new RuntimeException(e);
}
}
//1.初始化
public MyConnectionPoolUpdate() throws SQLException
{
//初始化连接池
freeConnections = new LinkedList<Connection>();
//将指定数量(init_cont)加入到连接池中
for(int i=0;i<init_count;i++)
{
// 记录当前连接数目
current_count++;
// 创建连接对象
Connection con = createConnection();
// 把连接加入连接池
freeConnections.add(con);
}
}
public int getCurrentCount()
{
return current_count;
}
private Connection createConnection() throws SQLException
{
final Connection conn = DriverManager.getConnection(url, user, password);
Connection proxy = (Connection)Proxy.newProxyInstance(
//conn.getClass().getClassLoader();// 第一个参数,类加载器(方法一)
Connection.class.getClassLoader(), // 第一个参数,类加载器(方法二)
//conn.getClass().getInterfaces(),//第二个参数,如果当前目标对象是一个具体的类的时候,用这个
new Class[]{Connection.class}, //第二个参数,由于Connection是一个接口
new InvocationHandler()
{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
// 方法返回值
Object result = null;
System.out.println(proxy.getClass());
// 当前执行的方法的方法名
String methodName = method.getName();
if("close".equals(methodName))
{
System.out.println("begin:当前执行close方法开始!");
// 调用释放连接池的方法
MyConnectionPoolUpdate.this.releaseConnection(conn);
System.out.println("end: 当前连接已经放入连接池了!");
}
else
{
// 调用目标对象方法
result = method.invoke(conn, args);
}
return result;
}
});
return proxy;
}
//2.获取数据库的连接
public Connection getConnection() throws SQLException
{
//判断连接池中是否有连接, 如果有连接,就直接从连接池取出
if(freeConnections.size()>0)
{
return freeConnections.removeFirst();
}
else if(current_count<max_count)//连接池中没有连接;并且如果没有达到最大连接数,创建新连接;
{
current_count++;
return createConnection();
}
else//当前已经达到最大连接数
{
System.out.println("已经达到连接最大数量限制,无法获得新的连接");
return null;
}
}
//3.释放连接
public void releaseConnection(Connection conn) throws SQLException
{
System.out.println("MyConnectionPoolUpdate.releaseConnection()");
//池的数目如果小于初始化连接,就放入池中
if(freeConnections.size() < init_count)
{
freeConnections.addLast(conn);
}
else
{//关闭
current_count--;
conn.close();
}
}
public static void main(String[] args) throws SQLException
{
MyConnectionPoolUpdate pool = new MyConnectionPoolUpdate();
System.out.println("0-->" + pool.getCurrentCount());
Connection conn1 = pool.getConnection();
conn1.close();
}
}
3、开源的连接池技术
Sun公司约定: 如果是连接池技术,需要实现一个接口:javax.sql.DataSource!
注意,DataSource位于javax.sql包下,而不是java.sql包下,其中x表示扩展的意思。
下面是DataSource的源代码,它只提供了两个重载方法getConnection。
public interface DataSource extends CommonDataSource,Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
}
javax.sql.DataSource接口
(1)DataSource object是对DriverManager的一种替代。推荐使用DataSource来获得Connection对象。
An alternative to the DriverManager facility, a DataSource object is the preferred means of getting a connection.
(2)实现DataSource接口的object常常是用JNDI注册的。
An object that implements the DataSource interface will typically be registered with a naming service based on the JavaTM Naming and Directory (JNDI) API.
(3)DataSource接口由driver vendor实现,有三种实现类型:
3.1)Basic implementation:基本实现,产生标准的Connection对象
3.2)Connection pooling implementation:连接池实现,有connection pool,由a middle-tier connection pooling manager
3.3)Distributed transaction implementation:分布式事务实现,由a middle-tier transaction manager和a connection pooling manager.
The DataSource interface is implemented by a driver vendor. There are three types of implementations:
a)Basic implementation -- produces a standard Connection object
b)Connection pooling implementation -- produces a Connection object that will automatically participate in connection pooling. This implementation works with a middle-tier connection pooling manager.
c)Distributed transaction implementation -- produces a Connection object that may be used for distributed transactions and almost always participates in connection pooling. This implementation works with a middle-tier transaction manager and almost always with a connection pooling manager.
(4)通过DataSource获得的driver,并不会用DriverManager注册。
回忆一下:DriverManager可以注册驱动。
Driver driver = new com.mysql.jdbc.Driver();
//注册驱动程序(可以注册多个驱动程序)
DriverManager.registerDriver(driver);
A driver that is accessed via a DataSource object does not register itself with the DriverManager. Rather, a DataSource object is retrieved though a lookup operation and then used to create a Connection object.
(5)如果是basic implementation,那么通过DataSource object获得的Connection与通过DriverManager获得的Connection是一样的。
With a basic implementation, the connection obtained through a DataSource object is identical to a connection obtained through the DriverManager facility.
3.1、DBCP连接池
DBCP 是 Apache 软件基金组织下的开源连接池实现,使用DBCP数据源,应用程序应在系统中增加如下两个 jar 文件:
Commons-dbcp.jar:连接池的实现
Commons-pool.jar:连接池实现的依赖库
Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。
核心类:BasicDataSource
使用步骤
引入jar文件
commons-dbcp-1.4.jar
http://commons.apache.org/proper/commons-dbcp/download_dbcp.cgi
commons-pool-1.6.jar
http://commons.apache.org/proper/commons-pool/download_pool.cgi
使用连接池,创建连接
a) 硬编码方式
b) 配置方式(db.properties)
package com.rk.dbcp;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.junit.Test;
public class Demo
{
// 1. 硬编码方式实现连接池
@Test
public void testDbcp() throws SQLException
{
// DBCP连接池核心类
BasicDataSource dataSource = new BasicDataSource();
// 连接池参数配置: 连接字符串、驱动、用户、密码
dataSource.setUrl("jdbc:mysql:///testdb");//"jdbc:mysql://localhost:3306/testdb"
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("root");
// 连接池参数配置:初始化连接数、最大连接数 、最大空闲时间
dataSource.setInitialSize(3);
dataSource.setMaxActive(6);
dataSource.setMaxIdle(3000);
// 获取连接
Connection conn = dataSource.getConnection();
System.out.println(conn);
conn.prepareStatement("DELETE FROM T_Student WHERE Id=10").executeUpdate();
// 关闭
conn.close();
}
@Test
// 2. 【推荐】配置方式实现连接池 , 便于维护
public void testProp() throws Exception
{
// 获取文件流
InputStream inStream = Demo.class.getResourceAsStream("db.properties");
//创建Properties对象
Properties prop = new Properties();
// 加载属性配置文件
prop.load(inStream);
// 根据prop配置,直接创建数据源对象
DataSource dataSource = BasicDataSourceFactory.createDataSource(prop);
// 获取连接
Connection conn = dataSource.getConnection();
System.out.println(conn);
conn.prepareStatement("DELETE FROM T_Student WHERE Id=10").executeUpdate();
// 关闭
conn.close();
}
}
配置方式实现DBCP连接池, 配置文件中的key与BaseDataSouce中的属性一样:
url=jdbc:mysql://localhost:3306/testdb
driverClassName=com.mysql.jdbc.Driver
username=root
password=root
initialSize=3
maxActive=6
maxIdle=3000
org.apache.commons.dbcp.BasicDataSource
(1)Apache的DBCP中的核心类BasicDataSource是对javax.sql.DataSource接口的Basic implementation。
Basic implementation of javax.sql.DataSource that is configured via JavaBeans properties.
org.apache.commons.dbcp.BasicDataSourceFactory
(1)BasicDataSourceFactory是一个JNDI object factory,用于创建一个BasicDataSource实例。
JNDI object factory that creates an instance of BasicDataSource.
3.2、C3P0连接池
C3P0连接池:最常用的连接池技术!Spring框架,默认支持C3P0连接池技术!
核心类:ComboPooledDataSource
使用:
1.下载,引入jar文件: c3p0-0.9.1.2.jar
https://sourceforge.net/projects/c3p0/
http://www.mchange.com/projects/c3p0/
2. 使用连接池,创建连接
a) 硬编码方式
b) 配置方式(xml)
package com.rk.c3p0;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
import org.junit.Test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class Demo
{
@Test
//1. 硬编码方式,使用C3P0连接池管理连接
public void testCode() throws PropertyVetoException, SQLException
{
// 创建连接池核心工具类
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 设置连接参数:url、驱动、用户密码、初始连接数、最大连接数
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/testdb");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setInitialPoolSize(3);
dataSource.setMaxPoolSize(6);
dataSource.setMaxIdleTime(1000);
// ---> 从连接池对象中,获取连接对象
Connection conn = dataSource.getConnection();
System.out.println(conn);
conn.prepareStatement("DELETE FROM T_Student WHERE Id=10").executeUpdate();
// 关闭
conn.close();
}
@Test
//2. XML配置方式,使用C3P0连接池管理连接
public void testXML() throws SQLException
{
// 创建c3p0连接池核心工具类
// 自动加载src下c3p0的配置文件【c3p0-config.xml】
ComboPooledDataSource dataSource = new ComboPooledDataSource();// 使用默认的配置
//ComboPooledDataSource dataSource = new ComboPooledDataSource("oracle_config");// 使用指定的配置
// ---> 从连接池对象中,获取连接对象
Connection conn = dataSource.getConnection();
System.out.println(conn);
conn.prepareStatement("DELETE FROM T_Student WHERE Id=10").executeUpdate();
// 关闭
conn.close();
}
}
c3p0-config.xml文件放在src目录下
<c3p0-config>
<default-config>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/testdb</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="initialPoolSize">3</property>
<property name="maxPoolSize">6</property>
<property name="maxIdleTime">1000</property>
</default-config>
<named-config name="oracle_config">
<property name="jdbcUrl">jdbc:mysql://localhost:3306/testdb</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="initialPoolSize">3</property>
<property name="maxPoolSize">6</property>
<property name="maxIdleTime">1000</property>
</named-config>
</c3p0-config>