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. js表白心形特效

    好久没有仔细钻研技术了,闲下来借鉴一下做出一些效果 友情链接: http://tiepeng.applinzi.com/love_you/ ;;background:#ffe;font-size:12 ...

  2. JS基础部分小结

    [使用js的三种方式] 1.HTML标签中内嵌JS(不提倡使用) 2.HTML页面中直接使用JS <script type="text/javascript"> JS代 ...

  3. spring计划任务

    Spring3中加强了注解的使用,其中计划任务也得到了增强,现在创建一个计划任务只需要两步就完成了: 创建一个Java类,添加一个无参无返回值的方法,在方法上用@Scheduled注解修饰一下: 在S ...

  4. 【Netty】Netty核心组件介绍

    一.前言 前篇博文体验了Netty的第一个示例,下面接着学习Netty的组件和其设计. 二.核心组件 2.1. Channel.EventLoop和ChannelFuture Netty中的核心组件包 ...

  5. 使用Browserify来实现CommonJS的浏览器加载

    前面的话 Nodejs的模块是基于CommonJS规范实现的,可不可以应用在浏览器环境中呢? var math = require('math'); math.add(2, 3); 第二行math.a ...

  6. 将github上的项目源码导入到eclipse中

    1.注册github帐号 在github上注册一个自己的帐号. 2.安装git插件egit 在eclipse中安装git插件egit,安装方法可以参考这篇文章: http://www.cnblogs. ...

  7. redis-key相关命令

    本篇主要介绍和redis的key进行操作的相关命令. 命令 描述 复杂度 返回值 DEL key [key ...] 删除给定的一个或多个 key .不存在的 key 会被忽略.删除单个列表.集合.有 ...

  8. crontab的相关设置&linux定时备份数据库

    对于才了解crontab的人来说,应该按照以下的步骤来设置crontab 1.首先要检查是否装了crontab http://blog.sina.com.cn/s/blog_4881040d01011 ...

  9. linux c++爬虫(一)

    int main(int argc, void *argv[]) { ]; ; char ch; ) { switch(ch) { case 'v': version(); break; case ' ...

  10. Window7 下 WNMP 环境搭建

    WNMP 指"Windows 下的 Nginx.MariaDB/MySQL 和 PHP 环境". 下载 Nginx:http://nginx.org/en/download.htm ...