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

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

package davenkin.step3_connection_holder;

import javax.sql.DataSource;

import java.sql.Connection;

import java.sql.SQLException;

import java.util.HashMap;

import java.util.Map;

public class ConnectionHolder

{

private Map connectionMap = new HashMap();

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类如下:

package davenkin.step3_connection_holder;

import javax.sql.DataSource;

import java.sql.Connection;

import java.sql.SQLException;

public class SingleThreadConnectionHolder

{

private static ThreadLocal localConnectionHolder = new ThreadLocal();

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类将事务处理相关工作集中管理:

package davenkin.step3_connection_holder;

import javax.sql.DataSource;

import java.sql.Connection;

import java.sql.SQLException;

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:

package davenkin.step3_connection_holder;

import davenkin.BankService;

import javax.sql.DataSource;

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模式来完成事务处理。

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

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

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

  2. Java中的事务——全局事务与本地事务

    转载自   Java中的事务--全局事务与本地事务 在上一篇文章中说到过,Java事务的类型有三种:JDBC事务.JTA(Java Transaction API)事务.容器事务. 这是从事务的实现角 ...

  3. Java中的事务——JDBC事务和JTA事务

    转载自 Java中的事务--JDBC事务和JTA事务 我的博客中曾经关于事务有过很多讨论,之前的事务介绍基本都是数据库层面的事务,本文来介绍一下J2EE中和事务相关的内容,在阅读本文之前,希望读者对分 ...

  4. java中审核订单流程图_Java 后端横扫阿里、滴滴、美团总结的面试经验!

    这次面试的公司有一点点多,主要是因为毕业后前两份工作找的都很草率,这次换工作就想着,emm,毕业三年了,该找个工作好好沉淀几年了. 先说下这次面试的结果吧: 到 hr 面的:阿里.美团.滴滴.金山云. ...

  5. java中u怎么用_Java中interrupt的使用

    通常我们会有这样的需求,即停止一个线程.在java的api中有stop.suspend等方法可以达到目的,但由于这些方法在使用上存在不安全性,会带来不好的副作用,不建议被使用.具体原因可以参考Why ...

  6. java中字符串的算法_Java中的字符串搜索算法

    我正在使用大量数据进行字符串匹配. 编辑:我正在匹配一个大列表中的单词与一些本体文本文件.我从本体中获取每个文件,并搜索每个文件行的第三个字符串与列表中的任何单词之间的匹配. 我在监督这样一个事实上犯 ...

  7. java中抽牌程序_Java—— 随机抽取扑克牌游戏

    /* * Copyright (c) 2014, 烟台大学计算机学院 * All rights reserved. * 文件名称:test.cpp * 作    者:李晓凯 * 完成日期:2015年 ...

  8. Java中[xxx:xxx,aaa:aaa]格式字符串解析

    Java中[xxx:xxx,aaa:aaa]格式字符串解析 String str = "[name:张三,age:18,phone:15888887777,email:15888887777 ...

  9. java中使用事务案例_Java事务之四——成功的案例

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

最新文章

  1. 用WINHEX合并两个或多个BIN文件
  2. KClient——kafka消息中间件源码解读
  3. 使用C#编程解决数独求解(从图片识别到数独求解)
  4. 如何实现REST资源的输入验证
  5. Java50道经典习题-程序18 乒乓球赛
  6. 机器学习爬大树之决策树(ID3,C4.5)
  7. 荣耀v10玩flash游戏_沫子玩王者荣耀被打哭?直言这个游戏比吃鸡还难玩
  8. asp.net 用正则表达式过滤内容中的电话,qq,email
  9. Android Jetpack基础组件之AppCompat
  10. python实现图片嗅探工具——自编driftnet
  11. 求内切圆半径的c语言编程,内切圆半径公式推导
  12. 国家信息安全证书体系解读(nisp,cisp)
  13. python递归排列组合_Python 排列组合
  14. Edward Frenkel关于几何化朗兰兹纲领的采访
  15. 服务器主板最多能装几个cpu,双路主板能不能只用一块CPU?
  16. 用 JAVA 实现画板
  17. 当你凝视深渊时,深渊也在凝视着你。
  18. PHP使用PDO连接带密码Access数据库(简单版)
  19. ACL 2020 MART: Memory-Augmented Recurrent Transformer for Coherent Video Paragraph Captioning
  20. 服务器参数知多少 带你一一认识这些参数

热门文章

  1. 可观测性-可视化-Grafana中table列的gradient guage填充度问题
  2. java对视频进行截图
  3. 软磁盘属于微型计算机的什么设备,2014云南省全国计算机等级考试二级VB笔试试卷及参考答案最新考试试题库...
  4. 父组件传值给子组件子组件向父组件传值的方法
  5. 脑机接口信号基本操作回顾
  6. 没去阿里就职,我获得了什么
  7. Microsoft Edge 从老版本升级到chromium 内核后,打开所有网页报此页存在问题
  8. 【Debug】关于Could not get lock /var/lib/dpkg/lock-frontend解决办法
  9. 台湾 计算机术语,台湾电脑术语与大陆术语对比表
  10. 金吧台台球计费系统会员导出教程