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

BankService的功能为:某个用户有两个账户,分别为银行账户和保险账户,并且有各自的账户号,BankService的transfer方法从该用户的银行账户向保险账户转帐,两个DAO分别用于对两个账户表的存取操作。

定义一个BankService接口如下:

package davenkin;public interface BankService {public void transfer(int fromId, int toId, int amount);
}

在两个DAO对象中,我们通过传入的同一个DataSource获得Connection,然后通过JDBC提供的API直接对数据库进行操作。

定义操作银行账户表的DAO类如下:

package davenkin.step1_failure;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class FailureBankDao {private DataSource dataSource;public FailureBankDao(DataSource dataSource) {this.dataSource = dataSource;}public void withdraw(int bankId, int amount) throws SQLException {Connection connection = dataSource.getConnection();PreparedStatement selectStatement = connection.prepareStatement("SELECT BANK_AMOUNT FROM BANK_ACCOUNT WHERE BANK_ID = ?");selectStatement.setInt(1, bankId);ResultSet resultSet = selectStatement.executeQuery();resultSet.next();int previousAmount = resultSet.getInt(1);resultSet.close();selectStatement.close();int newAmount = previousAmount - amount;PreparedStatement updateStatement = connection.prepareStatement("UPDATE BANK_ACCOUNT SET BANK_AMOUNT = ? WHERE BANK_ID = ?");updateStatement.setInt(1, newAmount);updateStatement.setInt(2, bankId);updateStatement.execute();updateStatement.close();connection.close();}
}

FailureBankDao的withdraw方法,从银行账户表(BANK_ACCOUNT)中帐号为bankId的用户账户中取出数量为amount的金额。

采用同样的方法,定义保险账户的DAO类如下:

package davenkin.step1_failure;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class FailureInsuranceDao {private DataSource dataSource;public FailureInsuranceDao(DataSource dataSource){this.dataSource = dataSource;}public void deposit(int insuranceId, int amount) throws SQLException {Connection connection = dataSource.getConnection();PreparedStatement selectStatement = connection.prepareStatement("SELECT INSURANCE_AMOUNT FROM INSURANCE_ACCOUNT WHERE INSURANCE_ID = ?");selectStatement.setInt(1, insuranceId);ResultSet resultSet = selectStatement.executeQuery();resultSet.next();int previousAmount = resultSet.getInt(1);resultSet.close();selectStatement.close();int newAmount = previousAmount + amount;PreparedStatement updateStatement = connection.prepareStatement("UPDATE INSURANCE_ACCOUNT SET INSURANCE_AMOUNT = ? WHERE INSURANCE_ID = ?");updateStatement.setInt(1, newAmount);updateStatement.setInt(2, insuranceId);updateStatement.execute();updateStatement.close();connection.close();}
}

FailureInsuranceDao类的deposit方法向保险账户表(INSURANCE_ACCOUNT)存入amount数量的金额,这样在BankService中,我们可以先调用FailureBankDao的withdraw方法取出一定金额的存款,再调用FailureInsuranceDao的deposit方法将该笔存款存入保险账户表中,一切看似OK,实现BankService接口如下:

package davenkin.step1_failure;import davenkin.BankService;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;public class FailureBankService implements BankService{private FailureBankDao failureBankDao;private FailureInsuranceDao failureInsuranceDao;private DataSource dataSource;public FailureBankService(DataSource dataSource) {this.dataSource = dataSource;}public void transfer(int fromId, int toId, int amount) {Connection connection = null;try {connection = dataSource.getConnection();connection.setAutoCommit(false);failureBankDao.withdraw(fromId, amount);failureInsuranceDao.deposit(toId, amount);connection.commit();} catch (Exception e) {try {assert connection != null;connection.rollback();} catch (SQLException e1) {e1.printStackTrace();}} finally {try{assert connection != null;connection.close();} catch (SQLException e){e.printStackTrace();}}}public void setFailureBankDao(FailureBankDao failureBankDao) {this.failureBankDao = failureBankDao;}public void setFailureInsuranceDao(FailureInsuranceDao failureInsuranceDao) {this.failureInsuranceDao = failureInsuranceDao;}
}

在FailureBankService的transfer方法中,我们首先通过DataSource获得Connection,然后调用connection.setAutoCommit(false)已开启手动提交模式,如果一切顺利,则commit,如果出现异常,则rollback。 接下来,开始测试我们的BankService吧。

