JDBC連接------事務處理(ThreadLocal的使用)
目錄
1、緒論
之前的的JDBC連接都是寫的單線程連接。如果考慮併發問題,用戶A進行轉賬操作,把自己的前轉A到另一個用戶賬戶上,自己的賬戶獲取一次連接減少前,另一個賬戶加錢又獲取一次同樣的連接。如果對這個連接不加以限制,在這個時候又有另外的用戶B進行轉賬操作,此時極有可能獲取的是用戶A的連接(如果不限制連接,這種情況是極有可能發生的,而且相當的嚴重),那麼B用戶可做的操作就可以影響到A,造成錯誤的結果!那麼我們就需要對我們的連接進行一個限制,即每個用戶只能使用自己的連接,不能使用別人的連接。這裏每個用戶可以視爲一個線程。
java中爲我們提供了這樣一個類ThreaLocal,線程本地變量,也有叫線程本地存儲的。在JDBC連接中,可以用這個類來存儲連接給線程去用,每個線程從ThreadLocal中獲取連接,每個線程都只能使用他自己獲取的這個連接,不能使用其他線程的連接
2、關於ThreadLocal
ThreadLocal底層相當於一個map數組,key用來存儲當前線程,value用來存儲當前線程下共享的數據。它裏面有一些方法需要說明一下。
1、get() 獲取ThreadLocal中當前線程共享變量的值。
2、set(T value) 設置ThreadLocal中當前線程共享變量的值。
3、remove()移除ThreadLocal中當前線程共享變量的值
ThreadLocal深度解析參考下面文章,此處不作贅述
3、代碼實現
3-1、utils層
C3P0工具類在前面文章中有
package mytest.fenceng2.utils;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @Author: ${user}
*/
public class CollectionManager {
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
public static Connection getConnection() throws SQLException {
Connection conn = tl.get();
//如果容器中沒有連接,就從連接池獲取一個連接存到ThreadLocal中
if (conn == null) {
conn = C3P0Utils.getConection();
tl.set(conn);
}
return conn;
}
public static void begin() {
try {
getConnection().setAutoCommit(false);
} catch (SQLException e) {
}
}
public static void commit() {
try {
getConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void rollback() {
try {
getConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void close() {
try {
getConnection().close();
//爲確保連接使用過後不再被調用,需要刪除此連接
tl.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3-2、service層
package mytest.fenceng2.service;
import mytest.fenceng2.dao.TeacherDao;
import mytest.fenceng2.utils.C3P0Utils;
import mytest.fenceng2.utils.CollectionManager;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @Author: ${user}
*/
public class TeacherService {
public void transfer(int fclass,int lclass,String fname,String lname) throws SQLException {
TeacherDao td = new TeacherDao();
try{
CollectionManager.begin();
int i = td.firstChange( fclass, fname);
// System.out.println(1/0);
int i1 = td.lastChange(lclass, lname);
if (i==1&&i1==1){
System.out.println("轉班成功");
CollectionManager.commit();
}
}
catch (Exception e){
e.printStackTrace();
CollectionManager.rollback();
}finally {
CollectionManager.close();
}
}
}
3-3、DAO層
package mytest.fenceng2.dao;
import mytest.fenceng2.utils.CollectionManager;
import org.apache.commons.dbutils.QueryRunner;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @Author: ${user}
*/
public class TeacherDao {
public int firstChange(int tclass,String tname) throws SQLException {
// conn = C3P0Utils.getConection();
Connection conn = CollectionManager.getConnection();
String sql = "update teacher set tclass = ? where tname = ?";
QueryRunner qr = new QueryRunner();
int update = qr.update(conn, sql, tclass, tname);
return update;
}
public int lastChange(int tclass,String tname) throws SQLException {
// conn = C3P0Utils.getConection();
Connection conn = CollectionManager.getConnection();
String sql = "update teacher set tclass = ? where tname = ?";
QueryRunner qr = new QueryRunner();
int update = qr.update(conn, sql, tclass, tname);
return update;
}
}
3-4、測試方法
package mytest.fenceng2.control;
import mytest.fenceng2.service.TeacherService;
import java.sql.SQLException;
import java.util.Scanner;
/**
* @Author: ${user}
*/
public class TeacherControl {
public static void main(String[] args) throws SQLException {
Scanner sc =new Scanner(System.in);
System.out.println("哪個老師要轉班");
String fname = sc.next();
System.out.println("轉到哪個班");
int fclass = sc.nextInt();
System.out.println("那這個班老師是誰");
String lname = sc.next();
System.out.println("她轉到哪個班");
int lclass = sc.nextInt();
TeacherService ts =new TeacherService();
ts.transfer(fclass,lclass,fname,lname);
}
}
總結
通過創建CollectionManager工具類,將獲取連接和事務操作的方法寫在一起,增加了代碼的複用性。而ThreadLocal通過底層代碼實現了各個線程只能處理自己的數據,不能處理其他線程的數據,有效的解決了事務處理時的併發問題。
關閉ThreadLocal源碼的解析,在後面的文章中也有給出,此處附上連接,方便跳轉
能力尚淺,有待進步,如有不足,不吝賜教!