Spring(5)-(20)事务相关之转账案例

一.代码演示,引出事务的概念

(1)Accout类

package com.keen.proxy.domain;

public class Accout {
    private int id;
    private int banlance;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public int getBanlance() {
        return banlance;
    }
    public void setBanlance(int banlance) {
        this.banlance = banlance;
    }
    

}

(2)IAccoutDAO接口

package com.keen.proxy.dao;

public interface IAccoutDAO {
    //转出操作
    void transOut(int outId ,int money) ;
    //转入操作
    void transInt(int inId ,int money);
}

(3)IAccoutDAO实现类

package com.keen.proxy.dao.impl;

import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;

import com.keen.proxy.dao.IAccoutDAO;

public class IAccoutDAOImpl implements IAccoutDAO {
    private JdbcTemplate jdbcTemplate; 
    
    public void setDataSource(DataSource ds) {
        this.jdbcTemplate = new JdbcTemplate(ds);
        
    }
    
    public void transOut(int outId, int money) {
        jdbcTemplate.update("update accout set balance =balance -? where id = ?",money , outId);
        
    }

    
    public void transInt(int inId, int money) {
        jdbcTemplate.update("update accout set balance =balance +? where id = ?",money , inId);
        
        
    }

}


(4)IAccoutService接口(转账服务)

package com.keen.proxy.service;

public interface IAccoutService {
    //做转账事务
    void trans(int outId ,int inId ,int money) ;

  
}

(5) IAccoutSerivce的实现

package com.keen.proxy.service.impl;

import com.keen.proxy.dao.IAccoutDAO;
import com.keen.proxy.service.IAccoutService;

public class IAccoutSerivceImpl implements IAccoutService{

    IAccoutDAO dao = null;
    public void setIAccoutDAO(IAccoutDAO dao) {
        this.dao = dao;
    

    }
    public void trans(int outId, int inId, int money) {
       dao.transOut(outId, money);
       // int i = 1/0;  模仿程序出错,这样就会出现一方转账了,钱减少了,而另一方却因程序中断而收不到转账金额。
       dao.transInt(inId, money);
        
    }

}

(6)配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--  从classPath的路径去加载db.properties文件 -->
<context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER" />
<!-- 配置一个durid的连接池 -->

<bean id = "dataSource" class ="com.alibaba.druid.pool.DruidDataSource"
       init-method = "init" destroy-method="close">
  <property name = "driverClassName" value ="${dirverClassName}"/>
  <property name = "url" value ="${url}"/>
  <property name = "username" value ="${username}"/>
  <property name = "password" value ="${password}"/>
  <property name = "initialSize" value ="${initialSize}"/>

</bean>
<!-- 配置dao -->
<bean id = "accoutDAO" class = "com.keen.proxy.dao.impl.IAccoutDAOImpl">
    <property name="dataSource" ref = "dataSource"/>
</bean>
<!-- 配置service -->
<bean id ="service" class = "com.keen.proxy.service.impl.IAccoutSerivceImpl">
    <property name = "iAccoutDAO" ref = "accoutDAO"/>
</bean>

</beans>

(7)测试

package com.keen.proxy;


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

import com.keen.proxy.service.IAccoutService;

@SpringJUnitConfig
public class AutoTest {
   
    @Autowired
    private IAccoutService service;
    
    @Test
    void testAccout() throws Exception {
    
        service.trans(10086, 10010, 500);
    }

}

二.转账过程分析

1.service ,掉调用DAO的转出方法--->发送转出的sql语句
2.service ,再调用DAO的转入方法---->发送转出SQL语句
⚠️:jdbc默认情况下,事务是自动提交的,获取一个connection对象的时候,事务已经打开了,执行完DML语句就自动提交事务
⚠️:因此当上面的程序发现异常就会出现转账不到帐的异常,因为转出装入两个操作根本就不在同一个事务中。

思路修改:

1.获取dataSource对象
2.通过DataSource对象获取数据库连接对象(Connection)
3.取消事务的自动提交机制  connection.setAutoCommit(false)
4.把connection对象绑定在当前线程中
5.在DAO的方法中,从当前线程中获取出connection对象,执行操作
6.如果整个service方法都正常执行,中途没有异常:提交事务 ,connection.commit()
7.如果service方法中出现任何异常: 回滚事务,connection.rollback()

IAccoutSerivce的实现 代码改为:

1.取消事务的自动提交机制
2.获取连接对象
try{

    public void trans(int outId, int inId, int money) {
          dao.transOut(outId, money);
          // int i = 1/0;  模仿程序出错,这样就会出现一方转账了,钱减少了,而另一方却因程序中断而收不到转账金额。
          dao.transInt(inId, money);
           
       }
       
}catch(Expection e){
    回滚事务.
}       

三.其实在xml配置文件中,我们配置一下事务管理器相关的操作就可以避免这次额问题了。

<!-- *********************看这里!看这里!看这里!**************************** -->
<!--1.what: 配置事务管理器的JDBC,需要一个连接池属性 -->
<bean id = "txManager" class = "org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref = "dataSource"/>
</bean>

<!-- 2.when:配置事务管理器增强 -->
<tx:advice id ="txAdvice" transaction-manager ="txManager">
    <tx:attributes>
       <tx:method name="trans"/>
    </tx:attributes>
</tx:advice> 

<!-- 3.where :配置切面 -->
<aop:config>
    <aop:pointcut expression="execution(* com.keen.proxy.service.*Service.*(..))" id="txPc"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPc"/>
</aop:config>


//补充说明:
<!-- ****配置一个通用事务配置**** -->
<tx:advice id="crudAdvice" transaction-manager="txManager">
  <tx:attributes>
  <!-- service中的查询方法 -->
  <tx:method name="get" read-only="true" propagation="REQUIRED"/>
  <tx:method name="list" read-only="true" propagation="REQUIRED"/>
  <tx:method name="query" read-only="true" propagation="REQUIRED"/>
  <!-- service中的其他方法(非查询) -->
  <tx:method name="*" propagation="REQUIRED"/>
  </tx:attributes>

</tx:advice>

四.事务的回顾

1.何为数据库事务

 事务是一系列操作组成的工作单元,该工作单位内的操作是不可分割的,即要么所有的操作都执行,要么所有的操作都不执行
    
 事务必需满足ACID(原子性,一致性,隔离性,和持久性)
 原子性(Atomicity):事务是不可分割的最小单位,事务内的操作要么全做,要不都不做;
 一致性(Consistency):事务执行前数据处于正确的状态,而事务执行后数据库的数据依然处于正确的状态,即数据完整性约束没有被破坏,如A给B转帐,无论是否转账成功,A和B之间的账户总额和转账前的总额是一致。
 隔离性(Isolation):当多个事务处于并发访问同一个数据资源时,事务之间相互影响程度,不同的隔离级别决定了各个事务对数据资源访问的不同行为。
 持久性(Durability):事务一旦执行成功,它对数据库的数据的改变是不可逆。

2.数据库并发问题

并发问题类型            产生原因和效果
第一类丢失更新          两个事务更新相同数据,如果一个事务提交,另一个事务回滚,第一个事务的更新就会被回滚

脏读                  第二个事务查询到第一个事务未提交的更新数据,第二个事务根据该数据执行,单第一个事务回滚,第二个事务操作脏数据

虚读                  一个事务查询到了另一个事务已经提交的新数据,导致多次查询的数据不一致

不可重复读             一个事务查询另一个事务已经修改的数据,导致多次查询的数据不一致

第二类丢失更新          多个事务同时读取相同数据,并完成各自的事务提交,导致最后一个事提交会覆盖前面所有事务对数据的改变

3.事务的隔离级别

为了解决这些并发问题,需要通过数据库级别来解决,在标准SQL规范中定义了四种隔离级别(√表示可能出现的情况)

隔离级别          脏读     不可重复读    幻读    第一类丢失更新     第二类丢失更新

READ UNCOMMITED   √        √           √         X               √
READ COMMITED     X         √           √          √              √
REPREATABLE READ  X          X          √           √              √      
SERIALIZABLE       X          X          X           X               X