为了准备测试数据,我们定义个BankFixture类,该类负责在每次测试之前准备测试数据,分别向银行账户(1111)和保险账户(2222)中均存入1000元。BankFixture还提供了两个helper方法(getBankAmount和getInsuranceAmount)帮助我们从数据库中取出数据以做数据验证。我们使用HSQL数据库的in-memory模式,这样不用启动数据库server,方便测试。BankFixture类定义如下:

package davenkin;import org.junit.Before;
import javax.sql.DataSource;
import java.sql.*;public class BankFixture
{protected final DataSource dataSource = DataSourceFactory.createDataSource();@Beforepublic void setUp() throws SQLException{Connection connection = dataSource.getConnection();Statement statement = connection.createStatement();statement.execute("DROP TABLE BANK_ACCOUNT IF EXISTS");statement.execute("DROP TABLE INSURANCE_ACCOUNT IF EXISTS");statement.execute("CREATE TABLE BANK_ACCOUNT (\n" +"BANK_ID INT,\n" +"BANK_AMOUNT INT,\n" +"PRIMARY KEY(BANK_ID)\n" +");");statement.execute("CREATE TABLE INSURANCE_ACCOUNT (\n" +"INSURANCE_ID INT,\n" +"INSURANCE_AMOUNT INT,\n" +"PRIMARY KEY(INSURANCE_ID)\n" +");");statement.execute("INSERT INTO BANK_ACCOUNT VALUES (1111, 1000);");statement.execute("INSERT INTO INSURANCE_ACCOUNT VALUES (2222, 1000);");statement.close();connection.close();}protected int getBankAmount(int bankId) throws SQLException{Connection connection = dataSource.getConnection();PreparedStatement selectStatement = connection.prepareStatement("SELECT BANK_AMOUNT FROM BANK_ACCOUNT WHERE BANK_ID = ?");selectStatement.setInt(1, bankId);ResultSet resultSet = selectStatement.executeQuery();resultSet.next();int amount = resultSet.getInt(1);resultSet.close();selectStatement.close();connection.close();return amount;}protected int getInsuranceAmount(int insuranceId) throws SQLException{Connection connection = dataSource.getConnection();PreparedStatement selectStatement = connection.prepareStatement("SELECT INSURANCE_AMOUNT FROM INSURANCE_ACCOUNT WHERE INSURANCE_ID = ?");selectStatement.setInt(1, insuranceId);ResultSet resultSet = selectStatement.executeQuery();resultSet.next();int amount = resultSet.getInt(1);resultSet.close();selectStatement.close();connection.close();return amount;}}

编写的Junit测试继承自BankFixture类,测试代码如下:

package davenkin.step1_failure;import davenkin.BankFixture;
import org.junit.Test;
import java.sql.SQLException;
import static junit.framework.Assert.assertEquals;public class FailureBankServiceTest extends BankFixture
{@Testpublic void transferSuccess() throws SQLException{FailureBankDao failureBankDao = new FailureBankDao(dataSource);FailureInsuranceDao failureInsuranceDao = new FailureInsuranceDao(dataSource);FailureBankService bankService = new FailureBankService(dataSource);bankService.setFailureBankDao(failureBankDao);bankService.setFailureInsuranceDao(failureInsuranceDao);bankService.transfer(1111, 2222, 200);assertEquals(800, getBankAmount(1111));assertEquals(1200, getInsuranceAmount(2222));}@Testpublic void transferFailure() throws SQLException{FailureBankDao failureBankDao = new FailureBankDao(dataSource);FailureInsuranceDao failureInsuranceDao = new FailureInsuranceDao(dataSource);FailureBankService bankService = new FailureBankService(dataSource);bankService.setFailureBankDao(failureBankDao);bankService.setFailureInsuranceDao(failureInsuranceDao);int toNonExistId = 3333;bankService.transfer(1111, toNonExistId, 200);assertEquals(1000, getInsuranceAmount(2222));assertEquals(1000, getBankAmount(1111));}
}

运行测试,第一个测试(transferSuccess)成功,第二个测试(transferFailure)失败。

分析错误,原因在于:我们分别从FailureBankService,FailureBankDao和FailureInsuranceDao中调用了三次dataSource.getConnection(),亦即我们创建了三个不同的Connection对象,而Java事务是作用于Connection之上的,所以从在三个地方我们开启了三个不同的事务,而不是同一个事务。

