Java事务之二——失败的案例

Java事务失效的七种方式 在Java中,事务管理通常由Spring框架提供支持,通过使用@Transactional注解来声明事务。然而,在某些情况下,事务可能会失效,导致数据不一致或其他问题。以下是几种常见的Java事务失效情况及其原因和解决方法。 阅读详情

Java事务之二——失败的案例

本文转载自: http://www.cnblogs.com/davenkin/archive/2013/02/17/java-transaction-2.html,侵删。

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

  请通过以下方式下载github源代码:

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

 

  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();

    @Before
    public 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
{
    @Test
    public 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));

    }

    @Test
    public 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对象的方式达到事务处理的目的。

Java事务处理全解析(二)—— 失败的案例 在本系列的上一篇文章中,我们讲到了Java事务处理的基本问题,并且讲到了Service层和DAO层,在本篇文章中,我们将以BankService为例学习一个事务处理失败的案例。   请通过以下方式下载github源代码: git clone https://github.com/davenkin/java_transaction_workshop.git   Bank 阅读详情

相关推荐

Java大事务导致并发更新失败,如何解决?

🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!

**My Coding Family** 1231

JAVA 事务不生效的常见场景和修改方案

java事务是很多初学者和初级乃至于中级程序员不明白的点,绝大多数情况都是使用声明式事务也就是在方法上面来个@Transactional(rollbackFor = Exception.class),会出现事务没有回滚的情况,很多博客都有写,这篇博客主要是整理和记录一下事务不生效的情况和处理方法,如有问题可以私信或评论............

someaure的博客 6252

给新人的储能系统入门图解:从电芯到EMS,一张图看懂BESS里那些‘黑话’

本文通过视觉化拆解储能系统,从电芯到EMS详细解析BESS中的关键组件和行业术语。涵盖电芯参数、模组组装、PCS功能、BMS与EMS分工,以及SOC、DOD等关键运维参数,帮助新人快速理解储能系统的核心技术和应用场景。

weixin_42522964的博客 594

java spring 事务失效的12种情况

2. 方法用final修饰 3.方法内部调用 有时候我们需要在某个Service类的某个方法中,调用另外一个事务方法,比如: 在该Service类中注入自己; 在该Service类中注入自己也是一种选择。具体代码如下: 4.未被spring管理 没有添加 @Controller、@Service、@Component、@Repository等注解 5.多线程调用 两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务 6.表不支持事务 数据库 存储引擎 不

myStyle的博客 2935

S32K344 Flash Driver实战:手把手教你实现任意字节写入与扇区解锁(含C40_Ip避坑指南)

本文深入解析S32K344微控制器的Flash驱动开发,重点解决C40_Ip驱动库的8字节对齐限制和扇区保护锁问题。通过实战案例和代码示例,详细介绍了如何实现任意字节写入、扇区解锁及性能优化策略,为Bootloader和OTA升级等应用提供可靠解决方案。

weixin_33713707的博客 432

Sony DPT RP1/CP1破解攻略

前言 自己有一台Sony DPT CP1和一台kindle,两个都已经用了很多年。直到最近发现微信读书上经常可以找到一些免费而且不错的书,所以就萌生了在两台机器上使用微信读书的想法。kindle比较容易实现,直接打开内置浏览器,进入微信读书网页版就行了,而Sony的电纸书则只能通过破解+安装微信读书的方式实现。大概弄了两三个小时吧,也遇到了一些问题,所以这里记录一下整个过程,希望能方便后来的使用者。 首先感谢HappyZ, osc_t5nbj8ds。前者是提供公开破解的方法的大神,后者则是解决了我遇到的问题

weixin_43467572的博客 1万+

数据库备份与恢复简介

1、事务故障:是由于程序执行错误而引起事务非预期的、异常终止的故障。通常有如下两类错误引起事务执行失败:(1)逻辑错误。如非法输入、找不到数据、溢出、超出资源限制等原因引起的事务执行失败。(2)系统错误。系统进入一种不良状态(如死锁),导致事务无法继续执行。2、系统故障:是指硬件故障、软件(如DBMS、OS或应用程序)漏洞的影响,导致丢失了内存中的信息,影响正在执行的事务,但未破坏存储在外存上的信息。3、介质故障:是指数据库的存储介质发生故障,如磁盘损坏、瞬间强磁场干扰等。

wdywxwxld的博客 1908

Upwork数据战争:用爬虫与AI预测垄断订单

当普通自由职业者还在“等客户发需求”时,顶尖玩家已用数据预测客户明天需要什么; 当竞争对手熬夜抢单时,你的爬虫早已自动提交提案—— 这不是科幻,而是2025年的现实! 本文将手把手教你用合法工具搭建“数据战情室”,让每个决策都有万亿级数据支撑!

qq_61813593的博客 982

Java事务提交失败_Java事务之二——失败的案例

在本系列的上一篇文章中,我们讲到了Java事务处理的基本问题,并且讲到了Service层和DAO层,在本篇文章中,我们将以BankService为例学习一个事务处理失败的案例。请通过以下方式下载github源代码:git clone https://github.com/davenkin/java_transaction_workshop.gitBankService的功能为:某个用户有两个账户,...

weixin_39614146的博客 303

深入理解 Terraform 中的资源更新与管理

摘要:本文探讨Terraform处理AWS安全组名称变更时的行为。当修改不可变属性(如安全组名称)时,Terraform会创建新资源并删除旧资源。通过状态文件比对所需状态与实际状态,Terraform对不可变属性采用"先创建后删除"策略,对可变属性则直接更新。实例演示了修改安全组名称和关联EC2实例时的处理过程,强调理解资源属性可变性对基础设施管理的重要性。(149字)

