轉載請注明原文地址:http://www.cnblogs.com/ygj0930/p/6405861.html
在項目中的應用見: https://github.com/ygj0930/CoupleSpace
一般我們在項目中操作數據庫時,都是每次需要操作數據庫就建立一個連接,操作完成后釋放連接。因為jdbc沒有保持連接的能力,一旦超過一定時間沒有使用(大約幾百毫秒),連接就會被自動釋放掉。而每次新建連接都需要140毫秒左右的時間,所以耗費時間比較多。若使用C3P0連接池來池化連接,隨時取用,則平均每次取用只需要10-20毫秒。這在高並發隨機訪問數據庫的時候對效率的提升有很大幫助。
C3P0連接池會根據你的配置來初始化N個數據庫連接,空閑T時間后連接過期又會自動新建K個連接使得連接池總有空閑的數據庫連接等待被取用。我們只需通過dataSourse.getConnection()即可從線程池中取用一個已經連接好的空閑連接,執行數據庫操作。然后“斷開”(放回)這個連接,把這個連接的使用權放回連接池。真正的數據庫連接的創建與釋放是由C3P0在后台自動完成的,我們花的只是取用與釋放占用權的時間。全程耗時10+毫秒,比原來提高了幾十倍。
下面介紹一種C3P0的三層式使用。與一般的C3P0使用方式不同,三層式把數據庫操作分為了三部分:C3P0操作類C3P0Utils、封裝了一次數據庫操作的VO對象DBUtils_BO、數據庫操作類DBUtils。下面一一講解:
一:C3P0Utils:負責從連接池獲取一個連接、放回一個連接。
這里主要是C3P0連接池的使用。步驟如下:
1:下載C3P0工具包: https://sourceforge.net/projects/c3p0/files/latest/download?source=files
解壓出來后得到3個jar包,導入你的項目中。
2:對C3P0連接池進行配置。
主要包括:初始化連接池時建立多少個連接、連接池最少多少個連接最多容納多少連接、每個連接的生存時間、連接池能同時允許多少個操作進行,以及對具體數據庫連接的配置:數據庫的驅動、數據庫的URL、數據庫登錄名、數據庫密碼、對這個數據庫的連接池的細化配置(比如初始化時建立多少連接,最多最少連接數等等)。一個數據庫的連接池配置用一個<named-config name="標識"> </name-config>節點來定義。在C3P0Utils中創建連接池時把 “標識” 作為連接池的構造函數的參數傳入,則C3P0在配置文件中找到同名節點,按照這個節點的配置來創建相應配置的連接池。配置步驟如下:
在src目錄下新建一個名叫 c3p0-config.xml 的文件,注意,必須是這個文件名。
然后在這個文件中進行配置上面講到的各項:
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <!--默認配置--> <default-config> <property name="initialPoolSize">10</property> <property name="maxIdleTime">30</property> <property name="maxPoolSize">100</property> <property name="minPoolSize">10</property> <property name="maxStatements">200</property> </default-config> <!--配置連接池mysql--> <named-config name="mysql"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/CoupleSpace</property> <property name="user">root</property> <property name="password">root</property> <property name="initialPoolSize">10</property> <property name="maxIdleTime">30</property> <property name="maxPoolSize">100</property> <property name="minPoolSize">10</property> <property name="maxStatements">200</property> </named-config> <!--配置連接池2--> ...... <!--配置連接池3--> ...... <!--配置連接池4--> ...... </c3p0-config>
更細致化的有以下配置:
<!--acquireIncrement:鏈接用完了自動增量3個。 --> <property name="acquireIncrement">3</property> <!--acquireRetryAttempts:鏈接失敗后重新試30次。--> <property name="acquireRetryAttempts">30</property> <!--acquireRetryDelay;兩次連接中間隔1000毫秒。 --> <property name="acquireRetryDelay">1000</property> <!--autoCommitOnClose:連接關閉時默認將所有未提交的操作回滾。 --> <property name="autoCommitOnClose">false</property> <!--automaticTestTable:c3p0測試表,沒什么用。--> <property name="automaticTestTable">Test</property> <!--breakAfterAcquireFailure:出錯時不把正在提交的數據拋棄。--> <property name="breakAfterAcquireFailure">false</property> <!--checkoutTimeout:100毫秒后如果sql數據沒有執行完將會報錯,如果設置成0,那么將會無限的等待。 --> <property name="checkoutTimeout">100</property> <!--connectionTesterClassName:通過實現ConnectionTester或QueryConnectionTester的類來測試連接。類名需制定全路徑。Default: com.mchange.v2.c3p0.impl.DefaultConnectionTester--> <property name="connectionTesterClassName"></property> <!--factoryClassLocation:指定c3p0 libraries的路徑,如果(通常都是這樣)在本地即可獲得那么無需設置,默認null即可。--> <property name="factoryClassLocation">null</property> <!--forceIgnoreUnresolvedTransactions:作者強烈建議不使用的一個屬性。--> <property name="forceIgnoreUnresolvedTransactions">false</property> <!--idleConnectionTestPeriod:每60秒檢查所有連接池中的空閑連接。--> <property name="idleConnectionTestPeriod">60</property> <!--initialPoolSize:初始化時獲取三個連接,取值應在minPoolSize與maxPoolSize之間。 --> <property name="initialPoolSize">3</property> <!--maxIdleTime:最大空閑時間,60秒內未使用則連接被丟棄。若為0則永不丟棄。--> <property name="maxIdleTime">60</property> <!--maxPoolSize:連接池中保留的最大連接數。 --> <property name="maxPoolSize">15</property> <!--maxStatements:最大鏈接數。--> <property name="maxStatements">100</property> <!--maxStatementsPerConnection:定義了連接池內單個連接所擁有的最大緩存statements數。Default: 0 --> <property name="maxStatementsPerConnection"></property> <!--numHelperThreads:異步操作,提升性能通過多線程實現多個操作同時被執行。Default: 3--> <property name="numHelperThreads">3</property> <!--overrideDefaultUser:當用戶調用getConnection()時使root用戶成為去獲取連接的用戶。主要用於連接池連接非c3p0的數據源時。Default: null--> <property name="overrideDefaultUser">root</property> <!--overrideDefaultPassword:與overrideDefaultUser參數對應使用的一個參數。Default: null--> <property name="overrideDefaultPassword">password</property> <!--password:密碼。Default: null--> <property name="password"></property> <!--preferredTestQuery:定義所有連接測試都執行的測試語句。在使用連接測試的情況下這個一顯著提高測試速度。注意: 測試的表必須在初始數據源的時候就存在。Default: null--> <property name="preferredTestQuery">select id from test where id=1</property> <!--propertyCycle:用戶修改系統配置參數執行前最多等待300秒。Default: 300 --> <property name="propertyCycle">300</property> <!--testConnectionOnCheckout:因性能消耗大請只在需要的時候使用它。Default: false --> <property name="testConnectionOnCheckout">false</property> <!--testConnectionOnCheckin:如果設為true那么在取得連接的同時將校驗連接的有效性。Default: false --> <property name="testConnectionOnCheckin">true</property> <!--user:用戶名。Default: null--> <property name="user">root</property> <!--usesTraditionalReflectiveProxies:動態反射代理。Default: false--> <property name="usesTraditionalReflectiveProxies">false</property>
(另:有人試過用properties文件來配置C3P0,通過setProperties(properties)方法並不能成功設置連接池的屬性,而需要一條條屬性去設置setXXX(properties.get(XX))才能成功設置。我的理解是:連接池本身有一個屬性叫做properties,然后調用setProperties()方法只是把配置文件賦值給了連接池的properties屬性而已,而不是讀取這個配置文件並把這個配置文件中的key-value對提取出來並set給連接池的相應屬性。所以,還需要手動加載這個文件,並且逐條屬性去設置給連接池。而我們這里用xml文件去配置的話,C3P0創建連接池時是自動根據這個文件的配置去創建的,所以配置生效。)
3:創建C3P0Utils類,定義獲取connection、釋放connection的方法
public class C3p0Utils { static org.apache.log4j.Logger logger=org.apache.log4j.Logger.getLogger(C3p0Utils.class.getName()); //通過標識名來創建相應連接池 static ComboPooledDataSource dataSource=new ComboPooledDataSource("mysql"); //從連接池中取用一個連接 public static Connection getConnection(){ try { return dataSource.getConnection(); } catch (Exception e) { logger.error("Exception in C3p0Utils!", e); throw new MyError("數據庫連接出錯!", e); } } //釋放連接回連接池 public static void close(Connection conn,PreparedStatement pst,ResultSet rs){ if(rs!=null){ try { rs.close(); } catch (SQLException e) { logger.error("Exception in C3p0Utils!", e); throw new MyError("數據庫連接關閉出錯!", e); } } if(pst!=null){ try { pst.close(); } catch (SQLException e) { logger.error("Exception in C3p0Utils!", e); throw new MyError("數據庫連接關閉出錯!", e); } } if(conn!=null){ try { conn.close(); } catch (SQLException e) { logger.error("Exception in C3p0Utils!", e); throw new MyError("數據庫連接關閉出錯!", e); } } } }
二:創建DBUtils_BO類來封裝一次數據庫操作
BO類中主要封裝了一個數據庫連接、一個Statement/PreStatement(推薦,更安全)、一個結果集。這樣,在DAO層的一次數據庫操作就可封裝在這個bo對象里,直接把這個對象傳給數據庫操作類執行數據庫操作即可。操作完成后的結果集也封裝在了這個bo對象里,在需要提取結果的地方通過這個bo對象的rs字段進行結果提取即可。
public class DBUtil_BO { public Connection conn = null; public PreparedStatement st = null; public ResultSet rs = null; public DBUtil_BO() { super(); } }
三:創建DBUtils數據庫操作類進行具體的數據庫操作
數據庫操作類主要負責執行數據庫操作封裝類bo對象的操作以及釋放連接回連接池,並把結果封裝回bo對象。
public class DBUtils { static org.apache.log4j.Logger logger=org.apache.log4j.Logger.getLogger(DBUtils.class.getName()); private static void realseSource( Connection _conn, PreparedStatement _st,ResultSet _rs){ C3p0Utils.close(_conn,_st,_rs); } public static void realseSource(DBUtil_BO _vo){ if(_vo!=null){ realseSource(_vo.conn, _vo.st, _vo.rs); } } //注意:查詢操作完成后,因為還需提取結果集中信息,所以仍保持連接,在結果集使用完后才通過DBUtils.realseSource()手動釋放連接 public static void executeQuery(DBUtil_BO vo) { try{ vo.rs = vo.st.executeQuery(); }catch (SQLException e){ realseSource(vo); String uuid=Uuid.create().toString(); logger.error("UUID:"+uuid+", SQL語法有誤: ",e); throw new MyException("err.user.dao.jdbc",e,uuid); } } //而update操作完成后就可以直接釋放連接了,所以在方法末尾直接調用了realseSourse() public static void executeUpdate(DBUtil_BO vo) { Connection conn = vo.conn; PreparedStatement st = vo.st; try { st.executeUpdate(); } catch (SQLException e) { realseSource(conn, st, null); String uuid=Uuid.create().toString(); logger.error("UUID:"+uuid+", SQL語法有誤: ",e); throw new MyException("err.user.dao.jdbc",e,uuid); } realseSource(conn, st,null ); } }
四:在項目中使用連接池
主要分5步:
1:創建連接池datasourse、bo、utils對象
2:bo對象的連接是在連接池獲取的,所以bo.conn=C3P0Utils.getConnection();
3:定義數據庫操作語句sql,並用bo對象的preparestatement預處理
4:把封裝了數據庫操作的bo對象傳給utils對象執行數據庫操作
5:bo對象釋放連接,以便下一次獲取連接。(查詢操作的需要顯示釋放)
dbBo.conn=C3p0Utils.getConnection();//取用一個連接 String sql = "select * from users where username=? and password=?"; dbBo.st=dbBo.conn.prepareStatement(sql);//預處理sql語句 dbBo.st.setString(1, name); dbBo.st.setString(2, pass); //此時dbBo對象已經封裝了一個數據庫連接以及要執行的操作 dbUtils.executeQuery(dbBo);//通過數據庫操作類來執行這個操作封裝類,結果封裝回這個操作封裝類 //從dbBo類提取操作結果 if (dbBo.rs.next()) { uid =dbBo.rs.getInt("userid"); } //結果集遍歷完了,手動釋放連接回連接池 dbUtils.realseSource(dbBo);