ThreadLocal的介紹與運用( 三 )

? (6) service層代碼 : AccountService
package com.itheima.transfer.service;import com.itheima.transfer.dao.AccountDao;import java.sql.SQLException;public class AccountService {public boolean transfer(String outUser, String inUser, int money) {AccountDao ad = new AccountDao();try {// 轉出ad.out(outUser, money);// 轉入ad.in(inUser, money);} catch (Exception e) {e.printStackTrace();return false;}return true;}}? (7) web層代碼 : AccountWeb
package com.itheima.transfer.web;import com.itheima.transfer.service.AccountService;public class AccountWeb {public static void main(String[] args) {// 模擬數據 : Jack 給 Rose 轉賬 100String outUser = "Jack";String inUser = "Rose";int money = 100;AccountService as = new AccountService();boolean result = as.transfer(outUser, inUser, money);if (result == false) {System.out.println("轉賬失敗!");} else {System.out.println("轉賬成功!");}}}2.1.2 引入事務? 案例中的轉賬涉及兩個DML操作: 一個轉出,一個轉入 。這些操作是需要具備原子性的,不可分割 。不然就有可能出現數據修改異常情況 。
public class AccountService {public boolean transfer(String outUser, String inUser, int money) {AccountDao ad = new AccountDao();try {// 轉出ad.out(outUser, money);// 模擬轉賬過程中的異常int i = 1/0;// 轉入ad.in(inUser, money);} catch (Exception e) {e.printStackTrace();return false;}return true;}}? 所以這里就需要操作事務,來保證轉出和轉入操作具備原子性,要么同時成功,要么同時失敗 。
(1) JDBC中關于事務的操作的api
Connection接口的方法作用voidsetAutoCommit(false)禁用事務自動提交(改為手動)voidcommit();提交事務void rollback();回滾事務(2) 開啟事務的注意點:

  • 為了保證所有的操作在一個事務中,案例中使用的連接必須是同一個:service層開啟事務的connection需要跟dao層訪問數據庫的connection保持一致
  • 線程并發情況下, 每個線程只能操作各自的 connection
2.2常規解決方案2.2.1 常規方案的實現基于上面給出的前提,大家通常想到的解決方案是 :
  • 從service層將connection對象向dao層傳遞
  • 加鎖
以下是代碼實現修改的部分:
? (1 ) AccountService 類
package com.itheima.transfer.service;import com.itheima.transfer.dao.AccountDao;import com.itheima.transfer.utils.JdbcUtils;import java.sql.Connection;public class AccountService {public boolean transfer(String outUser, String inUser, int money) {AccountDao ad = new AccountDao();//線程并發情況下,為了保證每個線程使用各自的connection,故加鎖synchronized (AccountService.class) {Connection conn = null;try {conn = JdbcUtils.getConnection();//開啟事務conn.setAutoCommit(false);// 轉出ad.out(conn, outUser, money);// 模擬轉賬過程中的異常//int i = 1/0;// 轉入ad.in(conn, inUser, money);//事務提交JdbcUtils.commitAndClose(conn);} catch (Exception e) {e.printStackTrace();//事務回滾JdbcUtils.rollbackAndClose(conn);return false;}return true;}}}? (2) AccountDao 類 (這里需要注意的是: connection不能在dao層釋放,要在service層,不然在dao層釋放,service層就無法使用了)
package com.itheima.transfer.dao;import com.itheima.transfer.utils.JdbcUtils;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.SQLException;public class AccountDao {public void out(Connection conn, String outUser, int money) throws SQLException{String sql = "update account set money = money - ? where name = ?";//注釋從連接池獲取連接的代碼,使用從service中傳遞過來的connection//Connection conn = JdbcUtils.getConnection();PreparedStatement pstm = conn.prepareStatement(sql);pstm.setInt(1,money);pstm.setString(2,outUser);pstm.executeUpdate();//連接不能在這里釋放,service層中還需要使用//JdbcUtils.release(pstm,conn);JdbcUtils.release(pstm);}public void in(Connection conn, String inUser, int money) throws SQLException {String sql = "update account set money = money + ? where name = ?";//Connection conn = JdbcUtils.getConnection();PreparedStatement pstm = conn.prepareStatement(sql);pstm.setInt(1,money);pstm.setString(2,inUser);pstm.executeUpdate();//JdbcUtils.release(pstm,conn);JdbcUtils.release(pstm);}}2.2.2 常規方案的弊端上述方式我們看到的確按要求解決了問題,但是仔細觀察 , 會發現這樣實現的弊端:
  1. 直接從service層傳遞connection到dao層, 造成代碼耦合度提高
  2. 加鎖會造成線程失去并發性,程序性能降低
2.3 ThreadLocal解決方案2.3.1 ThreadLocal方案的實現像這種需要在項目中進行數據傳遞和線程隔離的場景,我們不妨用ThreadLocal來解決:
? (1) 工具類的修改: 加入ThreadLocal
package com.itheima.transfer.utils;import com.mchange.v2.c3p0.ComboPooledDataSource;import java.sql.Connection;import java.sql.SQLException;public class JdbcUtils {//ThreadLocal對象 : 將connection綁定在當前線程中private static final ThreadLocal<Connection> tl = new ThreadLocal();// c3p0 數據庫連接池對象屬性private static final ComboPooledDataSource ds = new ComboPooledDataSource();// 獲取連接public static Connection getConnection() throws SQLException {//取出當前線程綁定的connection對象Connection conn = tl.get();if (conn == null) {//如果沒有,則從連接池中取出conn = ds.getConnection();//再將connection對象綁定到當前線程中tl.set(conn);}return conn;}//釋放資源public static void release(AutoCloseable... ios) {for (AutoCloseable io : ios) {if (io != null) {try {io.close();} catch (Exception e) {e.printStackTrace();}}}}public static void commitAndClose() {try {Connection conn = getConnection();//提交事務conn.commit();//解除綁定tl.remove();//釋放連接conn.close();} catch (SQLException e) {e.printStackTrace();}}public static void rollbackAndClose() {try {Connection conn = getConnection();//回滾事務conn.rollback();//解除綁定tl.remove();//釋放連接conn.close();} catch (SQLException e) {e.printStackTrace();}}}

推薦閱讀