2501_90323865的博客 471

ABB变频器使用PROFINET IO通信模块时的激活方法

ABB变频器使用PROFINET IO通信模块时的激活方法

Robot_PLC_HMI_Automation 2734

Java事务提交失败_Java事务处理全解析(二)——失败的案例

在本系列的上一篇文章中,我们讲到了Java事务处理的基本问题,并且讲到了Service层和DAO层,在本篇文章中,我们将以BankService为例学习一个事务处理失败的案例。请通过以下方式下载github源代码:git clone https://github.com/davenkin/java_transaction_workshop.gitBankService的功能为:某个用户有两个账户,...

weixin_42280315的博客 720

【HALCON工业视觉应用探索】02. C# + Halcon铸件毛刺机器人打磨引导:点云处理从实验室到工业现场的全流程落地

摘要:铸件毛刺的自动化打磨是机械制造领域的重要难题,其随机性(高度0.1-3mm)和复杂工况导致人工打磨效率低(3分钟/件)、工伤率高。本文基于Halcon 24.11与.NET 6,构建了一套点云处理方案:通过线激光扫描仪获取三维点云,经预处理、毛刺检测、路径规划三大模块,引导机器人完成打磨。虚拟实验测试显示,对高度>1mm的毛刺检出率达100%,定位误差±0.08mm,但工业现场受油污、粉尘影响,性能降至检出率68-75%、处理时间>45秒。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 1128

java回滚失败_java事务回滚失败问题分析

Spring-Java事物回滚失效处理最近在做项目中,无意间发现有个类在抛事物回滚操作,数据也正常的插入到数据库当中了,于是仔细查看看一下具体原因。一切还是要从Java的检查型异常和非检查型异常说起。那么什么是检查型异常什么又是非检查型异常呢?最简单的判断点有两个:1.继承自RuntimeException或Error的是非检查型异常,而继承自Exception的则是检查型异常(当然,Runtim...

weixin_36225424的博客 1814

《大数据最全面试题-Offer直通车》目录

做好这些不用担心试用期不通过:程序员入职新公司如何快速上手项目本人遇到大数据面试题和参考答案(超过1万字精华版)大数据面试英文自我介绍参考(万字长文)大数据运维应用场景面试题汇总及参考答案(持续更新)万字数据仓库面试题及参考答案数据仓库数据质量监控和处理方法最佳实践数据仓库建模方法万字详解Doris的3种数据模型详解和数据仓库每一层的模型选用大数据面试临阵磨枪不知看什么?看这份心理就有底了-大数据常用技术栈常见面试100道题最全Hive面试题2024年(2万字详解)Elasticsearch 面试题及参考答

大模型大数据攻城狮的专栏 2642

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

java事务学习笔记(二)--失败案例

在本系列的上一篇文章中,我们讲到了Java事务处理的基本问题,并且讲到了Service层和DAO层,在本篇文章中,我们将以BankService为例学习一个事务处理失败的案例。 请通过以下方式下载github源代码: git clone https://github.com/davenkin/java_transaction_workshop.git   Ban

JordanInShenzhen的博客 617

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

java事务启动失败_Java事务处理全解析(二)——失败的案例

在本系列的上一篇文章中,我们讲到了Java事务处理的基本问题,并且讲到了Service层和DAO层,在本篇文章中,我们将以BankService为例学习一个事务处理失败的案例。BankService的功能为:某个用户有两个账户,分别为银行账户和保险账户,并且有各自的账户号,BankService的transfer方法从该用户的银行账户向保险账户转帐,两个DAO分别用于对两个账户表的存取操作。定义一...

weixin_36274103的博客 243

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

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

在本系列的上一篇文章中,我们讲到了Java事务处理的基本问题,并且讲到了Service层和DAO层,在本篇文章中,我们将以BankService为例学习一个事务处理失败的案例。 BankService的功能为:某个用户有两个账户,分别为银行账户和保险账户,并且有各自的账户号,BankService的transfer方法从该用户的银行账户向保险账户转帐,两个DAO分别用于对两个账户表的存取操...

weixin_30415113的博客 75

java 事务回滚失败

Spring-Java事物回滚失效处理最近在做项目中,无意间发现有个类在抛事物回滚操作,数据也正常的插入到数据库当中了,于是仔细查看看一下具体原因。 一切还是要从Java的检查型异常和非检查型异常说起。 那么什么是检查型异常什么又是非检查型异常呢?  最简单的判断点有两个:     1.继承自RuntimeException或Error的是非检查型异常,而继承自Exception的

qq_34406670的博客 2427

Java事务(2)——失败的案例

在本系列的上一篇文章中,我们讲到了Java事务处理的基本问题,并且讲到了Service层和DAO层,在本篇文章中,我们将以BankService为例学习一个事务处理失败的案例。   BankService的功能为:某个用户有两个账户,分别为银行账户和保险账户,并且有各自的账户号,BankService的transfer方法从该用户的银行账户向保险账户转帐,两个DAO分别用于对两个账户表的存取

Leesire的专栏 640

java回滚失败_求教,事务回滚失败

该楼层疑似违规已被系统折叠隐藏此楼查看此楼我同时执行了两条sql语句,第二条故意写少一个问号,按道理错误了应该回滚两条sql语句都没成功才对,可是结果第一条sql语句还是成功执行插入了。新手求教。public int addStu(Student stu){int flag=0;PreparedStatement ps=null;Connection conn=ConnBean.getConne...

weixin_42134097的博客 414
上一篇: Java事务之一——Java事务的基本问题
下一篇: Java事务之三——丑陋的案例
TaiSung
博客等级 码龄15年 49粉丝 132原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值