ThreadLocal (扩展)

1 ThreadLocal API

ThreadLocal类只有三个方法:

l  void set(T value):保存值;

l  T get():获取值;

l  void remove():移除值。

2 ThreadLocal的内部是Map

ThreadLocal内部其实是个Map来保存数据。虽然在使用ThreadLocal时只给出了值,没有给出键,其实它内部使用了当前线程做为键。

class MyThreadLocal<T> {

private Map<Thread,T> map = new HashMap<Thread,T>();

public void set(T value) {

map.put(Thread.currentThread(), value);

}

public void remove() {

map.remove(Thread.currentThread());

}

public T get() {

return map.get(Thread.currentThread());

}

}

Service事务(扩展)

  在Service中使用ThreadLocal来完成事务,为将来学习Spring事务打基础!

1 DAO中的事务

在DAO中处理事务真是“小菜一碟”。

public void xxx() {

Connection con = null;

try {

con = JdbcUtils.getConnection();

con.setAutoCommitted(false);

QueryRunner qr = new QueryRunner();

String sql = …;

Object[] params = …;

qr.update(con, sql, params);

sql = …;

Object[] params = …;

qr.update(con, sql, params);

con.commit();

} catch(Exception e) {

try {

if(con != null)
{con.rollback();}

} catch(Exception e) {}

} finally {

try {

con.close();

} catch(Exception e) {}

}

}

2 Service才是处理事务的地方

  我们要清楚一件事,DAO中不是处理事务的地方,因为DAO中的每个方法都是对数据库的一次操作,而Service中的方法才是对应一个业务逻辑。也就是说我们需要在Service中的一方法中调用DAO的多个方法,而这些方法应该在一起事务中。

怎么才能让DAO的多个方法使用相同的Connection呢?方法不能再自己来获得Connection,而是由外界传递进去。

public void daoMethod1(Connection con, …)
{

}

public void daoMethod2(Connection con, …)
{

}

在Service中调用DAO的多个方法时,传递相同的Connection就可以了。

public class XXXService() {

private XXXDao dao = new XXXDao();

public void serviceMethod() {

Connection con = null;

try {

con = JdbcUtils.getConnection();

con.setAutoCommitted(false);

dao.daoMethod1(con, …);

dao.doaMethod2(con, …);

com.commint();

} catch(Exception e) {

try {

con.rollback();

} catch(Exception e) {}

} finally {

try {

con.close();

} catch(Exception e) {}

}

}

}

但是,在Service中不应该出现Connection,它应该只在DAO中出现,因为它是JDBC的东西,JDBC的东西是用来连接数据库的,连接数据库是DAO的事儿!!!但是,事务是Service的事儿,不能放到DAO中!!!

3 修改JdbcUtils

我们把对事务的开启和关闭放到JdbcUtils中,在Service中调用JdbcUtils的方法来完成事务的处理,但在Service中就不会再出现Connection这一“禁忌”了。

DAO中的方法不用再让Service来传递Connection了。DAO会主动从JdbcUtils中获取Connection对象,这样,JdbcUtils成为了DAO和Service的中介!

我们在JdbcUtils中添加beginTransaction()和rollbackTransaction(),以及commitTransaction()方法。这样在Service中的代码如下:

public class XXXService() {

private XXXDao dao = new XXXDao();

public void serviceMethod() {

try {

JdbcUtils.beginTransaction();

dao.daoMethod1(…);

dao.daoMethod2(…);

JdbcUtils.commitTransaction();

} catch(Exception e) {

JdbcUtils.rollbackTransaction();

}

}

}

DAO

public void daoMethod1(…) {

Connection con = JdbcUtils.getConnection();

}

public void daoMethod2(…) {

Connection con = JdbcUtils.getConnection();

}

在Service中调用了JdbcUtils.beginTransaction()方法时,JdbcUtils要做准备好一个已经调用了setAutoCommitted(false)方法的Connection对象,因为在Service中调用JdbcUtils.beginTransaction()之后,马上就会调用DAO的方法,而在DAO方法中会调用JdbcUtils.getConnection()方法。这说明JdbcUtils要在getConnection()方法中返回刚刚准备好的,已经设置了手动提交的Connection对象。

在JdbcUtils中创建一个Connection
con属性,当它为null时,说明没有事务!当它不为null时,表示开启了事务。

l  在没有开启事务时,可以调用“开启事务”方法;

l  在开启事务后,可以调用“提交事务”和“回滚事务”方法;

l  getConnection()方法会在con不为null时返回con,再con为null时,从连接池中返回连接。

beginTransaction()

判断con是否为null,如果不为null,就抛出异常!

如果con为null,那么从连接池中获取一个Connection对象,赋值给con!然后设置它为“手动提交”。

