1. 程式人生 > >JDBC-資料庫連線池/操作

JDBC-資料庫連線池/操作

1.資料庫連線池的必要性

不使用資料庫連線池:

在使用開發基於資料庫的web程式時,傳統的模式基本是按以下步驟:  
 在主程式(如servlet、beans、DAO)中建立資料庫連線。 
 進行sql操作
 斷開資料庫連線。
這種模式開發,存在的問題:
 普通的JDBC資料庫連線使用 DriverManager 來獲取,每次向資料庫建立連線的時候都要將 Connection 載入到記憶體中,再驗證使用者名稱和密碼(得花費0.05s~1s的時間)。需要資料庫連線的時候,就向資料庫要求一個,執行完成後再斷開連線。這樣的方式將會消耗大量的資源和時間。資料庫的連線資源並沒有得到很好的重複利用.若同時有幾百人甚至幾千人線上,頻繁的進行資料庫連線操作將佔用很多的系統資源,嚴重的甚至會造成伺服器的崩潰。
 對於每一次資料庫連線,使用完後都得斷開。否則,如果程式出現異常而未能關閉,將會導致資料庫系統中的記憶體洩漏,最終將導致重啟資料庫。
 這種開發不能控制被建立的連線物件數,系統資源會被毫無顧及的分配出去,如連線過多,也可能導致記憶體洩漏,伺服器崩潰。

2.資料庫連線池的基本思想

  資料庫連線池的基本思想就是為資料庫連線建立一個“緩衝池”。預先在緩衝池中放入一定數量的連線,當需要建立資料庫連線時,
只需從“緩衝池”中取出一個,使用完畢之後再放回去。資料庫連線池負責分配、管理和釋放資料庫連線,
它允許應用程式重複使用一個現有的資料庫連線,而不是重新建立一個。

  資料庫連線池在初始化時將建立一定數量的資料庫連線放到連線池中,這些資料庫連線的數量是由最小資料庫連線數來設定的。無論這些
資料庫連線是否被使用,連線池都將一直保證至少擁有這麼多的連線數量。連線池的最大資料庫連線數量限定了這個連線池能佔有的
最大連線數,當應用程式向連線池請求的連線數超過最大連線數量時,這些請求將被加入到等待佇列中。

3.資料庫連線池技術的優點

資源重用:
   由於資料庫連線得以重用,避免了頻繁建立,釋放連線引起的大量效能開銷。在減少系統消耗的基礎上,另一方面也增加了系統執行環境的平穩性。
更快的系統反應速度:
   資料庫連線池在初始化過程中,往往已經建立了若干資料庫連線置於連線池中備用。此時連線的初始化工作均已完成。對於業務請求處理而言,直接利用現有可用連線,避免了資料庫連線初始化和釋放過程的時間開銷,從而減少了系統的響應時間
新的資源分配手段:
   對於多應用共享同一資料庫的系統而言,可在應用層通過資料庫連線池的配置,實現某一應用最大可用資料庫連線數的限制,避免某一應用獨佔所有的資料庫資源
統一的連線管理,避免資料庫連線洩露:
   在較為完善的資料庫連線池實現中,可根據預先的佔用超時設定,強制回收被佔用連線,從而避免了常規資料庫連線操作中可能出現的資源洩露

 4.兩種開源的資料庫連線池

JDBC 的資料庫連線池使用 javax.sql.DataSource 來表示,DataSource 只是一個介面,該介面通常由伺服器(Weblogic, WebSphere, Tomcat)提供實現,也有一些開源組織提供實現:
 DBCP 資料庫連線池
 C3P0 資料庫連線池
DataSource 通常被稱為資料來源,它包含連線池和連線池管理兩個部分,習慣上也經常把 DataSource 稱為連線池

 DBCP資料來源

DBCP 是 Apache 軟體基金組織下的開源連線池實現,該連線池依賴該組織下的另一個開源系統:Common-pool. 如需使用該連線池實現,應在系統中增加如下兩個 jar 檔案:
 Commons-dbcp.jar:連線池的實現
 Commons-pool.jar:連線池實現的依賴庫
Tomcat 的連線池正是採用該連線池來實現的。該資料庫連線池既可以與應用伺服器整合使用,也可由應用程式獨立使用。
注意:
  資料來源和資料庫連線不同,資料來源無需建立多個,它是產生資料庫連線的工廠,因此整個應用只需要一個數據源即可。
  當資料庫訪問結束後,程式還是像以前一樣關閉資料庫連線:conn.close(); 但conn.close()並沒有關閉資料庫的物理連線,它僅僅把資料庫連線釋放,歸還給了資料庫連線池。

C3P0資料來源

