深刻淺出JDBC(一) - Connection與事務介紹

自從數據須要被持久化存儲,程序與數據庫之間的交互就是不可避免的操做。而早期程序與不一樣的數據庫的交互方式不一樣,意味着程序開發不得不面對數據庫的具體實現,一旦切換數據庫,又是新的學習過程,這樣開發人員的效率始終被和數據庫的交互所限制。html

根據軟件設計中的依賴倒置原則,要針對接口編程,不要針對實現編程,於是因爲數據庫的變化而引發實現的變化是違背軟件設計的基本原則的。優化的方式是在數據庫上提供一層抽象接口,不一樣數據庫根據接口完成本身的實現,而用戶使用時,只需面向接口編程,而不用關心內部的實現細節。若是須要更換數據庫,只須要要指定不一樣的數據庫標識便可。java

Java的JDBC(Java DataBase Connectivity)就是一組這樣的抽象API,經過執行SQL語句,爲多種關係型數據庫提供統一訪問。下面以mysql的一個簡單查詢爲例,介紹JDBC的接口和基本構成。mysql

try {
	// 裝載驅動
	Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
	e.printStackTrace();
}

// 數據庫鏈接
Connection conn = null;
// sql執行對象
Statement statement = null;
// 結果集
ResultSet resultSet = null;