第一个测试之所以成功,是因为在此过程中没有任何异常发生。虽然在FailureBankService中将Connection的提交模式改为了手动提交,但是由于两个DAO使用的是各自的Connection对象,所以两个DAO中的Connection依然为默认的自动提交模式。

在第二个测试中,我们给出一个不存在的保险账户id(toNonExistId),就是为了使程序产生异常,然后在assertion语句中验证两张表均没有任何变化,但是测试在第二个assertion语句处出错。发生异常时,银行账户中的金额已经减少,而虽然程序发生了rollback,但是调用的是FailureBankService中Connection的rollback,而不是FailureInsuranceDao中Connection的,对保险账户的操作根本就没有执行,所以保险账户中依然为1000,而银行账户却变为了800。

因此,为了使两个DAO在同一个事务中,我们应该在整个事务处理过程中使用一个Connection对象,在下一篇文章中,我们将讲到通过共享Connection对象的方式达到事务处理的目的。

转载于:https://www.cnblogs.com/fengjian/p/4209651.html

Java事务处理全解析(二)——失败的案例相关推荐

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

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

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

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

  3. java生成以及解析二维码

    java生成以及解析二维码 欢迎使用Markdown编辑器 新的改变 功能快捷键 合理的创建标题,有助于目录的生成 如何改变文本的样式 插入链接与图片 如何插入一段漂亮的代码片 生成一个适合你的列表 ...

  4. Java生成和解析二维码工具类(简单经典)

    Java生成和解析二维码工具类 开箱即用,简单不废话. pom.xml引入依赖 <!-- https://mvnrepository.com/artifact/com.google.zxing/ ...

  5. Java 面试知识点解析(二)——高并发编程篇

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

  6. Java生成和解析二维码

    前言:曾经有做过不少微信公众号和移动网站的项目,对二维码还算有点了解,刚收到这个任务的时候就想着竟然要用二维码存文本,那就得先考究一下这小小的二维码到底能存多少的东西了. 需求:使用二维码存放文本(x ...

  7. Java生成与解析二维码

    1.下载支持二维码的jar包qrcode.jar和qrcode_swetake.jar, 其中qrcode_swetake.jar用于生成二维码,rcode.jar用于解析二维码,jar包下载地址(免 ...

  8. java生成和解析二维码实战——QRCode

    直接上代码,以下程序可直接运行: package qrcode;import java.awt.Color; import java.awt.Graphics2D; import java.awt.i ...

  9. java生成与解析二维码 支持插入图片与文字

    1.依赖: <!-- https://mvnrepository.com/artifact/com.google.zxing/core --><dependency><g ...

最新文章

  1. 突然就懵了!面试官问我:线程池中多余的线程是如何回收的?
  2. 管理-Tomcat和Resin如何配置对指定后缀文件(如:.pptx)下载支持
  3. INADDR_ANY的理解
  4. 编码时的一些普适原则
  5. 【大会】AI能解决哪些问题?
  6. 常用的机器学习数据挖掘知识点【转】
  7. scrapy ip地址 tcp time out_TCP的运作流程(一)——“三次握手”
  8. 交互系统的构建之(一)重写Makefile编译TLD系统
  9. IOS中延时执行的几种方式的比较和汇总
  10. mysql 求平方_如何用MySQL求一个范围内的完全平方数
  11. STM STC NXP单片机 按键扫描
  12. 复杂网络实验2:WS小世界模型(matlab)
  13. 云服务器被攻击了快速解决方案
  14. 怎么把在线网站保存的flv格式转换成mp4
  15. 发送RST报文的几种可能的情况
  16. 卢卡斯定理求组合数(逆元+费马小定理+扩展欧几里得)
  17. java如何等待异步结果_你如何等待所有异步调用在Java中完成?
  18. Caliburn.Micro将枚举 绑定到ComboBox
  19. 我的新书 asp net开发技巧精讲
  20. 『中级篇』docker导学(一)

热门文章

  1. node运行swagger-editor
  2. MySQL使用用户变量需确定取值的顺序
  3. Linux登陆Mariadb数据库,Mariadb数据库的远程连接(centos 7+ Navicat)
  4. Centos7入侵分析:分析SSH登录日志
  5. 奇异值分解 VS 特征值分解
  6. 交叉编译和交叉调试环境的搭建及使用
  7. 操作系统外壳(shell)
  8. c语言编译生成obj,GCC编译Objective-C源文件
  9. win7 linux 共享文件夹权限设置,samba 配置共享 win7 无权限访问
  10. GIt思维导图命令+案例分析