C3P0是一個開源的JDBC連線池,它實現了資料來源和JNDI繫結,支援JDBC3規範和JDBC2的標準擴充套件。目前使用它的開源專案有Hibernate,Spring等。

c3p0與dbcp區別

dbcp沒有自動回收空閒連線的功能

c3p0有自動回收空閒連線功能

Apache—DBUtils簡介

commons-dbutils 是 Apache 組織提供的一個開源 JDBC工具類庫,它是對JDBC的簡單封裝,學習成本極低,並且使用dbutils
能極大簡化jdbc編碼的工作量,同時也不會影響程式的效能。

DbUtils類

DbUtils :提供如關閉連線、裝載JDBC驅動程式等常規工作的工具類,裡面的所有方法都是靜態的。主要方法如下:

  public static void close(…) throws java.sql.SQLException: DbUtils類提供了三個過載的關閉方法。這些方法檢查所提供的引數是不是NULL,如果不是的話,它們就關閉Connection、Statement和ResultSet。
  public static void closeQuietly(…): 這一類方法不僅能在Connection、Statement和ResultSet為NULL 情況下避免關閉,還能隱藏一些在程式中丟擲的SQLEeception。
  public static void commitAndClose(Connection conn)throws SQLException 用來提交連線的事務,然後關閉連線
  public static void commitAndCloseQuietly(Connection conn): 用來提交連線的事務,然後關閉連線,並且在關閉連線時不丟擲SQL異常。
  public static boolean loadDriver(java.lang.String driverClassName):這一方裝載並註冊JDBC驅動程式,如果成功就返回true。使用該方法,你不需要捕捉這個異常ClassNotFoundException。

JDBC版

package com.jdbc.dbutils;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import org.apache.commons.dbutils.DbUtils;

public class JDBCTools {
	private static String driver;
	private static String url;
	private static String user;
	private static String password;
	
	static{
		//載入jdbc.properties資源配置檔案
		Properties pro = new Properties();
		try {
			pro.load(ClassLoader.getSystemResourceAsStream("jdbc.properties"));
			
			//初始化引數
			driver = pro.getProperty("driver");
			url = pro.getProperty("url");
			user = pro.getProperty("user");
			password = pro.getProperty("password");
			
			DbUtils.loadDriver(driver);
		}  catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 獲取資料庫連線
	 * @return Connection 資料庫連線物件
	 * @throws SQLException
	 */
	public static Connection getConnection() throws SQLException{
		Connection conn = null;
		conn = DriverManager.getConnection(url, user, password);
		return conn;
	}
	
	/**
	 * 釋放資料庫連線資源
	 * @param rs ResultSet 查詢資料庫的結果集
	 * @param st Statement 執行SQL語句的Statement
	 * @param conn Connection 資料庫連線
	 */
	public static void free(ResultSet rs,Statement st,Connection conn){
		DbUtils.closeQuietly(rs);
		DbUtils.closeQuietly(st);
		DbUtils.closeQuietly(conn);
	}
	
	public static void free(ResultSet rs){
		try {
			DbUtils.close(rs);
		} catch (SQLException e) {
			e.printStackTrace();
		} 
	}
	
	public static void free(Statement st){
		try {
			DbUtils.close(st);
		} catch (SQLException e) {
			e.printStackTrace();
		} 
	}

	public static void free(Connection conn){
		try {
			DbUtils.close(conn);
		} catch (SQLException e) {
			e.printStackTrace();
		} 
	}
	
	public static void freeCommit(Connection conn){
		DbUtils.commitAndCloseQuietly(conn);
	}
}

QueryRunner類

該類封裝了SQL的執行,是執行緒安全的。可以實現增、刪、改、查、批處理、考慮了事務處理需要共用Connection。

該類最主要的就是簡單化了SQL查詢,它與ResultSetHandler組合在一起使用可以完成大部分的資料庫操作,能夠大大減少編碼量。

QueryRunner類提供了兩個構造方法:

QueryRunner():預設的構造方法
QueryRunner(DataSource ds):需要一個 javax.sql.DataSource 來作引數的構造方法。

 使用QueryRunner類實現更新

public int update(Connection conn, String sql, Object[] params) throws SQLException:用來執行一個更新(插入、更新或刪除)操作。

public int update(Connection conn, String sql) throws SQLException:用來執行一個不需要置換引數的更新操作。
	@Test
	public void testQueryRunnerUpdate() {
		// 1.建立QueryRunner的例項
		QueryRunner qr = new QueryRunner();

		Connection conn = null;

		try {
			// 2.獲取連線
			conn = JDBCTools.getConnection();

			String sql = "insert into t_stu values(null,?,?,?,?)";
			// 3、使用update方法
			qr.update(conn, sql, "陳喬恩", "女", "H5", "170102班");
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			JDBCTools.free(null, null, conn);
		}
	}
	//要考慮事務的話,連線不關閉
	public static int update(Connection conn, String sql, Object[] params) throws SQLException{
		// 1.建立QueryRunner的例項
		QueryRunner qr = new QueryRunner();
		return qr.update(conn, sql, params);
	}