Mysql支持四种隔离级别,缺省时为 REPREATABLE READ

Oracle支持 READ COMMITED (缺省) SERIALIZABLE

如何选用

隔离级别越高,数据库事务并发执行性能越差,能处理的操作越少。

因此时间项目开发中为了考虑并发性能一般使用 READ COMMITED,它呢避免丢失更新和脏读,尽管不可重复读和幻读不能避免

更多情况下使用悲观锁和乐观锁来解决。

四.事务的类型

1,本地事务和分布式事务

本地事务:就是普通事务,能保证单台数据库上的操作的ACID,被限定在一台数据库

分布式事务:涉及多个数据库源的事务,即跨多台同类或异类数据库的事务(由每台数据库的本地事务组成),分布式事务旨在保证这些本地事务的所有操作的ACID,使事务可以跨越多台数据库

2,jdbc事务和jta事务

 JDBC事务:就是数据库事务类型中的本地事务,通过Connection对象的控制来管理事务
 
 JTA事务:JTA指(java Transaction API),是java EE数据库事务规范,JTA只提供了事务管理接口,由应用程序服务器厂商提供实现,JTA事务比JDBC更强大,支持分布式事务

3,按是否通过编程实现事务:

   编程式事务:通过编写代码来管理事务
   声明式事务:通过注解或xml配置来管理事务

五.Spring对事务支持的API

spring的事务管理主要有3个接口:

(1)platformTransactionManager: 
    根据TransactionDefinition提供的事务属性配置信息,创建事务

(2)TransactionDefinition:
    封装事务的隔离级别和超时时间,是否为只读事务和事务的隔离级别和传播规则等事务属性
    
(3) TransactionStatus:封装了事务的具体运行状态,如是否式新开启事务,是否已经提交事务,设置当前事务为rollback-only等

六.事务传播规则

在一个事务方法中调用其他事务方法,此时事务该如何传播 ,按照什么规则传播,用谁的事务,还是都不用...等等。

情况一:需要遵从当前事务
REQUIRED:必须存在一个事务,如果当前存在一个事务,则加入到该事务中,否则新建一个事务,使用比较多

SUPPORT:支持当前事务,如果当前存在事务,则使用该事务,否则,以非事务形式运行

MANDATORY:必须要存在事务,如果当前存在事务,则使用该事务,否则,出现非法的事务状态异常

情况二:不遵从当前事务
REQUIRES_NEW: 不管当前是否存在事务,都会新开启一个事务,必须是一个新的事务,使用的比较多
  
NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,把当前事务挂起(暂停)

NEVER:不支持事务,如果当前存在事务,抛出一个异常

情况三:寄生事务(外部事务/内部事务/嵌套事务)
NESTED:寄生事务,如果当前存在事务,则在内部事务内执行
               如果当前不存在事务,则创建一个新的事务 ,寄生事务通过数据库savePoint(保存点)来实现,寄生事务可以回滚的,但是它回滚不影响外部事务,但是外部事务的回滚回影响寄生事务

寄生事务并不是所以有的事务管理器都支持,比如HibernateTransactionManager默认就不支持,需要手动去开启 JDBC和MyBatis的事务管理器:DataSourceTransactionManager:默认就是支持的

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 很多人喜欢这篇文章,特此同步过来 由浅入深谈论spring事务 前言 这篇其实也要归纳到《常识》系列中,但这重点又...
    码农戏码阅读 4,706评论 2 59
  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,898评论 2 89
  • JDBC概述 在Java中,数据库存取技术可分为如下几类:JDBC直接访问数据库、JDO技术、第三方O/R工具,如...
    usopp阅读 3,530评论 3 75
  • 针灸康复学院的老师想让我们帮忙做一个能够播放针灸相关视频的网站,用户是本校的学生。这次项目中我除了负责界面设计以外...
    十0十阅读 224评论 0 0
  • 一份淡泊 一世安稳 留住闲情 给自己更多的快乐 拥有快乐 便是人生醉美的风景 看山山含笑 看水水怡人 人生路遥 欲...
    胡杨公主阅读 227评论 1 13