在本系列的上一篇文章中我们讲到,要实现在同一个事务中使用相同的Connection对象,我们可以通过传递Connection对象的方式达到共享的目的,但是这种做法是丑陋的。在本篇文章中,我们将引入另外一种机制(ConnectionHolder)来完成事务管理。

这是一个关于Java事务处理的系列文章,请通过以下方式下载github源代码:

git clone https://github.com/davenkin/java_transaction_workshop.git

ConnectionHolder的工作机制是:我们将Connection对象放在一个全局公用的地方,然后在不同的操作中都从这个地方取得Connection,从而完成Connection共享的目的,这也是一种ServiceLocator模式,有点像JNDI。定义一个ConnectionHolder类如下:

public class ConnectionHolder
{private Map<DataSource, Connection> connectionMap = new HashMap<DataSource, Connection>();public Connection getConnection(DataSource dataSource) throws SQLException{Connection connection = connectionMap.get(dataSource);if (connection == null || connection.isClosed()){connection = dataSource.getConnection();connectionMap.put(dataSource, connection);}return connection;}public void removeConnection(DataSource dataSource){connectionMap.remove(dataSource);}
}

从ConnectionHolder类中可以看出,我们维护了一个键为DataSource、值为Connection的Map,这主要用于使ConnectionHolder可以服务多个DataSource。在调用getConnection方法时传入了一个DataSource对象,如果Map里面已经存在该DataSource对应的Connection,则直接返回该Connection,否则,调用DataSource的getConnection方法获得一个新的Connection,再将其加入到Map中,最后返回该Connection。这样在同一个事务过程中,我们先后从ConnectionHolder中取得的Connection是相同的,除非在中途我们调用了ConnectionHolder的removeConnection方法将当前Connection移除掉或者调用了Connection.close()将Connection关闭,然后在后续的操作中再次调用ConnectionHolder的getConnection方法,此时返回的则是一个新的Connection对象,从而导致事务处理失败,你应该不会做出这种中途移除或关闭Connection的事情。

然而,虽然我们不会自己手动地在中途移除或者关闭Conncetion对象(当然,在事务处理末尾我们应该关闭Conncetion),我们却无法阻止其他线程这么做。比如,ConnectionHolder类是可以在多个线程中同时使用的,并且这些线程使用了同一个DataSource,其中一个线程使用完Connection后便将其关闭,而此时另外一个线程正试图使用这个Connection,问题就出来了。因此,上面的ConnectionHolder不是线程安全的。

为了获得线程安全的ConnectionHolder类,我们可以引入Java提供的ThreadLocal类,该类保证一个类的实例变量在各个线程中都有一份单独的拷贝,从而不会影响其他线程中的实例变量。定义一个SingleThreadConnectionHolder类如下:

public class SingleThreadConnectionHolder
{private static ThreadLocal<ConnectionHolder> localConnectionHolder = new ThreadLocal<ConnectionHolder>();public static Connection getConnection(DataSource dataSource) throws SQLException{return getConnectionHolder().getConnection(dataSource);}public static void removeConnection(DataSource dataSource){getConnectionHolder().removeConnection(dataSource);}private static ConnectionHolder getConnectionHolder(){ConnectionHolder connectionHolder = localConnectionHolder.get();if (connectionHolder == null){connectionHolder = new ConnectionHolder();localConnectionHolder.set(connectionHolder);}return connectionHolder;}}

有了一个线程安全的SingleThreadConnectionHolder类,我们便可以在service层和各个DAO中使用该类来获取Connection对象:

Connection connection = SingleThreadConnectionHolder.getConnection(dataSource);

当然,此时我们需要传入一个DataSource,这个DataSource可以作为DAO类的实例变量存在,所以我们不用像上一篇文章那样将Connection对象直接传给DAO的方法。这里你可能要问,既然可以将DataSource作为实例变量,那么在上一篇文章中,为什么不可以将Connection也作为实例变量呢,这样不就不会造成丑陋的API了吗?原因在于:将Connection对象作为实例变量同样会带来线程安全问题,当多个线程同时使用同一个DAO类时,一个线程关闭了Connection而另一个正在使用,这样的问题和上面讲到的ConnectionHolder的线程安全问题一样。

