1. 數據庫連接池
JDBC部分的前兩個總結主要總結了一下JDBC的基本操作,而且有個共同點,就是應用程序都是直接獲取數據庫連接的。這會有個弊端:用戶每次請求都需要向數據庫獲得連接,而數據庫創建連接通常需要消耗相對較大的資源,創建時間也較長。假設一個網站每天有10萬次訪問量,那么數據庫服務器就需要創建10萬次連接,這極大的浪費數據庫的資源,並且極易造成數據庫服務器內存溢出、脫機。
為了解決上述問題,數據庫連接池的技術便得到了廣泛的使用。為了優化性能,應用程序一啟動,就向數據庫要一批連接放到池(也就是一個集合而已)中,當用戶箱操作數據庫的時候,直接找連接池要,當完成對數據庫的操作后,再把連接還給連接池供其他用戶使用。
有了上面的思路,我們可以自己寫一個數據庫的連接池,主要使用動態代理技術。
2. 自己編寫的數據庫連接池
2.1 連接池JdbcPool類的實現
編寫連接池需要實現Java.sql.DateSource接口。DateSource接口中定義了兩個重載的getConnection方法:Connection getConnection();
方法和 Connection getConnection(String username, String password);
方法,實現連接池功能的步驟如下:
- 在DateSource構造函數或者靜態代碼塊中批量創建與數據庫的連接,並把創建的連接加入LinkedList對象中;
- 實現getConnection方法,讓getConnection方法每次調用時,從LinkedList中取一個Connection返回給用戶;
- 當用戶使用完Connection,調用Connection.close()方法時,Connection對象應保證將自己返回到LinkedList中(編程的難點,動態代理實現),而不要把conn還給數據庫。
public class JdbcPool implements DataSource { private static LinkedList<Connection> list = new LinkedList<Connection>(); static{ try{ InputStream in = JdbcPool.class.getClassLoader().getResourceAsStream("db.properties"); Properties prop = new Properties(); prop.load(in);//加載配置文件 String driver = prop.getProperty("driver"); String url = prop.getProperty("url"); String username = prop.getProperty("username"); String password = prop.getProperty("password"); Class.forName(driver); //加載驅動 //連接池中創建10個Connection for(int i = 0; i < 10; i++){ Connection conn = DriverManager.getConnection(url, username, password); System.out.println("獲取到了連接" + conn); list.add(conn); } } catch(Exception e){ e.printStackTrace(); throw new ExceptionInInitializerError(e); } } /* * 用動態代理,返回一個代理對象出去,攔截close方法的調用,對close進行增強 */ @Override public synchronized Connection getConnection() throws SQLException { if(list.size() > 0){ final Connection conn = list.removeFirst();//刪掉並返回給conn // return conn;//這里不能直接return,因為用戶使用完了后,調用conn.close()會操作數據庫,並沒有把這個conn返回給連接池中 System.out.println("池大小是" + list.size()); //下面用動態代理技術來寫: //動態代理技術:使用的是攔截技術 return (Connection) Proxy.newProxyInstance(JdbcPool.class.getClassLoader(), new Class[]{Connection.class}, new InvocationHandler() { //這里的第二個參數原來為conn.getClass.getInterface(),不過會出錯,最終改成了new Class[]{Connection.class} //原因見帖子:http://blog.csdn.net/njchenyi/article/details/3091092 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(!method.getName().equals("close")){//如果判斷不是調用close方法,不管就是了 return method.invoke(conn, args); } else{//如果是調用close方法,將conn放到連接池里 list.add(conn); System.out.println(conn + "被還到池中"); System.out.println("池大小為" + list.size()); return null; } } }); } else{ throw new RuntimeException("對不起,數據庫忙"); } } //下面是其他需要實現的方法,默認生成即可,不用寫代碼 @Override ...... }
2.2 Jdbc工具類JdbcUtils的實現
有了上面的連接池,我們在工具類中獲取Collection就不用像原來那樣使用DriverManager來獲取Connection了,我們可以直接使用自己的連接池了,如下:
public class JdbcUtils { private static JdbcPool pool = new JdbcPool();//定義一個連接池 public static Connection getConnection() throws SQLException { return pool.getConnection();//直接從連接池中獲取一個Connection } public static void release(Connection conn, Statement st, ResultSet rs) { if(rs != null){ try{ rs.close(); }catch(Exception e) { e.printStackTrace(); } rs = null; } if(st != null){ try{ st.close(); }catch(Exception e) { e.printStackTrace(); } st = null; } if(conn != null){ try{ conn.close(); }catch(Exception e) { e.printStackTrace(); } conn = null; } } }
2.3 寫一個測試用例
我們寫一個模擬轉賬的程序來測試一下自己寫的連接池能不能正常使用:
public class Demo1 { //模擬轉賬 @Test public void changeAccount () { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; Savepoint sp = null; try{ conn = JdbcUtils.getConnection(); conn.setAutoCommit(false);//相當於開啟事務start transaction String sql1 = "update account set money=money-100 where name=?"; st = conn.prepareStatement(sql1); st.setString(1, "aaa"); st.executeUpdate(); // sp = conn.setSavepoint(); // int x = 1 / 0; //故意讓程序拋異常,用來抓取然后手動回滾,測回滾用的 String sql2 = "update account set money=money+100 where name=?"; st = conn.prepareStatement(sql2); st.setString(1, "bbb"); st.executeUpdate(); conn.commit(); }catch(Exception e){ try { conn.rollback(sp);//回滾到指定位置,該位置之前的sql都被有效處理 conn.commit();//回滾了要記得提交 } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } e.printStackTrace(); }finally{ JdbcUtils.release(conn, st, rs); } } }
3. 開源數據庫連接池
現在很多web服務器(Weblogic,WebSphere, Tomcat)都提供了DataSource的實現,即連接池的實現。通常我們把DataSource的實現,按其英文含義稱之為數據源,數據源中都包含了數據庫連接池的實現。也有一些開源組織提供了數據源的獨立實現:如DBCP數據庫連接池和C3P0數據庫連接池(spring中用的就是這個)。使用這些連接池時不需要編寫連接數據庫的代碼,直接從數據源獲得數據庫的連接,程序員編程時也盡量使用這些數據源的實現,以提升程序的數據庫訪問性能。
下面我們來學習一下這些開源數據庫的連接池。
3.1 DPCP連接池
DBCP是Apache軟件基金組織下的開源連接池實現,使用DBCP數據源需要在程序中加入下面兩個jar包(dbcp的jar包下載地址:http://download.csdn.net/detail/eson_15/9525736):
commons-dbcp.jar
:連接池的實現;commons-pool.jar
:連接池實現的依賴庫
Tomcat的連接池正是采用DBCP連接池來實現的,該數據庫連接池既可以與應用服務器整合使用,也可以獨立使用,下面我們使用DBCP來改寫上面的JdbcUtils工具類:
public class JdbcUtils_DBCP { private static DataSource ds = null;//定義數據源 static{ try{ InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcp.properties"); Properties prop = new Properties(); prop.load(in); BasicDataSourceFactory factory = new BasicDataSourceFactory();//數據源工廠 ds = factory.createDataSource(prop);//工廠產生數據源 System.out.println(ds);//打印出來瞧瞧是何方神聖~~ }catch(Exception e){ throw new ExceptionInInitializerError(e); } } public static synchronized Connection getConnection() throws SQLException { Connection conn = ds.getConnection();//從數據源中拿一個Connection來用~ return conn; } public static void release(Connection conn, Statement st, ResultSet rs) { if(rs != null){ try{ rs.close(); }catch(Exception e) { e.printStackTrace(); } rs = null; } if(st != null){ try{ st.close(); }catch(Exception e) { e.printStackTrace(); } st = null; } if(conn != null){ try{ conn.close(); }catch(Exception e) { e.printStackTrace(); } conn = null; } } }
從上面代碼中可以看出,獲得了數據源dataSource后,就可以直接通過這個數據源拿到Connection,說明拿之前連接池中已經放好了一些Connection了,這些都已經被DBCP封裝好了,我們不用去管,我們需要做的就是在配置文件中做一些配置,DBCP會根據配置文件中的配置去初始化數據庫連接池的。我們看一下都需要配置啥:
#連接設置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/databasename
username=root
password=root
#初始化連接:10個
initialSize=10
#最大連接數量
maxActive=50
#最大空閑連接
maxIdle=20
#最小空閑連接
minIdle=5
#超時等待時間以毫秒為單位 6000毫秒/1000等於60秒
maxWait=60000
#JDBC驅動建立連接時附帶的連接屬性屬性的格式必須為這樣:[屬性名=property;]
#注意:"user" 與 "password" 兩個屬性會被明確地傳遞,因此這里不需要包含他們。
connectionProperties=useUnicode=true;characterEncoding=gbk
#指定由連接池所創建的連接的自動提交(auto-commit)狀態。
defaultAutoCommit=true
#driver default 指定由連接池所創建的連接的只讀(read-only)狀態。
#如果沒有設置該值,則“setReadOnly”方法將不被調用。(某些驅動並不支持只讀模式,如:Informix)
defaultReadOnly=
#driver default 指定由連接池所創建的連接的事務級別(TransactionIsolation)。
#可用值為下列之一:(詳情可見javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
上面就是DBCP連接池的基本配置,我們只要配置好了,它就會自己根據配置文件中的配置進行初始化。
3.2 C3P0連接池
用C3P0數據庫連接池,需要導入下面兩個jar包(c3p0的jar包下載地址:http://download.csdn.net/detail/eson_15/9525734):
c3p0-0.9.5.1.jar
mchange-commons-java-0.2.10.jar
這樣就可以使用C3P0來改寫JdbcUtils工具類了:
public class JdbcUtils_C3P0 { private static ComboPooledDataSource ds = null; static { try { //配置文件可以用properties文件,也可以用xml,這里使用xml配置,下面給出配置好的xml(要放在類路徑下) ds = new ComboPooledDataSource(); //使用默認配置 //ds = new ComboPooledDataSource("mysql"); //指定配置 } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static synchronized Connection getConnection() throws SQLException { Connection conn = ds.getConnection(); return conn; } public static void release(Connection conn, Statement st, ResultSet rs) {....} }
下面看一下C3P0的配置文件c3p0-config.xml:
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <!--默認配置--> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/databasename</property> <property name="user">root</property> <property name="password">root</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> <!-- intergalactoApp adopts a different approach to configuring statement caching --> <property name="maxStatements">0</property> <property name="maxStatementsPerConnection">5</property> <!-- he's important, but there's only one of him --> </default-config> <named-config name="mysql"> <!--mysql的配置,在new ComboPooledDataSource()時候括號中指定,所以還可以再對oracle進行配置--> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/databasename</property> <property name="user">root</property> <property name="password">root</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> <!-- intergalactoApp adopts a different approach to configuring statement caching --> <property name="maxStatements">0</property> <property name="maxStatementsPerConnection">5</property> <!-- he's important, but there's only one of him --> </named-config> </c3p0-config>
我們可以看出,C3P0的配置文件中可以指定哪個數據庫,也可以指定默認的數據庫,這就很方便了,我們在獲得數據源的時候就可以直接用自己配置的參數指定即可。
4.Tomcat配置數據源
這種方式在開發中也用的比較多。Tomcat服務器在啟動時可以幫我們創建一個池,這樣我們可以直接利用這個連接池,但是需要進行配置。在META-INF目錄下新建一個context.xml文檔(也可以在WEB-INF目錄下的web.xml中進行配置),然后在里面進行如下配置
<Context> <Resource name="jdbc/EmployeeDB" auth="Container" type="javax.sql.DataSource" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/day16" maxTotal="8" maxIdle="4"/> </Context>
其他參數可以參照dbcp的配置文件進行設置,因為tomcat連接池內部就是dbcp。然后新建一個servlet,在servlet中編寫如下代碼(Tomcat連接池的模板代碼):
try { Context initCtx = new InitialContext();// 初始化jndi Context envCtx = (Context) initCtx.lookup("java:comp/env");// 得到jndi容器 DataSource ds = (DataSource) envCtx.lookup("jdbc/EmployeeDB");// 從容器中檢索連接池 Connection conn = ds.getConnection(); System.out.println(conn); } catch (Exception e) { e.printStackTrace(); }
啟動服務器,運行該servlet即可在控制台打印出連接信息。
注:如果程序拋出異常,可能是eclipse版本問題,低版本需要將工程中MySQL-connector-java-5.1.26-bin.jar包拷貝到Tomcat服務器的lib文件夾下。
好了,關於JDBC的內容就介紹這么多吧~如有錯誤之處,歡迎留言指正~
原本以為jdbc比較簡單但是 test出的問題也多,后續可能在JavaWeb基礎總結里面 再次總結。