 使用QueryRunner類實現查詢

public Object query(Connection conn, String sql, ResultSetHandler rsh,Object... params) throws SQLException:執行一個查詢操作,在這個查詢中,物件陣列中的每個元素值被用來作為查詢語句的置換引數。該方法會自行處理 PreparedStatement 和 ResultSet 的建立和關閉。
public Object query(String sql, ResultSetHandler rsh, Object... params) throws SQLException: 幾乎與第一種方法一樣;唯一的不同在於它不將資料庫連線提供給方法,並且它是從提供給構造方法的資料來源(DataSource) 或使用的setDataSource 方法中重新獲得 Connection。
public Object query(Connection conn, String sql, ResultSetHandler rsh) throws SQLException : 執行一個不需要置換引數的查詢操作。
public Object query( String sql, ResultSetHandler rsh) throws SQLException : 執行一個不需要置換引數的查詢操作。

ResultSetHandler介面

該介面用於處理 java.sql.ResultSet,將資料按要求轉換為另一種形式。

ResultSetHandler 介面提供了一個單獨的方法:Object handle (java.sql.ResultSet  rs)

該方法的返回值將作為QueryRunner類的query()方法的返回值。
	@Test
	public void testResultSetHandler() {
		// 1.建立QueryRunner的例項
		QueryRunner qr = new QueryRunner();

		Connection conn = null;

		try {
			// 2.獲取連線
			conn = JDBCTools.getConnection();
			
			class MyResultSetHandler implements ResultSetHandler{
				@Override
				public Object handle(ResultSet rs) throws SQLException {
					Student stu = new Student();
					if (rs.next()) {
						stu.setId(rs.getInt(1));
						stu.setSname(rs.getString(2));
						stu.setSex(rs.getString(3));
						stu.setMajor(rs.getString(4));
						stu.setClasses(rs.getString(5));
					}
					return stu;
				}
			}

			String sql = "select sno,sname,sex,major,classes from t_stu where sno =?";
			// 3、使用query方法
			// QueryRunner 的 query 方法的返回值取決於其 ResultSetHandler 引數的handle 方法的返回值
			Object obj = qr.query(conn, sql, new MyResultSetHandler(),1);

			System.out.println(obj);
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			JDBCTools.free(null, null, conn);
		}
	}
ArrayHandler:把結果集中的第一行資料轉成物件陣列。
ArrayListHandler:把結果集中的每一行資料都轉成一個數組,再存放到List中。
BeanHandler:將結果集中的第一行資料封裝到一個對應的JavaBean例項中。
BeanListHandler:將結果集中的每一行資料都封裝到一個對應的JavaBean例項中,存放到List裡。
ColumnListHandler:將結果集中某一列的資料存放到List中。
KeyedHandler(name):將結果集中的每一行資料都封裝到一個Map裡,再把這些map再存到一個map裡,其key為指定的key。
MapHandler:將結果集中的第一行資料封裝到一個Map裡,key是列名,value就是對應的值。
MapListHandler:將結果集中的每一行資料都封裝到一個Map裡,然後再存放到List

 BeanHandler實現類

BeanHandler: 把結果集的第一條記錄轉為建立 BeanHandler 物件時傳入的 Class引數對應的物件. 
當JavaBean的屬性名與欄位名不一致時,可以通過指定別名告知屬性名

BeanListHandler實現類

BeanListHandler: 把結果集轉為一個 List, 該 List 不為 null, 但可能為空集合(size() 方法返回 0) 若
SQL 語句的確能夠查詢到記錄, List 中存放建立 BeanListHandler 傳入的 Class物件對應的物件.

MapHandler實現類

MapListHandler實現類

ScalarHandler實現類(可以用來統計)

 ScalarHandler: 把結果集轉為一個數值(可以是任意基本資料型別和字串, Date 等)返回
 ScalarHandler()只取第一行第一列
 ScalarHandler(int columnIndex):取第一行的第columnIndex列
 ScalarHandler(String columnName):取第一行的列名為columnName列的值

QueryLoader類

QueryLoader類是一個從一個檔案載入查詢到一個Map的簡單的類。然後,當需要的時候,你從 Map 中選擇一些查詢。
即把sql語句也配置到檔案中
@Test
	public void  testQueryLoader(){
		try {
			// /表示類路徑的根目錄
			Map<String,String> sqls = QueryLoader.instance().load("/sql.properties");
			String sql = sqls.get("insert");
			System.out.println(sql);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}