关于Bank DAO和Insurance DAO类的源代码这里就不列出了,他们和上篇文章只是获得Connection对象的方法不一样而已,你可以参考github源代码。

接下来,我们再来看看TransactionManager类,在上几篇文章中,我们都是在service类中直接写和事务处理相关的代码,而更好的方式是声明一个TransactionManger类将事务处理相关工作集中管理:

public class TransactionManager
{private DataSource dataSource;public TransactionManager(DataSource dataSource){this.dataSource = dataSource;}public final void start() throws SQLException{Connection connection = getConnection();connection.setAutoCommit(false);}public final void commit() throws SQLException{Connection connection = getConnection();connection.commit();}public final void rollback(){Connection connection = null;try{connection = getConnection();connection.rollback();} catch (SQLException e){throw new RuntimeException("Couldn't rollback on connection[" + connection + "].", e);}}public final void close(){Connection connection = null;try{connection = getConnection();connection.setAutoCommit(true);connection.setReadOnly(false);connection.close();SingleThreadConnectionHolder.removeConnection(dataSource);} catch (SQLException e){throw new RuntimeException("Couldn't close connection[" + connection + "].", e);}}private Connection getConnection() throws SQLException{return SingleThreadConnectionHolder.getConnection(dataSource);}
}

可以看出,TransactionManager对象也维护了一个DataSource实例变量,并且也是通过SingleThreadConnectionHolder来获取Connection对象的。然后我们在service类中使用该TransactionManager:

public class ConnectionHolderBankService implements BankService
{private TransactionManager transactionManager;private ConnectionHolderBankDao connectionHolderBankDao;private ConnectionHolderInsuranceDao connectionHolderInsuranceDao;public ConnectionHolderBankService(DataSource dataSource){transactionManager = new TransactionManager(dataSource);connectionHolderBankDao = new ConnectionHolderBankDao(dataSource);connectionHolderInsuranceDao = new ConnectionHolderInsuranceDao(dataSource);}public void transfer(int fromId, int toId, int amount){try{transactionManager.start();connectionHolderBankDao.withdraw(fromId, amount);connectionHolderInsuranceDao.deposit(toId, amount);transactionManager.commit();} catch (Exception e){transactionManager.rollback();} finally{transactionManager.close();}}
}

在ConnectionHolderBankService中,我们使用TransactionManager来管理事务,由于TransactionManger和两个DAO类都是使用SingleThreadConnectionHolder来获取Connection,故他们在整个事务处理过程中使用了相同的Connection对象,事务处理成功。我们也可以看到,在两个DAO的withdraw和deposit方法没有接受和业务无关的对象,消除了API污染;另外,使用TransactionManager来管理事务,使Service层代码也变简洁了。

在下一篇文章中,我们将讲到使用Template模式来完成事务处理。

转载地址:http://www.davenkin.me/post/2013-02-23/40049986447

