一种技术的出现,要么是解决实际问题,要么是优化现有技术。数据库连接池技术的出现,是为了优化数据库连接操作的性能。
在使用JDBC进行数据库开发的时候,一般经历这样一个过程:
1)加载数据库的驱动
2)建立数据库的连接(Connection)
3)创建SQL语句声明(Statement)
4)执行更新(executeUpdate)或查询(executeQuery)
本文中讲的数据库连接池,只是针对Connection的部分的优化。
学习连接池
a. 自定义一个连接池
b. 学习优秀的连接池组件
1)DBCP
2)C3P0
思考:程序中Connection连接是如何管理的?
数据库的连接(Connection)涉及到的操作有:a)数据库操作开始,创建连接,b)操作结束,关闭连接。
我们知道连接资源十分宝贵,因此需要对它进行管理。如果频繁的打开和关闭连接,会影响程序的运行效率!
连接管理的思路:预先创建一组连接,用的时候每次取出一个;用完之后,将连接放回去。
第一个版本
package com.rk.pool; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.LinkedList; /** * 自定义连接池,管理连接(Connection) * 全局参数:初始化数目、最大连接数、当前连接数、连接池集合 * * 当启动的时候,就有3(init_count)个初始连接Connection * 1.创建连接的方法createConnection() * 2.提供获取连接的方法getConnection() * 2.1如果有空闲连接,则直接返回 * 2.2如果没有空闲连接,且没有达到最大限制数量,则创建连接返回 * 2.3如果没有空闲连接,且连接数据达到最大限制数,则无法获取到连接,返回null * 3.提供释放连接的方法releaseConnection(Connection conn) * 3.1如果连接池内的空闲连接数量小于初始的连接数量,则当前连接返回到连接池中 * 3.2如果连接池内的空闲连接数量等于初始的连接数量,则将当前连接关闭 * @author lsieun * */ 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()); Connection conn4 = pool.getConnection(); System.out.println("4-->" + pool.getCurrentCount()); Connection conn5 = pool.getConnection(); System.out.println("5-->" + pool.getCurrentCount()); Connection conn6 = pool.getConnection(); System.out.println("6-->" + pool.getCurrentCount()); Connection conn7 = pool.getConnection(); System.out.println("7-->" + pool.getCurrentCount()); */ 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; /** * 自定义连接池,管理连接(Connection) * 全局参数:初始化数目、最大连接数、当前连接数、连接池集合 * * 当启动的时候,就有3(init_count)个初始连接Connection * 1.创建连接的方法createConnection() * 2.提供获取连接的方法getConnection() * 2.1如果有空闲连接,则直接返回 * 2.2如果没有空闲连接,且没有达到最大限制数量,则创建连接返回 * 2.3如果没有空闲连接,且连接数据达到最大限制数,则无法获取到连接,返回null * 3.提供释放连接的方法releaseConnection(Connection conn) * 3.1如果连接池内的空闲连接数量小于初始的连接数量,则当前连接返回到连接池中 * 3.2如果连接池内的空闲连接数量等于初始的连接数量,则将当前连接关闭 * @author lsieun * */ 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(); } }
Sun公司约定: 如果是连接池技术,需要实现一个接口:javax.sql.DataSource!
注意,DataSource位于javax.sql包下,而不是java.sql包下,其中x表示扩展的意思。
下面是DataSource的源代码,它只提供了两个重载方法getConnection。
public interface DataSource extends CommonDataSource,Wrapper { /** * <p>Attempts to establish a connection with the data source that * this <code>DataSource</code> object represents. */ Connection getConnection() throws SQLException; /** * <p>Attempts to establish a connection with the data source that * this <code>DataSource</code> object represents. */ 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.
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.
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>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。