本文包括
傳統(tǒng)JDBC的缺點
連接池原理
自定義連接池
開源數據庫連接池
DBCP連接池
C3P0連接池
Tomcat內置連接池
1、傳統(tǒng)JDBC的缺點
用戶每次請求都需要向數據庫獲得鏈接,而數據庫創(chuàng)建連接通常需要消耗相對較大的資源,創(chuàng)建時間也較長。
假設網站一天10萬訪問量,數據庫服務器就需要創(chuàng)建10萬次連接,極大的浪費數據庫的資源,并且極易造成數據庫服務器內存溢出、拓機。
2、連接池原理
在服務器端一次性創(chuàng)建多個連接,將多個連接保存在一個連接池對象中,當應用程序的請求需要操作數據庫時,不會為請求創(chuàng)建新的連接,而是直接從連接池中獲得一個連接,操作數據庫結束之后,并不需要真正關閉連接,而是將連接放回到連接池中。
節(jié)省創(chuàng)建連接、釋放連接 資源
3、自定義連接池
-
編寫連接池需實現(xiàn)javax.sql.DataSource接口。DataSource接口中定義了兩個重載的getConnection方法:
Connection.getConnection() Connection.getConnection(String username, String password) -
自定義一個類,實現(xiàn)DataSource接口,并實現(xiàn)連接池功能的步驟:
在自定義類的構造函數中批量創(chuàng)建Connection,并把創(chuàng)建的連接保存到一個集合對象中(LinkedList)。
在自定義類中實現(xiàn)Connection.getConnection方法,讓getConnection方法每次調用時,從集合對象中取出一個Connection返回給用戶。
-
當用戶使用完Connection,不能調用Connection.close()方法,而要使用連接池提供關閉方法,即將Connection放回到連接池之中(把Connection存入集合對象中)。
Connection對象應保證將自己返回到連接池的集合對象中,而不要把Connection還給數據庫。
如果用戶習慣調用Connection.close()方法,則可以使用動態(tài)代理來增強原有方法。
-
demo:
public class MyDataSource implements DataSource { // 鏈表 --- 實現(xiàn) 棧結構 、隊列 結構 private LinkedList<Connection> dataSources = new LinkedList<Connection>(); public MyDataSource() { // 一次性創(chuàng)建10個連接 for (int i = 0; i < 10; i++) { try { Connection conn = JDBCUtils.getConnection(); // 將連接加入連接池中 dataSources.add(conn); } catch (Exception e) { e.printStackTrace(); } } } @Override public Connection getConnection() throws SQLException { // 取出連接池中一個連接 final Connection conn = dataSources.removeFirst(); // 刪除第一個連接返回 System.out.println("取出一個連接剩余 " + dataSources.size() + "個連接!"); // 將目標Connection對象進行增強 Connection connProxy = (Connection) Proxy.newProxyInstance(conn .getClass().getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler() { // 執(zhí)行代理對象任何方法 都將執(zhí)行 invoke @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("close")) { // 需要加強的方法 // 不將連接真正關閉,將連接放回連接池 releaseConnection(conn); return null; } else { // 不需要加強的方法 return method.invoke(conn, args); // 調用真實對象方法 } } }); return connProxy; } // 將連接放回連接池 public void releaseConnection(Connection conn) { dataSources.add(conn); System.out.println("將連接 放回到連接池中 數量:" + dataSources.size()); } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } @Override public PrintWriter getLogWriter() throws SQLException { // TODO Auto-generated method stub return null; } @Override public int getLoginTimeout() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public void setLogWriter(PrintWriter out) throws SQLException { // TODO Auto-generated method stub } @Override public void setLoginTimeout(int seconds) throws SQLException { // TODO Auto-generated method stub } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { // TODO Auto-generated method stub return false; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { // TODO Auto-generated method stub return null; } }
4、開源數據庫連接池
現(xiàn)在很多WEB服務器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的實現(xiàn),即連接池的實現(xiàn)。通常我們把DataSource的實現(xiàn),按其英文含義稱之為數據源,數據源中都包含了數據庫連接池的實現(xiàn)。
-
也有一些開源組織提供了數據源的獨立實現(xiàn):
Apache commons-dbcp 數據庫連接池
C3P0 數據庫連接池
Apache Tomcat內置的連接池(apache dbcp)
實際應用時不需要編寫連接數據庫代碼,直接從數據源獲得數據庫的連接。程序員編程時也應盡量使用這些數據源的實現(xiàn),以提升程序的數據庫訪問性能。
原來由jdbcUtil創(chuàng)建連接,現(xiàn)在由dataSource創(chuàng)建連接,為實現(xiàn)不和具體數據綁定,因此datasource也應采用配置文件的方法獲得連接。
-
在Apache官網下載時,注意這些開源連接池的版本,要與本地JDK與JDBC版本對應!
比如說:Apache Commons DBCP 2.1.1 for JDBC 4.1 (Java 7.0+)
我在MyEclipse創(chuàng)建工程時設置JAVASE-1.6,而現(xiàn)在最新的DBCP版本為2.1.1,它要求的是Java 7.0+,所以在import相關包時,會報錯,提示需要configure build path。
解決方法:下載低版本的兩個jar包即可。
5、DBCP連接池
-
DBCP 是 Apache 軟件基金組織下的開源連接池實現(xiàn),使用DBCP連接池,需要在build path中增加如下兩個 jar 文件:
Commons-dbcp.jar:連接池的實現(xiàn)
Commons-pool.jar:連接池實現(xiàn)的依賴庫
Tomcat 的連接池正是采用該連接池來實現(xiàn)的。該數據庫連接池既可以與應用服務器整合使用,也可由應用程序獨立使用。
-
使用DBCP連接池,需要有driverclass、url、username、password,有兩種方法配置:
-
使用BasicDataSource.setXXX(XXX)方法手動設置四個參數
@Test public void demo1() throws SQLException { // 使用BasicDataSource 創(chuàng)建連接池 BasicDataSource basicDataSource = new BasicDataSource(); // 創(chuàng)建連接池 一次性創(chuàng)建多個連接池 // 連接池 創(chuàng)建連接 ---需要四個參數 basicDataSource.setDriverClassName("com.mysql.jdbc.Driver"); basicDataSource.setUrl("jdbc:mysql:///day14"); basicDataSource.setUsername("root"); basicDataSource.setPassword("123"); // 從連接池中獲取連接 Connection conn = basicDataSource.getConnection(); String sql = "select * from account"; PreparedStatement stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery(); while (rs.next()) { System.out.println(rs.getString("name")); } JDBCUtils.release(rs, stmt, conn); } -
編寫properties配置文件,在測試代碼中創(chuàng)建Properties對象加載文件
dbcp.properties文件:
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql:///day14 username=root password=123測試代碼:
@Test public void demo2() throws Exception { // 讀取dbcp.properties ---- Properties Properties properties = new Properties(); properties.load(new FileInputStream(this.getClass().getResource( "/dbcp.properties").getFile())); DataSource basicDataSource = BasicDataSourceFactory .createDataSource(properties); // 從連接池中獲取連接 Connection conn = basicDataSource.getConnection(); String sql = "select * from account"; PreparedStatement stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery(); while (rs.next()) { System.out.println(rs.getString("name")); } JDBCUtils.release(rs, stmt, conn); }
-
6、C3P0連接池
使用C3P0連接池,需要在build path中添加一個jar包:c3p0-版本號.jar
-
Basic Pool Configuration 基本屬性
acquireIncrement 當連接池連接用完了,根據該屬性決定一次性新建多少連接
initialPoolSize 初始化一次性創(chuàng)建多少個連接
maxPoolSize 最大連接數
-
maxIdleTime 最大空閑時間,當連接池中連接經過一段時間沒有使用,根據該數據進行釋放
maxIdleTime
Default: 0
Seconds a Connection can remain pooled but unused before being discarded. Zero means idle connections never expire. [See "Basic Pool Configuration"]
minPoolSize 最小連接池尺寸總而言之:當創(chuàng)建連接池時,一次性創(chuàng)建initialPoolSize 個連接,當連接使用完一次性創(chuàng)建 acquireIncrement 個連接,連接最大數量 maxPoolSize ,當連接池連接數量大于 minPoolSize ,經過maxIdleTime 連接沒有使用, 該連接將被釋放。
-
使用C3P0連接池,同樣有兩種方法配置
-
手動配置(除了設置四個必須的參數,還可以設置Basic Pool Configuration)
@Test public void demo1() throws Exception { // 創(chuàng)建一個連接池 ComboPooledDataSource dataSource = new ComboPooledDataSource(); // 手動設置四個參數 dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql:///day14"); dataSource.setUser("root"); dataSource.setPassword("123"); //dataSource.setMaxPoolSize(40); // 手動設置Basic Pool Configuration Connection conn = dataSource.getConnection(); String sql = "select * from account"; PreparedStatement stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery(); while (rs.next()) { System.out.println(rs.getString("name")); } JDBCUtils.release(rs, stmt, conn); } -
使用c3p0-config.xml配置:
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:///day14</property> <property name="user">root</property> <property name="password">123</property> <property name="acquireIncrement">10</property> <property name="initialPoolSize">10</property> <property name="maxPoolSize">100</property> <property name="maxIdleTime">60</property> <property name="minPoolSize">5</property> </default-config> <named-config name="itcast"> <!-- 自定義配置 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql:///day14</property> <property name="user">root</property> <property name="password">123</property> <property name="acquireIncrement">10</property> <property name="initialPoolSize">10</property> <property name="maxPoolSize">100</property> <property name="maxIdleTime">60</property> <property name="minPoolSize">5</property> </named-config> </c3p0-config>測試代碼:
@Test public void demo2() throws SQLException { // 使用c3p0配置文件 // 自動加載src/c3p0-config.xml,不需要像前文的dbcpconfig.properties那樣加載配置文件! // ComboPooledDataSource dataSource = new ComboPooledDataSource(); // 使用默認配置 ComboPooledDataSource dataSource = new ComboPooledDataSource("itcast"); // 使用自定義配置 Connection conn = dataSource.getConnection(); String sql = "select * from account"; PreparedStatement stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery(); while (rs.next()) { System.out.println(rs.getString("name")); } JDBCUtils.release(rs, stmt, conn); }
-
7、Tomcat內置連接池
因為Tomcat和 dbcp 都是Apache公司項目,Tomcat內部連接池就是dbcp。
Tomcat支持Servlet、JSP等,類似于容器,但并不支持所有JavaEE規(guī)范,JNDI就是JavaEE規(guī)范之一。
開發(fā)者通過JNDI方式可以訪問Tomcat內置連接池。
使用Tomcat內置連接池的前提
將web工程部署到Tomcat三種方式: 配置server.xml <Context> 元素、配置獨立xml文件 <Context> 元素 、直接將網站目錄復制Tomcat/webapps
虛擬目錄 ---- <Context> 元素-
若想使用Tomcat內置連接池,必須要在Context元素中添加Resource標簽,具體代碼如下:
<Context> <Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="root" password="123" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/day14"/> </Context> -
在哪里配置元素?有三個位置可以配置:
tomcat安裝目錄/conf/context.xml --------- 對當前Tomcat內部所有虛擬主機中任何工程都有效
tomcat安裝目錄/conf/Catalina/虛擬主機目錄/context.xml -------- 對當前虛擬主機任何工程都有效
-
在web工程根目錄/META-INF/context.xml ------- 對當前工程有效 (常用?。?/strong>
具體做法:MyEclipse中,在項目根目錄的WebRoot/META-INF中,新建context.xml文件,配置代碼如下:
<?xml version="1.0" encoding="UTF-8"?> <!-- tomcat啟動時,加載該配置文件,創(chuàng)建連接池,將連接池保存tomcat容器中 --> <Context> <Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="root" password="123" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/day14"/> </Context>
JNDI技術簡介
JNDI(Java Naming and Directory Interface),Java命名和目錄接口,它對應于J2SE中的javax.naming包,
這套API的主要作用在于:它可以把Java對象放在一個容器中(支持JNDI容器 Tomcat),并為容器中的java對象取一個名稱,以后程序想獲得Java對象,只需通過名稱檢索即可。
其核心API為Context,它代表JNDI容器,其lookup方法為檢索容器中對應名稱的對象。
使用JNDI訪問Tomcat內置連接池
將數據庫驅動的jar包復制到Tomcat安裝目錄/lib中,這樣Tomcat服務器才能找到數據庫驅動。
編寫訪問JNDI程序,運行在Tomcat內部,所以通常是運行在Servlet、JSP中。
在Tomcat啟動時,自動加載配置文件(context.xml),創(chuàng)建數據庫連接池,該連接池由Tomcat管理。

-
demo:
public class TomcatServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { // 創(chuàng)建檢索對象 Context initCtx = new InitialContext(); // 默認查找頂級java,名稱串固定:java:comp/env Context envCtx = (Context) initCtx.lookup("java:comp/env"); // 根據設置的名稱查找連接池對象 DataSource ds = (DataSource) envCtx.lookup("jdbc/TestDB"); // 獲得連接池中一個連接,接下來的代碼和連接池無關 Connection conn = ds.getConnection(); String sql = "select * from account"; PreparedStatement stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery(); while (rs.next()) { System.out.println(rs.getString("name")); } JDBCUtils.release(rs, stmt, conn); } catch (NamingException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
JDBC文集:
Java 與數據庫的橋梁——JDBC:http://www.itdecent.cn/p/c0acbd18794c
JDBC 進階——連接池:http://www.itdecent.cn/p/ad0ff2961597
JDBC 進階——元數據:http://www.itdecent.cn/p/36d5d76342f1
JDBC框架——DBUtils:http://www.itdecent.cn/p/10241754cdd7