海创软件组-20200503-Spring数据库事务管理(一)

声明式事务管理

…Spring 的声明式事务管理是通过 AOP 技术实现的事务管理,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
…声明式事务管理最大的优点是不需要通过编程的方式管理事务,因而不需要在业务逻辑代码中掺杂事务处理的代码,只需相关的事务规则声明便可以将事务规则应用到业务逻辑中。通常情况下,在开发中使用声明式事务处理不仅因为其简单,更主要的是因为这样使得纯业务代码不被污染,极大地方便了后期的代码维护。
…与编程式事务管理相比,声明式事务管理唯一不足的地方是最细粒度只能作用到方法级别,无法做到像编程式事务管理那样可以作用到代码块级别。但即便有这样的需求,也可以通过变通的方法进行解决,例如可以将需要进行事务处理的代码块独立为方法等。如果使用的是声明式事务,那么当你的业务方法不发生异常(或者发生异常 但该异常也被配置信息允许提交事务)时, Spring 就会让事务管理器提交事务,而发生异常(并且该异常不被你的配置信息所允许提交事务)时,则让事务管理器回滚事务。
…Spring的声明式事务管理可以通过两种方式实现,一是基于XML的方式,二是基于@Transactional注解的方式

@Transactional注解的方式

海创软件组-20200503-Spring数据库事务管理(一)_第1张图片
海创软件组-20200503-Spring数据库事务管理(一)_第2张图片
海创软件组-20200503-Spring数据库事务管理(一)_第3张图片
bean

public class Account {

	private Integer aid;
	private String aname; // 买方账户
	private double balance;//账户余额
	
	public Account() {
		super();
	}
	public Account(String aname, double balance) {
		super();
		this.aname = aname;
		this.balance = balance;
	}
	public Integer getAid() {
		return aid;
	}
	public void setAid(Integer aid) {
		this.aid = aid;
	}
	public String getAname() {
		return aname;
	}
	public void setAname(String aname) {
		this.aname = aname;
	}
	public double getBalance() {
		return balance;
	}
	public void setBalance(double balance) {
		this.balance = balance;
	}
	@Override
	public String toString() {
		return "Account [aid=" + aid + ", aname=" + aname + ", balance=" + balance + "]";
	}
	
	
}
public class Stock {

	private Integer sid;
	private String sname;//所买的物品的名称
	private int count;//所买物品的数量
	public Stock() {
		super();
	}
	public Stock(String sname, int count) {
		super();
		this.sname = sname;
		this.count = count;
	}
	public Integer getSid() {
		return sid;
	}
	public void setSid(Integer sid) {
		this.sid = sid;
	}
	public String getSname() {
		return sname;
	}
	public void setSname(String sname) {
		this.sname = sname;
	}
	public int getCount() {
		return count;
	}
	public void setCount(int count) {
		this.count = count;
	}
	@Override
	public String toString() {
		return "Stock [sid=" + sid + ", sname=" + sname + ", count=" + count + "]";
	}
	
}

service层
使用了@Transactional注解

public class BuyStockServiceImpl implements IBuyStockService {

	private IAccountDao adao;
	private IStockDao sdao;
	
	public void setAdao(IAccountDao adao) {
		this.adao = adao;
	}

	public void setSdao(IStockDao sdao) {
		this.sdao = sdao;
	}

	@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
	@Override
	public void openAccount(String aname, double money) {
  
		adao.insertAccount(aname,money);
	}

	@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
	@Override
	public void openStock(String sname, int amount) {
		sdao.insertStock(sname,amount);

	}
	@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,rollbackFor=BuyStockException.class)
	@Override
	public void buyStock(String aname, double money, String sname, int amount) throws BuyStockException{
		boolean isBuy =true;
		adao.updateAccount(aname,money,isBuy);
		if(1==1) {
			throw new BuyStockException("购买股票异常");
			}
		sdao.updateStock(sname,amount,isBuy);

	}

}

xml

<!-- 注册数据源:Spring内置连接池 -->
    <bean id="myDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"> </property>
    <property name="url" value="jdbc:mysql://localhost:3306/text?serverTimezone=UTC"/> 
    <property name="username" value="root"/>
    <property name="password" value="root"/>    
    </bean>

    
     <!-- 注册Dao -->
    <bean id="accountDao" class="com.spring.dao.AccountDaoImpl">
    <property name="dataSource" ref="myDataSource"> </property>
    </bean>
    
       <!-- 注册Dao -->
    <bean id="stockDao" class="com.spring.dao.StockDaoImpl">
    <property name="dataSource" ref="myDataSource"> </property>
    </bean>
    
   <!-- 注册Service -->
    <bean id="buyStockService" class="com.spring.service.BuyStockServiceImpl">
    <property name="adao" ref="accountDao"> </property>   
    <property name="sdao" ref="stockDao"> </property>
      </bean>
   
   <!-- 注册事务管理器 -->
   <bean id="myTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <property name="dataSource" ref="myDataSource"></property>
   
  </bean>
   <!-- 使用声明式事务需要配置注解驱动,只需要加入如下的即可:-->
   <tx:annotation-driven transaction-manager="myTransactionManager"/>

测试类:

public class Text {
	

