【菜鳥學Java】14:使用ThreadLocal對Connection進行封裝

連江偉發表於2016-02-24

        問題背景:

        使用JDBC進行開發的時候,每一次的增刪改查都必須和資料庫建立連線,才可以對資料項進行相應的操作。當我們的業務比較複雜的情況下,可能會出現在一個方法中多次的執行增刪改查,這樣的話,在這個方法的執行過程中,就需要與資料庫建立多次的連線,在這種場景中,如何保證在併發執行這個方法的過程中,與資料庫的連線不會混亂,保證這些操作的原子性,就顯得尤為重要了。如何解決這個問題呢?

        問題分析:

我能想到的方案有兩個,其一:在這個多次執行增刪改查的方法內,宣告一個區域性變數用於存放資料庫連線物件Connection,這樣在呼叫增刪改查方法的時候,將Connection物件作為引數傳進去,這樣就保證了這些子操作都使用的是同一個連線,從而保證了所有操作的連續性和原子性,不會出現資料庫連線物件混亂使用的情況。

      其二:我們仔細想想,其實使用者的每一次請求,都會呼叫程式相應的Servlet,而Servlet是單例項多執行緒的,也就是說每一次的請求程式都啟動一個執行緒為使用者服務,在這個執行緒中會呼叫很多的方法,包括我們上面提到的那個需要和資料庫進行多次連線的複雜方法,由此我們可以這樣想,只要保證在這個執行緒中,我們所使用的資料庫連線物件Connection都是同一個,一樣可以保證這些操作的連續性和原子性。

        相比較第一種方案而言,第二種顯然要更好一些,因為第一種需要我們在編寫增刪改查方法時,定義Connection的引數,第二種則不用,直接將Connection進行一下封裝即可。

        問題解決:

        JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多執行緒程式的併發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多執行緒程式,ThreadLocal並不是一個Thread,而是Thread的區域性變數。我們就使用ThreadLocal這個類來對Connection進行一下簡單的封裝,以滿足我們的需求。程式碼如下:

/**
 * 採用ThreadLocal封裝Connection
 * @author ljw
 *
 */
public class ConnectionManager {

	//宣告一個本地執行緒變數,用於存放connection
	private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>();
	
	/**
	 * 得到Connection
	 * @return
	 */
	public static Connection getConnection() {
		Connection conn = connectionHolder.get();
		//如果在當前執行緒中沒有繫結相應的Connection
		if (conn == null) {
			try {
				JdbcConfig jdbcConfig = XmlConfigReader.getInstance().getJdbcConfig();
				Class.forName(jdbcConfig.getDriverName());
				conn = DriverManager.getConnection(jdbcConfig.getUrl(), jdbcConfig.getUserName(), jdbcConfig.getPassword());
				//將Connection設定到執行緒變數ThreadLocal中
				connectionHolder.set(conn);
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
				throw new ApplicationException("系統錯誤,請聯絡系統管理員");
			} catch (SQLException e) {
				e.printStackTrace();
				throw new ApplicationException("系統錯誤,請聯絡系統管理員");
			}
		}
		return conn;
	}
	/**
	 * 關閉連線,只關閉當前執行緒的連線
	 */
	public static void closeConnection() {
		Connection conn = connectionHolder.get();
		if (conn != null) {
			try {
				conn.close();
				//從ThreadLocal中清除Connection
				connectionHolder.remove();
			} catch (SQLException e) {
				e.printStackTrace();
			}	
		}
	}
	
	public static void close(Connection conn) {
		if (conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void close(Statement pstmt) {
		if (pstmt != null) {
			try {
				pstmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void close(ResultSet rs ) {
		if (rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void beginTransaction(Connection conn) {
		try {
			if (conn != null) {
				if (conn.getAutoCommit()) {
					conn.setAutoCommit(false); //手動提交
				}
			}
		}catch(SQLException e) {}
	}
	
	public static void commitTransaction(Connection conn) {
		try {
			if (conn != null) {
				if (!conn.getAutoCommit()) {
					conn.commit();
				}
			}
		}catch(SQLException e) {}
	}
	
	public static void rollbackTransaction(Connection conn) {
		try {
			if (conn != null) {
				if (!conn.getAutoCommit()) {
					conn.rollback();
				}
			}
		}catch(SQLException e) {}
	}

}

        小結一下:

        我們可以在方法內部進行控制,當然我們也可以線上程中進行控制,同一個執行緒的區域性變數不會影響到另一個執行緒的,Java為我們提供了豐富的類庫去開發各種各樣的程式,把玩這些類可以窺探程式設計樂趣,這就是程式設計的魅力所在。在程式設計中多執行緒的問題很值得研究,本篇文章中只是解決了一個小小的Connection獨立性的問題,還有很多很多的其他問題,需要我們去認識和理解。

相關文章