Code 【事务控制】dbutils关于使用ThreadLocal实现事务控制 编程 29次访问 05-12 12:29 #### 前言 事务控制是数据库重要的一个环节,一个事务包含一个或多个SQL语句。 在我们的正常开发中,经常遇到需要一次执行多个SQL语句的情况,好比是插入后需要查询该条记录。亦或是保证事物的原子性和数据的一致性,我们必须在多条语句的业务中使用事物。 #### 分析 - **如何实现事务控制** 以使用dbutils为例: dbutils中一个connection就是一个事物,但是正常的业务操作使用了很多个dao的操作(QueryRunner)。  为保证dao的操作都能使用到同一个connection。我们在使用runner查询时可以在sql前加入同一个connecttion。实现业务层和持续层使用同一个connection. 如下图所示:那么可想而知,这个工具类方法的功能就是获取一个正在使用的connection.  - **那么为什么要使用ThreadLocal呢?** ThreadLocal的特性:“**隔离性**”它可以在指定线程中存储信息,且只能在该线程中进行存取,新创建的threadlocal对数据进行了备份,每个访问他的线程都会得到这个备份的副本,保证了线程的**安全**性;同时它作为一种线程域,能够隐式的获取对象。极大的降低了程序的**耦合**度。 #### 代码 ...... [========] 首先我们需要两个工具类: - ConnectionUtils 通过ThreadLocal将connection和线程绑定 - TransactionManager 实现事务的开始、提交、回滚、销毁 ```java package com.utils; import org.springframework.stereotype.Component; import javax.annotation.Resource; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; /** * 用于从数据原中获取连接 * 并和线程绑定 * */ @Component("connectionUtils") public class ConnectionUtils { private ThreadLocal tl=new ThreadLocal(); @Resource(name = "dataSource") private DataSource dataSource; public Connection getThreadConnection(){ try { Connection conn=tl.get(); if (conn==null){ //如果该线程没有绑定connection,则为他绑定一个新的conn conn = dataSource.getConnection(); tl.set(conn); } return conn; } catch (SQLException e) { e.printStackTrace(); System.out.println("没有取得链接!"); return null; } } /** * 将线程和连接解绑 * */ public void remove(){ tl.remove(); } } ``` ```java package com.utils; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.sql.Connection; import java.sql.SQLException; /** * 事物控制工具 */ @Component("transactionManager") public class TransactionManager { @Resource(name = "connectionUtils") private ConnectionUtils connectionUtils; //开启事务 public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } } //提交 public void commit(){ try { connectionUtils.getThreadConnection().commit(); } catch (SQLException e) { e.printStackTrace(); } } //回滚 public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); } catch (SQLException e) { e.printStackTrace(); } } //销毁 public void close(){ try { connectionUtils.getThreadConnection().close(); connectionUtils.remove(); //这里销毁不仅要关闭connection连接,还要将其解绑,防止线程从线程池中再次被获取时,还有一个已经绑定的且被关闭的connection连接 } catch (SQLException e) { e.printStackTrace(); } } } ``` 最后将该事物用作于service中 ```java @Override public void transfer(String sourceName, String targetName, Integer money) { try{ //开启事物 tm.beginTransaction(); //执行操作 //根据名称找到原账号 Account sourceAccount=accountDao.findByName(sourceName); //根据名称找到目标账户 Account targetAccount=accountDao.findByName(targetName); //目标账户加钱 targetAccount.setMoney(targetAccount.getMoney()+money); //原账户减钱 sourceAccount.setMoney((sourceAccount.getMoney()-money)); //更新目标账户 accountDao.updateAccountById(targetAccount); //更新原账户 int i=1/0; accountDao.updateAccountById(sourceAccount); //提交事物 tm.commit(); //返回结果 }catch (Exception e){ //回滚 e.printStackTrace(); tm.rollback(); }finally { //释放链接 tm.close(); } } ``` 即使这里出现了1/0的错误,事物也会回滚操作,不会破坏数据的一致性。 < 夏日颓倦的午后 摸鱼中 > 让浏览器记住我!