Java事务处理全解析(四)—— 成功的案例(自己实现一个线程安全的TransactionManager)相关推荐

  1. java中使用事务案例_Java事务处理全解析(四)—— 成功的案例(自己实现一个线程安全的TransactionManager)...

    在本系列的上一篇文章中我们讲到,要实现在同一个事务中使用相同的Connection对象,我们可以通过传递Connection对象的方式达到共享的目的,但是这种做法是丑陋的.在本篇文章中,我们将引入另外 ...

  2. Java事务处理全解析(二)——失败的案例

    在本系列的上一篇文章中,我们讲到了Java事务处理的基本问题,并且讲到了Service层和DAO层,在本篇文章中,我们将以BankService为例学习一个事务处理失败的案例. BankService ...

  3. Java 面试全解析:核心知识点与典型面试题

    课程背景 又逢"金九银十",年轻的毕业生们满怀希望与忐忑,去寻找.竞争一个工作机会.已经在职的开发同学,也想通过社会招聘或者内推的时机争取到更好的待遇.更大的平台. 然而,面试人群 ...

  4. java进程内存一直没释放_面试官:一个线程OOM,进程里其他线程还能运行么?...

    引言 这题是一个网友@大脸猫爱吃鱼给我的提问,出自今年校招美团三面的一个真题.大致如下 一个进程有3个线程,如果一个线程抛出oom,其他两个线程还能运行么? 先说一下答案,答案是还能运行 不瞒大家说, ...

  5. java jta 例子_Java事务处理全解析(八)——分布式事务入门例子(Spring+JTA+Atomikos+Hibernate+JMS)...

    在本系列先前的文章中,我们主要讲解了JDBC对本地事务的处理,本篇文章将讲到一个分布式事务的例子. 请通过以下方式下载github源代码: 本地事务和分布式事务的区别在于:本地事务只用于处理单一数据源 ...

  6. java cookie全解析(session与cookie的机制和原理)

    http://www.cnblogs.com/agilework/articles/2290240.html 摘要:虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚 ...

  7. 中国IT成功人士特点6大成功密码全解析

    中国IT成功人士特点6大成功密码全解析 IT成功人士的成功之路 史玉柱.马云.陈天桥.马化腾.丁磊.刘德建这6位IT大佬,其创立的巨人.阿里巴巴.盛大.腾讯.网易.网龙,无疑是中国互联网的6大骄子企业 ...

  8. Java 面试知识点解析(六)——数据库篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

  9. Java 面试知识点解析(七)——Web篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

最新文章

  1. ClewareControl 2.4 发布,传感器控制程序,增加对 python 的支持
  2. linux c 获取进程 cpu占用率 内存占用情况
  3. 关于搭建haddoop分布式系统的全部过程复习
  4. micropython开发idethonny_ESP32 Micropython开发利器Thonny IDE介绍
  5. muduo学习笔记 - 第1章 C++多线程系统编程
  6. 网络编程(part4)--刷新缓冲区
  7. 2019年Linux与开源如何统治技术圈
  8. Java LinkedList对象的get(int index)方法与示例
  9. MATLAB IIR滤波器设计函数buttord与butter
  10. 什么是ajax?ajax作用是什么?
  11. Xamarin教程一:创建Android项目(VS2019 C#写安卓)
  12. 转:android 简历
  13. 打印驱动的安装和卸载
  14. java duplicate key_java.lang.IllegalStateException: Duplicate key 1
  15. GDB调试器源代码分析系列--Inferior call的实现与分析(1)
  16. IDEA 报错 Cannot connect to the Maven process. If the problem persists, check the jdk.
  17. MATLAB实现自编码器(三)——堆栈自编码器Stacked Autoencoders实现手写数字分类
  18. LeetCode——6. Z 字形变换
  19. Python 将.py转换为.exe详解
  20. 远程直播服务器连接失败,哔哩哔哩直播姬连接失败如何解决 直播姬连接失败解决方法攻略大全...

热门文章

  1. echarts月份折线图
  2. 我们在淘宝京东拼多多买东西时,有哪些价格歧视?
  3. 【软件安装】Win11安装SolidWorks2018 跳过注册机error6报错
  4. Codeforces Round #411 (Div. 2) A-F
  5. MoveNet-谷歌轻量级人体姿态估计算法
  6. 杰理AC692X---开关机实现方式
  7. 清洁环保的小型风电滑环基本介绍
  8. 错误:10 http://ppa.launchpad.net/fcitx-team/nightly/ubuntu bionic Release 404 Not Foun
  9. jquery里面的循环遍历
  10. 微信分享链接或网站文章到微信朋友圈,缩略图片不显示,该如何解决?