getConnection()

判断con是否为null,如果为null说明没有事务,那么从连接池获取一个连接返回;

如果不为null,说明已经开始了事务,那么返回con属性返回。这说明在con不为null时,无论调用多少次getConnection()方法,返回的都是同个Connection对象。

commitTransaction()

判断con是否为null,如果为null,说明没有开启事务就提交事务,那么抛出异常;

如果con不为null,那么调用con的commit()方法来提交事务;

调用con.close()方法关闭连接;

con = null,这表示事务已经结束!

rollbackTransaction()

判断con是否为null,如果为null,说明没有开启事务就回滚事务,那么抛出异常;

如果con不为null,那么调用con的rollback()方法来回滚事务;

调用con.close()方法关闭连接;

con = null,这表示事务已经结束!

JdbcUtils.java

public class JdbcUtils {

private static DataSource dataSource = new
ComboPooledDataSource();

private static Connection con = null;

public static DataSource getDataSource() {

return dataSource;

}

public static Connection getConnection() throws
SQLException {

if(con == null) {

return dataSource.getConnection();

}

return con;

}

public static void beginTranscation() throws
SQLException {

if(con != null) {

throw new SQLException("事务已经开启,在没有结束当前事务时,不能再开启事务!");

}

con = dataSource.getConnection();

con.setAutoCommit(false);

}

public static void commitTransaction() throws
SQLException {

if(con == null) {

throw new SQLException("当前没有事务,所以不能提交事务!");

}

con.commit();

con.close();

con = null;

}

public static void rollbackTransaction() throws
SQLException {

if(con == null) {

throw new SQLException("当前没有事务,所以不能回滚事务!");

}

con.rollback();

con.close();

con = null;

}

}

4 再次修改JdbcUtils

现在JdbcUtils有个问题,如果有两个线程!第一个线程调用了beginTransaction()方法,另一个线程再调用beginTransaction()方法时,因为con已经不再为null,所以就会抛出异常了。

我们希望JdbcUtils可以多线程环境下被使用!这说明最好的方法是为每个线程提供一个Connection,这样每个线程都可以开启自己的事务了。

还记得ThreadLocal类么?

public class JdbcUtils {

private static DataSource dataSource = new
ComboPooledDataSource();

private static
ThreadLocal<Connection> tl[涛1]  = new ThreadLocal<Connection>();

public static DataSource getDataSource() {

return dataSource;

}

public static Connection getConnection() throws
SQLException {

Connection con = tl.get();[涛2]

if(con == null) {

return dataSource.getConnection();

}

return con;

}

public static void beginTranscation() throws
SQLException {

Connection con = tl.get();[涛3]

if(con != null[涛4] ) {

throw new SQLException("事务已经开启,在没有结束当前事务时,不能再开启事务!");

}

con = dataSource.getConnection();[涛5]

con.setAutoCommit(false);[涛6]

tl.set(con);[涛7]

}

public static void commitTransaction() throws
SQLException {

Connection con = tl.get();[涛8]

if(con == null[涛9] ) {

throw new SQLException("当前没有事务,所以不能提交事务!");

}

con.commit();[涛10]

con.close();[涛11]

tl.remove();[涛12]

}

public static void rollbackTransaction() throws
SQLException {

Connection con = tl.get();

if(con == null) {

throw new SQLException("当前没有事务,所以不能回滚事务!");

}

con.rollback();

con.close();

tl.remove();

}

}

5 转账示例

public class AccountDao {

public void updateBalance(String name, double balance) throws
SQLException {

String sql = "update account set balance=balance+? where name=?";

Connection con = JdbcUtils.getConnection();

//修改的代码....

}

}

public class AccountService {

private AccountDao dao = new AccountDao();

public void transfer(String from, String
to, double balance) {

try {

JdbcUtils.beginTranscation();

dao.updateBalance(from, -balance);

dao.updateBalance(to, balance);

JdbcUtils.commitTransaction();

} catch(Exception e) {

try {

JdbcUtils.rollbackTransaction();

} catch (SQLException e1) {

throw new RuntimeException(e);

}

}

}

}

AccountService as = new
AccountService();

as.transfer("zs", "ls", 100);


tl其实是一个Map,每个线程都可以来保存自己的Connection

获取当前线程的连接

获取当前线程的连接

如果当前线程已经有了连接,说明已经开启了事务

如果当前线程没有连接,说明还没有开启事务,那么就获取一个连接

设置连接为手动提交

把连接放到tl中,当前线程就有了连接了。

获取当前线程的连接

如果当前线程没有连接,那么就不能提交事务

如果当前线程有连接,那么就提交事务

关闭连接

把连接从tl中移除,当前线程就没有连接了,表示事务结束