try {
	// 獲取mysql數據庫鏈接
	conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/lichao", "root", "root");
	// 建立sql執行對象
	statement = conn.createStatement();
	// 執行sql,返回結果集
	resultSet = statement.executeQuery("select * from user");
	// 遍歷結果集,獲取數據
	while(resultSet.next()){
		Long id = resultSet.getLong("id");
		String name = resultSet.getString("name");
		Integer age = resultSet.getInt("age");
		System.out.println("User[id=" + id + ",name=" + name + ",age=" + age + "]");
	}
} catch (SQLException e) {
	e.printStackTrace();
} finally {
	// 關閉各類資源
	if(resultSet!=null)
		try {
			resultSet.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	if(statement!=null)
		try {
			resultSet.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	if(conn!=null)
		try {
			conn.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
}
  1. Driver:驅動接口,不一樣數據庫提供本身的驅動實現,用戶使用時註冊指定的驅動實現。推薦使用Class.forName()的方式註冊。
  2. DriverManager:驅動管理器,用來管理驅動,以及建立鏈接
  3. Connection:與數據庫的鏈接會話,用來執行sql並返回結果。使用DriverManager.getConnection()建立新鏈接。
  4. Statement:sql執行的具體對象,用戶發送簡單的sql(不含參數)。子接口PreparedStatement,用於發送含有一個或多個參數的sql語句。
  5. ResultSet:數據庫返回的數據集

Connection

connection,官方文檔的定義是程序員

A connection (session) with a specific database. SQL statements are executed and results are returned within the context of a connection.sql

鏈接即一個特定數據庫的會話,在一個鏈接的上下文中,sql語句被執行,而後結果被返回。數據庫

經過getMetaData方法能夠獲取數據庫中描述的表的信息,支持的sql語法,存儲過程以及這個鏈接的其餘性能和能力。apache

setSchema和setCatalog指定數據庫具體的catalog或schema,setReadOnly設置鏈接是否爲可讀模式,setTypeMap指定字段類型和java類的映射關係(若是爲空集合,會覆蓋原有映射map),setNetworkTimeout則是等待數據庫返回的超時時間,setHoldability改變ResultSet在commit以後的Cursor是否關閉。編程

一個Connection對象中,包含的信息很是之多。在平常開發中,最常接觸到的仍是事務相關的操做。api

事務

事務(Transaction),解決的是多個操做執行時,同時成功或同時失敗的問題。好比銀行轉帳時,扣減A的資金和增長B的資金必須一塊兒發生,否則銀行就幹不下去了。session

可是事務的存在,在多用戶同時訪問相同的數據時會產生衝突。可能出現如下幾種不肯定狀況:

更新丟失

兩個事務同時更新一行數據(事務都未提交),一個事務對數據的更新把另外一個事務對數據的更新覆蓋了。

髒讀

一個事務讀到了另外一個事務未提交的數據操做結果(未提交的數據可能會被回滾)

不可重複讀

一個事務對同一行數據重複讀取兩次,可是卻獲得了不一樣的結果。包括兩種狀況:

  1. 虛讀:事務1讀取某數據後,事務2對其作了修改,當事務1再次讀改數據時獲得與前一次不一樣的值。
  2. 幻讀:事務在操做過程當中進行兩次查詢,第二次查詢的結果包含或缺乏了第一次查詢中的數據(通常爲範圍查詢,兩次查詢sql可不同)。這是由於兩次查詢過程當中有另外一事務插入或刪除數據形成的。

爲了解決上述狀況,標準sql規範中,定義了4個事務隔離級別,不一樣的隔離級別對事務的處理不一樣。

  1. Read Uncommitted(未受權讀取):容許髒讀取,但不容許更新丟失。若是一個事務已經開始寫數據,則另一個事務則不容許同時進行寫操做,但容許其餘事務讀此行數據。
  2. Read Committed(受權讀取):容許不可重複讀取,但不容許髒讀取。讀取數據的事務容許其餘事務繼續訪問該行數據,可是未提交的寫事務將會禁止其餘事務訪問該行。
  3. Repeatable Read(可重複讀取):靜止不可重複讀取和髒讀取,但有時可能出現幻讀。讀取數據的事務將會禁止寫事務(但容許寫事務),寫事務則禁止任何其餘事務。
  4. Serializable(序列化):提供嚴格的事務隔離。要求事務序列化執行,不能併發執行。

隔離級別越高,越能保證數據的完整性和一致性,可是對併發性能的影響也越大。

Connection的事務

JDBC中,一個connection被建立時,默認是auto-commit模式,也即一個sql statement做爲一個事務,執行完成後自動commit。若是支持多個statement組成一個事務,則要禁止auto-commit模式。

con.setAutoCommit(false);

Connection支持了標準的四種隔離級別,若是沒有設置,則查詢數據庫的默認隔離級別,也能夠用setTransactionIsolation方法手動設置。

conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);

Connection使用commit和rollback方法支持事務的提交和回滾。

try {
	conn.setAutoCommit(false);
	// -- do query and update operation
	conn.commit();
} catch (Exception e) {
	conn.rollback();
}

同時使用setSavepoint和releaseSavepoint方法支持保存點的設置和釋放。

try {
	conn.setAutoCommit(false);
	// -- update 1
	Savepoint s1 = conn.setSavepoint();
	try {
		// -- update2
	} catch (Exception e) {
		conn.releaseSavepoint(s1);
	}
	conn.commit();
} catch (Exception e) {
	conn.rollback();
}

DataSource

DataSource表示一種建立Connection的工廠,在jdk 1.4引入,相對DriverManager的方式更優先推薦使用DataSource。支持三種實現類型:

  1. 基本實現:產生一個標準鏈接對象
  2. 鏈接池實現:將鏈接對象池化處理,由一個鏈接池管理中間件支持
  3. 分佈式事務實現:支持分佈式事務,一般也是池化的,由一個事務管理中間件支持。

基於DataSource產生了兩個很是經常使用的數據庫鏈接池框架:DBCP和C3P0,解決了數據庫鏈接的複用問題,極大地提升了數據庫鏈接的使用性能。看一個DBCP的簡單用例:

BasicDataSource basicDataSource = new BasicDataSource();
basicDataSource.setDriverClassName("com.mysql.jdbc.Driver");
basicDataSource.setUrl("jdbc:mysql://localhost:3306/lichao");
basicDataSource.setUsername("root");
basicDataSource.setPassword("root");
// 初始化鏈接數
basicDataSource.setInitialSize(5);
// 最大鏈接數
basicDataSource.setMaxActive(30);
// 最大空閒鏈接數
basicDataSource.setMaxIdle(5);
// 最小空閒鏈接數
basicDataSource.setMinIdle(2);
Connection conn = basicDataSource.getConnection();
// sql operation
conn.close();

關於DBCP和C3P0,具體的這裏就不深刻了。

JDBC解決了面向不一樣數據庫的統一接口問題,咱們只要遵照它的接口編程,則不用關心底層數據庫的內部實現。然而程序員對編程便捷的追求是永不止步的。咱們不能接受一次簡單的查詢須要如此多的步驟,打開鏈接,建立statement對象,解析結果集,最後關閉資源,甚至連JDBC我都不想關心。個人需求很簡單,就是提出一個對數據庫的請求(請求或更新),返回給我結果,若是是查詢,直接映射到java對象最好。咱們但願更多地關注真正關心的東西(好比業務),而不是數據的傳輸過程。因而就出現了各類對JDBC的封裝框架,好比apache的dbutils。

參考文檔:

  1. https://baike.baidu.com/item/%E4%BA%8B%E5%8A%A1%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/2638091
  2. https://docs.oracle.com/javase/8/docs/api/
  3. https://docs.oracle.com/javase/tutorial/jdbc/basics/transactions.html
相關文章
相關標籤/搜索