	private IBuyGameService service;
	@Before
	public void before() {

		ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
	    service=(IBuyGameService)ctx.getBean("buyStockService");

	
	}
	@Test
	public void test01() {
		service.openBuy(" 张三", 1000);
		service.openSell("游戏币", 0);
	}
	
	@Test
	public void test02() throws BuyGameException  {
		service.buyGame("张三", 200, "游戏币", 10);
	}
	
}

海创软件组-20200503-Spring数据库事务管理(一)_第4张图片
海创软件组-20200503-Spring数据库事务管理(一)_第5张图片

使用xml方式进行事务管理器的配置
<!-- 注册事务管理器 -->
   <bean id="myTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <property name="dataSource" ref="myDataSource"></property>
   
  </bean>
   <!-- 生成事务代理对象 -->
    <bean id="serviceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
     <property name="transactionManager" ref="myTransactionManager"></property>
     <property name="target" ref="buyStockService"></property>
     <property name="transactionAttributes">
       <props>
        <!-- key代表的是业务方法的正则式匹配,而其内容可以配置各类事务定义参数-->
           <prop key="open*">ISOLATION_DEFAULT,PROPAGATION_REQUIRED</prop>
           <!---)异常:表示发生指定异常后回滚,这时的异常通常时受查异常
                (+)异常:表示发生指定异常后提交,这时的异常通常是运行时异常 -->
          <prop key="buyStock">ISOLATION_DEFAULT,PROPAGATION_REQUIRED,-BuyStockException</prop>     
       </props>   
     </property>
   </bean>
事务定义器

海创软件组-20200503-Spring数据库事务管理(一)_第6张图片

声明式事务的约定编程

…@Transaction注解可以使用在方法或者类上面,在 Spring IoC 容器初始化时, Spring 会读入这个注解或者XML配置的事务信息,并且保存到 个事务定义类里面( TransactionDefinition 接口的
子类),以备将来使用。当运行时会让 Spring 拦截注解标注的某一个方法或者类的所有方法。海创软件组-20200503-Spring数据库事务管理(一)_第7张图片

数据库事务 ACID 特性

…数据库事务正确执行的4个基础要素是原子性(Atomicity )、一致性( Consistency )、隔离性 Clso lation )和持久性( Durabi lity)。
①原子性: 整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没被执行过一样。
②一致性:指一个事务可以改变封装状态(除非它是一个只读的〉。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。
③隔离性: 它是指两个事务之间的隔离程度。
④持久性: 在事务完成以后,该事务对数据库所做的更改便持久保存在数据库之中,并不会被回滚。
…这里的原子性、一致性和持久性都比较好理解,而隔离性就不一样了,它涉及了多个事务并发的状态。首先多个事务并发会产生数据库丢失更新的问题,其次隔离性又分为多个层级。

丢失更新
第一类丢失更新:A事务撤销时,把已经提交的B事务的更新数据覆盖了。

假设一个场景,一个账户存在互联网消费和刷卡消费两种形式,而一对夫妻共用这个账户。老公喜欢刷卡消费,老婆喜欢互联网消费。那么可能会产生如表所示的场景:
海创软件组-20200503-Spring数据库事务管理(一)_第8张图片

整个过程中只有老公消费了1000元,而在最后的 T6 时刻,老婆回滚事务,却恢复了原来的初始值余额10000元,这显然不符合事实。这样的两个事务并发, 一个回滚、 一个提交成功导致不一致,我们称为第一类丢失更新。

第二类丢失更新:它和不可重复读本质上是同一类并发问题,通常将它看成不可重复读的特例。当两个或多个事务查询相同的记录,然后各自基于查询的结果更新记录时会造成第二类丢失更新问题。每个事务不知道其它事务的存在,最后一个事务对记录所做的更改将覆盖其它事务之前对该记录所做的更改。

海创软件组-20200503-Spring数据库事务管理(一)_第9张图片

… 整个过程中存在两笔交易,一笔是老公的请客吃饭, 另一笔是老婆 的网购,但是两者都提交了事务,由在不同的事务中,无法探知其他事务的操作,导致两 者提交后,余额都为9000元,而实际正确的应为8000 元,这就是第二类丢失更新。

隔离级别

第一种现象:脏读

…脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。脏读又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的。

第二种现象:不可重复读现象

…在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据并修改数据。那么,在第一个事务的两次读数据之间。由于另一个事务的修改,那么第一个事务两次读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复。就比如两个并发的事务,事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。

第三种现象:幻读是指当事务不是独立执行时发生的一种现象。

海创软件组-20200503-Spring数据库事务管理(一)_第10张图片

对于不合理使用隔离级别类型可能会造成的现象
1、READ_UNCOMMITED(读未提交数据):

如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。避免了更新丢失,却可能出现脏读、不可重复读、幻读的情况。

2、READ_COMMITED(读写提交数据):

读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。该隔离级别避免了脏读,但是却可能出现不可重复读、幻读的情况。

3、REPEATED_COMMITTED(可重复读):

读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。避免了不可重复读取和脏读,但是有时可能出现幻读。这中隔离级别可以通过“共享读锁”和“排他写锁”实现。

4、SERIALIZABLE(串行化):

提供严格的事务隔离。它要求事务依照顺序进行操作。
海创软件组-20200503-Spring数据库事务管理(一)_第11张图片

你可能感兴趣的:(海创软件组)