ThreadLocal来管理事务的更多相关文章

  1. 用ThreadLocal管理事务

    1.适用场景 一个service,操作两个dao,要求两个dao为同一个事务,要么全成功,要么全失败.

  2. ThreadLocal在Spring事务管理中的应用

    ThreadLocal是用来处理多线程并发问题的一种解决方案.ThreadLocal是的作用是提供线程的局部变量,在多线程并发环境下,提供了与其他线程隔离的局部变量.通常这样的设计的情况是因为这个局部 ...

  3. 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别。

    基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)到底有什么区别. 我还是喜欢基于Schema风格的Spring事务管理,但也有很多人在用基于@Tras ...

  4. Spring中的事物管理,用 @Transactional 注解声明式地管理事务

    事物: 事务管理是企业级应用程序开发中必不可少的技术,  用来确保数据的 完整性和 一致性. 事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用 事务的四 ...

  5. 通过案例掌握Spring 管理事务的步骤及配置

    案例描述  通过完成生成订单业务,掌握事务处理.  需要d_order表和d_item表  订单生成时的业务逻辑:向d_order插入1条数据的同时,向t_item中插入若干条数据  这就是一个独立的 ...

  6. spring是如何管理 事务的

    Spring提供的事务管理可以分为两类:编程式的和声明式的.编程式的,比较灵活,但是代码量大,存在重复的代码比较多:声明式的比编程式的更灵活方便.  1.传统使用JDBC的事务管理  以往使用JDBC ...

  7. Spring -- <tx:annotation-driven>注解基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)的区别。

    借鉴:http://jinnianshilongnian.iteye.com/blog/1508018 基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional ...

  8. 对Spring 容器管理事务支持的总结

    1.问题 Connection conn = DataSourceUtils.getConnection(); //开启事务 conn.setAutoCommit(false); try { Obje ...

  9. spring框架学习(六)AOP事务及spring管理事务方式之Template模板

    概念 1.事务 1)事务特性:ACID 原子性 :强调事务的不可分割. 一致性 :事务的执行的前后数据的完整性保持一致. 隔离性 :一个事务执行的过程中,不应该受到其他事务的干扰. 持久性 :事务一旦 ...

随机推荐

  1. Swift、Objective-C 单例模式 (Singleton)

    Swift.Objective-C 单例模式 (Singleton) 本文的单例模式分为严格单例模式和不严格单例模式.单例模式要求一个类有一个实例,有公开接口可以访问这个实例.严格单例模式,要求一个类 ...

  2. ashMap源码阅读与解析

    目录结构 导入语 HashMap构造方法 put()方法解析 addEntry()方法解析 get()方法解析 remove()解析 HashMap如何进行遍历 导入语 HashMap是我们最常见也是 ...

  3. SublimeText3编译JavaScript

    这个操作很简单总的来说分为两步,1.安装Node.js  2.添加SublimeText3 JS编译系统 首先我们去官网下载node.js https://nodejs.org/en/ 然后安装 验证 ...

  4. 详细解剖大型H5单页面应用的核心技术点

    项目 Xut.js 阐述下开发中一个比较核心的优化技术点,这是一套平台代码,并非某一个插件功能或者框架可以直接拿来使用,核心代码大概是6万行左右(不包含任何插件) .这也并非一个开源项目,不能商业使用 ...

  5. System.Data.SqlClient.SqlException (0x80131904): EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 1,当前计数 = 0。 EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 1,当前计数 = 0。

    EF使用ExecuteSqlCommand(db.Database.ExecuteSqlCommand("exec proc_DeleteCaseInfo_Output @caseID&qu ...

  6. npm 常用命令详解

    本文以Windows平台上做测试,以gulp为示例做教程,出自作者白树,转载请声明出处! 目录 npm是什么 npm install 安装模块 npm uninstall 卸载模块 npm updat ...

  7. JS-监听文本回车事件写入数据表单

    场景   ERP系统扫描输入货品编号到文本框后,触发写入记录到数据表格,并对数据进行渲染.   解决方案 通过发现回车或者换行符,则写入数据表格   代码 //监听文本框输入事件 $('#gidinp ...

  8. Python实现脚本锁功能,同时只能执行一个脚本

    1. 文件锁 脚本启动前检查特定文件是否存在,不存在就启动并新建文件,脚本结束后删掉特定文件. 通过文件的判断来确定脚本是否正在执行. 方法实现也比较简单,这里以python脚本为例 #coding= ...

  9. PHP获取Mp3文件信息

    扫描本地MP3文件,获取文件信息

  10. 查看当前用户名称:whoami命令

    没什么可讲的,就是显示当前用户名称,效果同"id -un"命令.