c3p0连接池用法
使用连接池的时候并不是在代码中不用获取/释放数据库连接,而是在代码中向连接池申请/释放连接,对于代码而言,可以把连接池看成数据库。
换句话说,连接池就是数据库的代理,之所以要使用这个代理是因为直接向数据库申请/释放连接是要降低性能的:如果每一次数据访问请求都必须经历建立数据库连接、打开数据库、存取数据和关闭数据库连接等步骤,而连接并打开数据库是一件既消耗资源又费时的工作,那么频繁发生这种数据库操作时,系统的性能必然会急剧下降。
连接池的作用是自己维护数据库连接,数据库连接池的主要操作如下:
(1)建立数据库连接池对象(服务器启动)。
(2)按照事先指定的参数创建初始数量的数据库连接(即:空闲连接数)。
(3)对于一个数据库访问请求,直接从连接池中得到一个连接。如果数据库连接池对象中没有空闲的连接,且连接数没有达到最大(即:最大活跃连接数),创建一个新的数据库连接。
(4)存取数据库。
(5)关闭数据库,释放所有数据库连接(此时的关闭数据库连接,并非真正关闭,而是将其放入空闲队列中。如实际空闲连接数大于初始空闲连接数则释放连接)。
(6)释放数据库连接池对象(服务器停止、维护期间,释放数据库连接池对象,并释放所有连接)。
从连接池获取的连接connection跟JDK中的connection有点不同,前者的close方法并没有关闭与数据库的连接,而是将连接返回到池中,这样就可以复用了。如果不调用close方法的话拿就失去了使用连接池的意义了。
开源连接池有很多:DBCP、C3P0、Proxool 、 BoneCP等
C3P0是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。
- 下载c3p0的jar,并添加log4j.jar.
- 采用ThreadLocal线程局部变量保证线程安全.
使用连接池和不使用连接池时的性能差异简单的C3P0使用测试示例
package com.lnbdqn; import java.beans.PropertyVetoException; import java.sql.Connection; import java.sql.SQLException; import com.mchange.v2.c3p0.ComboPooledDataSource; public final class ConnectionManager { private static ConnectionManager instance; private static ComboPooledDataSource dataSource; private ConnectionManager() throws SQLException, PropertyVetoException { dataSource = new ComboPooledDataSource(); dataSource.setUser("loux"); dataSource.setPassword("loux"); dataSource.setJdbcUrl("jdbc:oracle:thin:@192.168.100.70:1521:orcl"); dataSource.setDriverClass("oracle.jdbc.driver.OracleDriver"); dataSource.setInitialPoolSize(5); dataSource.setMinPoolSize(1); dataSource.setMaxPoolSize(10); dataSource.setMaxStatements(50); dataSource.setMaxIdleTime(60); } public static final ConnectionManager getInstance() { if (instance == null) { try { instance = new ConnectionManager(); } catch (Exception e) { e.printStackTrace(); } } return instance; } public synchronized final Connection getConnection() { Connection conn = null; try { conn = dataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return conn; } }
package com.lnbdqn; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import oracle.jdbc.pool.OracleDataSource; public class ConnectionDemo { public static void main(String[] args) throws SQLException { System.out.println("使用连接池................................"); for (int i = 0; i < 20; i++) { long beginTime = System.currentTimeMillis(); Connection conn = ConnectionManager.getInstance().getConnection(); try { PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM t_fmscpy200"); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { } } catch (SQLException e) { e.printStackTrace(); } finally { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } long endTime = System.currentTimeMillis(); System.out.println("第" + (i + 1) + "次执行花费时间为:" + (endTime - beginTime)); } System.out.println("不使用连接池................................"); for (int i = 0; i < 20; i++) { long beginTime = System.currentTimeMillis(); OracleDataSource ods = new OracleDataSource(); ods.setUser("loux"); ods.setPassword("loux"); ods.setURL("jdbc:oracle:thin:@192.168.100.70:1521:orcl"); Connection conn = ods.getConnection(); try { PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM table_name"); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { // do nothing... } } catch (SQLException e) { e.printStackTrace(); } finally { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } long endTime = System.currentTimeMillis(); System.out.println("第" + (i + 1) + "次执行花费时间为:" + (endTime - beginTime)); } } }
控制台输出的结果为: 使用连接池................................ 第1次执行花费时间为:1469 第2次执行花费时间为:0 第3次执行花费时间为:16 第4次执行花费时间为:0 第5次执行花费时间为:0 第6次执行花费时间为:15 第7次执行花费时间为:0 第8次执行花费时间为:0 第9次执行花费时间为:0 第10次执行花费时间为:0 第11次执行花费时间为:16 第12次执行花费时间为:0 第13次执行花费时间为:0 第14次执行花费时间为:0 第15次执行花费时间为:0 第16次执行花费时间为:16 第17次执行花费时间为:0 第18次执行花费时间为:0 第19次执行花费时间为:15 第20次执行花费时间为:0 不使用连接池................................ 第1次执行花费时间为:47 第2次执行花费时间为:31 第3次执行花费时间为:32 第4次执行花费时间为:46 第5次执行花费时间为:32 第6次执行花费时间为:31 第7次执行花费时间为:47 第8次执行花费时间为:31 第9次执行花费时间为:47 第10次执行花费时间为:31 第11次执行花费时间为:47 第12次执行花费时间为:31 第13次执行花费时间为:32 第14次执行花费时间为:46 第15次执行花费时间为:47 第16次执行花费时间为:32 第17次执行花费时间为:46 第18次执行花费时间为:47 第19次执行花费时间为:32 第20次执行花费时间为:31
可以看出,在使用连接池时,第一次执行花费的时间稍长,因为第一次初始化操作需要创建多个连接并放入池中,以后使用时将会大大缩短执行时间。
在不使用连接池时,每次花费的时间都比较长。
下面是一个service层的银行转账方法
public void transferMoneyNew(int from,int to,float money) throws Exception{ AccountDAOImpl dao = null; try{ /*完成的功能: 1.从数据源中获取Connection 2.开启事务 3. 放到线程上 */ JDBCUtils.startTransaction(); //创建DAO dao = new AccountDAOImpl(); //获取账户信息 Account fromAccount = dao.findAccountByID(from); Account toAccount = dao.findAccountByID(to); //扣钱和加钱 fromAccount.setMoney(fromAccount.getMoney() - money); toAccount.setMoney(toAccount.getMoney() + money); //更新数据库 dao.updateAccount(fromAccount); //产生错误 int i=1/0; dao.updateAccount(toAccount); //提交 JDBCUtils.commit(); }catch(Exception ex){ JDBCUtils.rollback(); throw new ServiceException(ex); }finally{ JDBCUtils.release(); } } //////////////////////////////////////////////////////////////////////////////////// JDBCUtils类 public class JDBCUtils { //连接的容器 public static ThreadLocal<Connection> container = new ThreadLocal<Connection>(); //定义c3p0 数据源 private static DataSource ds = new ComboPooledDataSource(); /*完成的功能: 1.从数据源中获取Connection 2.开启事务 3. 放到线程上 */ public static void startTransaction() throws SQLException{ Connection conn = container.get(); //当前线程上是否已经存在连接 if(conn == null){ conn = ds.getConnection(); } //开启事务 conn.setAutoCommit(false); //放到当前线程上 container.set(conn); } //提交当前线程上的连接 public static void commit() throws SQLException{ Connection conn = container.get(); if(conn != null){ conn.commit(); } } //回滚当前线程上的连接 public static void rollback() throws SQLException{ Connection conn = container.get(); if(conn != null){ conn.rollback(); } } //释放当前线程上的连接 public static void release() throws SQLException{ Connection conn = container.get(); if(conn != null){ //从当前线程上,拿掉连接 container.remove(); conn.close(); } } //返回数据源 public static DataSource getDataSource(){ return ds; } public static Connection getConnection() throws SQLException { return ds.getConnection(); } //释放资源 public static void release(Connection conn,Statement st,ResultSet rs){ if(rs != null){ try { rs.close(); } catch (SQLException e) { throw new RuntimeException(e); }finally{ rs = null; } } if(st != null){ try { st.close(); } catch (SQLException e) { throw new RuntimeException(e); }finally{ st = null; } } if(conn != null){ try { conn.close(); } catch (SQLException e) { throw new RuntimeException(e); }finally{ conn = null; } } } }
OSChina 的 DBManager 类 管理数据库连接
public class DBManager { private final static Log log = LogFactory.getLog(DBManager.class); private final static ThreadLocal<Connection> conns = new ThreadLocal<Connection>(); private static DataSource dataSource; private static boolean show_sql = false; static { initDataSource(null); } /** * 初始化连接池 * @param props * @param show_sql */ private final static void initDataSource(Properties dbProperties) { try { if(dbProperties == null){ dbProperties = new Properties(); dbProperties.load(DBManager.class.getResourceAsStream("db.properties")); } Properties cp_props = new Properties(); for(Object key : dbProperties.keySet()) { String skey = (String)key; if(skey.startsWith("jdbc.")){ String name = skey.substring(5); cp_props.put(name, dbProperties.getProperty(skey)); if("show_sql".equalsIgnoreCase(name)){ show_sql = "true".equalsIgnoreCase(dbProperties.getProperty(skey)); } } } dataSource = (DataSource)Class.forName(cp_props.getProperty("datasource")).newInstance(); if(dataSource.getClass().getName().indexOf("c3p0")>0){ //Disable JMX in C3P0 System.setProperty("com.mchange.v2.c3p0.management.ManagementCoordinator", "com.mchange.v2.c3p0.management.NullManagementCoordinator"); } log.info("Using DataSource : " + dataSource.getClass().getName()); BeanUtils.populate(dataSource, cp_props); Connection conn = getConnection(); DatabaseMetaData mdm = conn.getMetaData(); log.info("Connected to " + mdm.getDatabaseProductName() + " " + mdm.getDatabaseProductVersion()); closeConnection(); } catch (Exception e) { throw new DBException(e); } } /** * 断开连接池 */ public final static void closeDataSource(){ try { dataSource.getClass().getMethod("close").invoke(dataSource); } catch (NoSuchMethodException e){ } catch (Exception e) { log.error("Unabled to destroy DataSource!!! ", e); } } public final static Connection getConnection() throws SQLException { Connection conn = conns.get(); if(conn ==null || conn.isClosed()){ conn = dataSource.getConnection(); conns.set(conn); } return (show_sql && !Proxy.isProxyClass(conn.getClass()))? new _DebugConnection(conn).getConnection():conn; } /** * 关闭连接 */ public final static void closeConnection() { Connection conn = conns.get(); try { if(conn != null && !conn.isClosed()){ conn.setAutoCommit(true); conn.close(); } } catch (SQLException e) { log.error("Unabled to close connection!!! ", e); } conns.set(null); } /** * 用于跟踪执行的SQL语句 * @author Winter Lau */ static class _DebugConnection implements InvocationHandler { private final static Log log = LogFactory.getLog(_DebugConnection.class); private Connection conn = null; public _DebugConnection(Connection conn) { this.conn = conn; } /** * Returns the conn. * @return Connection */ public Connection getConnection() { return (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { try { String method = m.getName(); if("prepareStatement".equals(method) || "createStatement".equals(method)) log.info("[SQL] >>> " + args[0]); return m.invoke(conn, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } } }
# DataSource jdbc.datasource=com.mchange.v2.c3p0.ComboPooledDataSource jdbc.show_sql=true # Database Configurations jdbc.driverClass=com.mysql.jdbc.Driver jdbc.jdbcUrl=jdbc:mysql://localhost:3306/oscdb jdbc.user=root jdbc.password=xxxx jdbc.maxPoolSize=100 jdbc.minPoolSize=2 jdbc.initialPoolSize=2 jdbc.acquireIncrement=2 jdbc.maxStatements=1000 jdbc.maxIdleTime=300 jdbc.checkoutTimeout=5000
参数配置例子
package com.wb.db; import java.beans.PropertyVetoException; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; /** * 采用ThreadLocal线程局部变量保证线程安全 * @author hemes1314 */ public class C3p0Pool { public static ThreadLocal connectionHolder = new ThreadLocal(); private static DataSource dataSource; public C3p0Pool(){ } public static Connection getConnection() { Connection conn = (Connection) connectionHolder.get(); //如果在当前线程中没有绑定相应的Connection if(conn==null){ if (dataSource == null) { initDataSource(); } try { conn = dataSource.getConnection(); //将Connection设置到ThreadLocal线程变量中 connectionHolder.set(conn); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return conn; } public static void closeConnection(){ Connection conn = (Connection) connectionHolder.get(); if(conn!=null){ try { conn.close(); //从ThreadLocal中清除Connection connectionHolder.remove(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public static void initDataSource(){ String driverClassName=null; String url=null; String username=null; String password=null; int initialPoolSize=3; int maxPoolSize=15; int minPoolSize=5; int acquireRetryDelay=1000; int maxIdleTime=60; Configuration config=new Configuration("oraConn.properties"); driverClassName = config.getValue("driver"); url = config.getValue("url"); username = config.getValue("user"); password = config.getValue("password"); initialPoolSize = Integer.parseInt(config.getValue("initialPoolSize").trim()); maxPoolSize = Integer.parseInt(config.getValue("maxPoolSize").trim()); minPoolSize = Integer.parseInt(config.getValue("minPoolSize").trim()); maxIdleTime = Integer.parseInt(config.getValue("maxIdleTime").trim()); ComboPooledDataSource cpds = new ComboPooledDataSource(); try { cpds.setDriverClass(driverClassName); } catch (PropertyVetoException e) { // TODO Auto-generated catch block e.printStackTrace(); } cpds.setUser(username); cpds.setPassword(password); cpds.setJdbcUrl(url); //初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 initialPoolSize cpds.setInitialPoolSize(initialPoolSize); //连接池中保留的最大连接数。Default: 15 maxPoolSize cpds.setMaxPoolSize(maxPoolSize); //连接池中保留的最小连接数。 cpds.setMinPoolSize(minPoolSize); //获得连接的最大等待毫秒数。Default: 1000 acquireRetryDelay cpds.setAcquireRetryDelay(acquireRetryDelay); //最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 maxIdleTime cpds.setMaxIdleTime(maxIdleTime); //当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 acquireIncrement //cpds.setAcquireIncrement(3); //每60秒检查所有连接池中的空闲连接。Default: 0 idleConnectionTestPeriod //cpds.setIdleConnectionTestPeriod(60); //连接关闭时默认将所有未提交的操作回滚。Default: false autoCommitOnClose //cpds.setAutoCommitOnClose(true); //JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 //cpds.setMaxStatements(1); //maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数 //cpds.setMaxStatementsPerConnection(100); //定义所有连接测试都执行的测试语句。在使用连接测试的情况下这个一显著提高测试速度。注意:测试的表必须在初始数据源的时候就存在。Default: null preferredTestQuery //cpds.setPreferredTestQuery("select sysdate from dual"); // 因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的 // 时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable // 等方法来提升连接测试的性能。Default: false testConnectionOnCheckout //cpds.setTestConnectionOnCheckout(true); //如果设为true那么在取得连接的同时将校验连接的有效性。Default: false testConnectionOnCheckin //cpds.setTestConnectionOnCheckin(true); //定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 acquireRetryAttempts //cpds.setAcquireRetryAttempts(30); //获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效 //保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试 //获取连接失败后该数据源将申明已断开并永久关闭。Default: false breakAfterAcquireFailure //cpds.setBreakAfterAcquireFailure(false); dataSource = cpds; } /* 用于测试连接状态的方法*/ public static void main(String[] args) { ComboPooledDataSource ds=(ComboPooledDataSource)dataSource; try { System.out.println(ds.getConnection()); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } //System.out.println(ds.getInitialSize()); //System.out.println(ds.getNumActive()); //System.out.println(ds.getNumIdle()); //System.out.println(ds.getDefaultAutoCommit()); } }