使用连接池的时候并不是在代码中不用获取/释放数据库连接,而是在代码中向连接池申请/释放连接,对于代码而言,可以把连接池看成数据库。
换句话说,连接池就是数据库的代理,之所以要使用这个代理是因为直接向数据库申请/释放连接是要降低性能的:如果每一次数据访问请求都必须经历建立数据库连接、打开数据库、存取数据和关闭数据库连接等步骤,而连接并打开数据库是一件既消耗资源又费时的工作,那么频繁发生这种数据库操作时,系统的性能必然会急剧下降。
连接池的作用是自己维护数据库连接,数据库连接池的主要操作如下:
(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.从数据源中获取Connection2.开启事务3. 放到线程上*/JDBCUtils.startTransaction(); //创建DAOdao = 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.从数据源中获取Connection2.开启事务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 C3P0System.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();//如果在当前线程中没有绑定相应的Connectionif(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()); }}