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)配置文件








  
  
  
  
  




    



    




(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.何为数据库事务

 事务是一系列操作组成的工作单元,该工作单位内的操作是不可分割的,即要么所有的操作都执行,要么所有的操作都不执行
    
 事务必需满足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:默认就是支持的

你可能感兴趣的:(Spring(5)-(20)事务相关之转账案例)