使用連接池的時候並不是在代碼中不用獲取/釋放數據庫連接,而是在代碼中向連接池申請/釋放連接,對於代碼而言,可以把連接池看成數據庫。
換句話說,連接池就是數據庫的代理,之所以要使用這個代理是因為直接向數據庫申請/釋放連接是要降低性能的:如果每一次數據訪問請求都必須經歷建立數據庫連接、打開數據庫、存取數據和關閉數據庫連接等步驟,而連接並打開數據庫是一件既消耗資源又費時的工作,那么頻繁發生這種數據庫操作時,系統的性能必然會急劇下降。
連接池的作用是自己維護數據庫連接,數據庫連接池的主要操作如下:
(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()); } }
