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();}}}

推荐阅读