java中使用事务案例_Java事务处理全解析(四)—— 成功的案例(自己实现一个线程安全的TransactionManager)...
在本系列的上一篇文章中我们讲到,要实现在同一个事务中使用相同的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)...相关推荐
- java jta 例子_Java事务处理全解析(八)——分布式事务入门例子(Spring+JTA+Atomikos+Hibernate+JMS)...
在本系列先前的文章中,我们主要讲解了JDBC对本地事务的处理,本篇文章将讲到一个分布式事务的例子. 请通过以下方式下载github源代码: 本地事务和分布式事务的区别在于:本地事务只用于处理单一数据源 ...
- Java中的事务——全局事务与本地事务
转载自 Java中的事务--全局事务与本地事务 在上一篇文章中说到过,Java事务的类型有三种:JDBC事务.JTA(Java Transaction API)事务.容器事务. 这是从事务的实现角 ...
- Java中的事务——JDBC事务和JTA事务
转载自 Java中的事务--JDBC事务和JTA事务 我的博客中曾经关于事务有过很多讨论,之前的事务介绍基本都是数据库层面的事务,本文来介绍一下J2EE中和事务相关的内容,在阅读本文之前,希望读者对分 ...
- java中审核订单流程图_Java 后端横扫阿里、滴滴、美团总结的面试经验!
这次面试的公司有一点点多,主要是因为毕业后前两份工作找的都很草率,这次换工作就想着,emm,毕业三年了,该找个工作好好沉淀几年了. 先说下这次面试的结果吧: 到 hr 面的:阿里.美团.滴滴.金山云. ...
- java中u怎么用_Java中interrupt的使用
通常我们会有这样的需求,即停止一个线程.在java的api中有stop.suspend等方法可以达到目的,但由于这些方法在使用上存在不安全性,会带来不好的副作用,不建议被使用.具体原因可以参考Why ...
- java中字符串的算法_Java中的字符串搜索算法
我正在使用大量数据进行字符串匹配. 编辑:我正在匹配一个大列表中的单词与一些本体文本文件.我从本体中获取每个文件,并搜索每个文件行的第三个字符串与列表中的任何单词之间的匹配. 我在监督这样一个事实上犯 ...
- java中抽牌程序_Java—— 随机抽取扑克牌游戏
/* * Copyright (c) 2014, 烟台大学计算机学院 * All rights reserved. * 文件名称:test.cpp * 作 者:李晓凯 * 完成日期:2015年 ...
- Java中[xxx:xxx,aaa:aaa]格式字符串解析
Java中[xxx:xxx,aaa:aaa]格式字符串解析 String str = "[name:张三,age:18,phone:15888887777,email:15888887777 ...
- java中使用事务案例_Java事务之四——成功的案例
在本系列的上一篇文章中我们讲到,要实现在同一个事务中使用相同的Connection对象,我们可以通过传递Connection对象的方式达到共享的目的,但是这种做法是丑陋的.在本篇文章中,我们将引入另外 ...
最新文章
- 用WINHEX合并两个或多个BIN文件
- KClient——kafka消息中间件源码解读
- 使用C#编程解决数独求解(从图片识别到数独求解)
- 如何实现REST资源的输入验证
- Java50道经典习题-程序18 乒乓球赛
- 机器学习爬大树之决策树(ID3,C4.5)
- 荣耀v10玩flash游戏_沫子玩王者荣耀被打哭?直言这个游戏比吃鸡还难玩
- asp.net 用正则表达式过滤内容中的电话,qq,email
- Android Jetpack基础组件之AppCompat
- python实现图片嗅探工具——自编driftnet
- 求内切圆半径的c语言编程,内切圆半径公式推导
- 国家信息安全证书体系解读(nisp,cisp)
- python递归排列组合_Python 排列组合
- Edward Frenkel关于几何化朗兰兹纲领的采访
- 服务器主板最多能装几个cpu,双路主板能不能只用一块CPU?
- 用 JAVA 实现画板
- 当你凝视深渊时,深渊也在凝视着你。
- PHP使用PDO连接带密码Access数据库(简单版)
- ACL 2020 MART: Memory-Augmented Recurrent Transformer for Coherent Video Paragraph Captioning
- 服务器参数知多少 带你一一认识这些参数
热门文章
- 可观测性-可视化-Grafana中table列的gradient guage填充度问题
- java对视频进行截图
- 软磁盘属于微型计算机的什么设备,2014云南省全国计算机等级考试二级VB笔试试卷及参考答案最新考试试题库...
- 父组件传值给子组件子组件向父组件传值的方法
- 脑机接口信号基本操作回顾
- 没去阿里就职,我获得了什么
- Microsoft Edge 从老版本升级到chromium 内核后,打开所有网页报此页存在问题
- 【Debug】关于Could not get lock /var/lib/dpkg/lock-frontend解决办法
- 台湾 计算机术语,台湾电脑术语与大陆术语对比表
- 金吧台台球计费系统会员导出教程