spring 事物管理

1. 事务的特性 ACID

  • 原子性:事务包含的所有操作要么全部成功,要么全部失败回滚
  • 一致性:事务执行前和执行后都必须处于一致性状态,拿转账来说,A账户和B账户共有1000元,那么不论A和B如何转账,事务结束后,A账户钱+B账户钱=1000
  • 隔离性:多个并发事务之间互相隔离
  • 持久性:事务一旦提交成功,那么就必须持久化到数据库,即使数据库系统遇到问题,不能丢失提交后的数据

2.事务的隔离级别

  

•   ① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
•   ② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
•   ③ Read committed (读已提交):可避免脏读的发生。
•   ④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
mysql默认的隔离级别是
Repeatable read

 

3.出现的问题

  • 脏读: 读取一个未提交的事务中的数据
  • 不可重复读:一个事务中多次查询,返回的结果不一致,这是因为在查询间隔中,有另一个事务修改并提交了数据
  • 幻读:  幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

 不可重复读重点是修改,而幻读重点是新增或删除。

4.spring 事务管理的核心接口

 

 

           Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,如上图所示,Spring并不直接管理事务,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,也就是将事务管理的职责委托给Hibernate或者JDBC等持久化机制所提供的相关平台框架的事务来实现。

 

5.事务的传播行为

  • PROPAGATION_REQUIRED:没有事务创建一个事务,如果有事务,则使用该事务
  • PROPAGATION_SUPPORTS:如果有事务,则使用该事务;如果没有事务,则以非事务的方式运行
  • PROPAGATION_MANDATORY:如果有事务,则使用该事务;如果没有事务,则抛出异常
  • PROPAGATION_REQUIRES_NEW :如果有事务,则将当前事务挂起,创建一个新的事物;如果没有事务,则创建一个新的事务

说明:内层事务失败,会导致外层事务回滚;

外层事务失败,不会导致内层事务回滚;

如果外层事务捕获了内层事务的异常,内层事务回滚,外层事务正常提交

  • PROPAGATION_NOT_SUPPORTED :以非事务的方式运行,如果有事务,则将当前事务挂起
  • PROPAGATION_NEVER :以非事务的方式运行,如果有事务,则抛出异常
  • PROPAGATION_NESTED :如果有事务,则在嵌套事务内执行。如果当前没有事务,则新建一个事务

 

6.声明式事务配置(AOP):数据源,事务管理器,事务传播特性,切面

<?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:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    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
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">
        
        
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        
        <!-- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
            <property name="driverClass" value="oracle.jdbc.driver.OracleDriver"/>
            <property name="jdbcUrl" value="jdbc:oracle:thin:@192.168.210.61:1521:orcl"/>
            <property name="user" value="stlpd"/>
            <property name="password" value="stlpd"/> 
        </bean> -->
        
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
            <property name="driverClass" value="com.mysql.jdbc.Driver"/>
            <property name="jdbcUrl" value="jdbc:mysql://192.168.210.61:3306/test"/>
            <property name="user" value="root"/>
            <property name="password" value="hefeiOrcl"/> 
        </bean>
        
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">  
            <property name="dataSource" ref="dataSource"/>  
       </bean>
        
    <!-- spring 声明式事物配置 1.  aop 切面管理 -->
    <tx:advice id="txAdvice"  transaction-manager="transactionManager"> 
        <tx:attributes>
            <tx:method name="add*"     propagation="REQUIRES_NEW"/>
            <tx:method name="update*"  propagation="REQUIRED"/>
            <tx:method name="delete*"  propagation="REQUIRED"/>
            <tx:method name="get*"   propagation="REQUIRED" read-only="true" />
            <tx:method name="find*"  propagation="REQUIRED" read-only="true"/>
            <tx:method name="select*" propagation="REQUIRED" read-only="true"/>
            <tx:method name="never*" propagation="NEVER" />
            <tx:method name="mandatory*" propagation="MANDATORY" />
            <tx:method name="requires_new*" propagation="REQUIRES_NEW" />
            <tx:method name="notSupported*" propagation="NOT_SUPPORTED" />
            <tx:method name="nested*" propagation="NESTED" />
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>        
    <!-- 即应该在哪些类的哪些方法上面进行事务切入-->    
      <aop:config>
        <aop:pointcut id="transactionPointcut" expression="execution( * com.test.transaction.*.*(..))" />
        <aop:advisor pointcut-ref="transactionPointcut" advice-ref="txAdvice" />
      </aop:config>

    <bean id="userService" class="com.test.transaction.UserService">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
        <property name="bsService" ref="bsService"></property>
    </bean> 
        
    <bean id="bsService" class="com.test.transaction.BsService" >
    <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
    
    
        
        
        
        
        
        </beans>

 

 

package com.test.transaction;

import org.springframework.jdbc.core.support.JdbcDaoSupport;

/**
 * 在同一个业务类里面,即使声明REQUIRES_NEW 也不会新启动一个事务,必须调用另一个类里面的方法
 * 
 * 
 * 声明式事务是通过aop切面函数来处理,切面函数是通过代理,即A方法调用B方法是通过this.B调用,而不是通过切面函数
 * ,如果通过applicationContext.getBean(A.class).B()  应该是可以的
 * 
 * @author: chenzb
 * @version: v1.0
 * @description: 
 * @date:2020年3月5日
 */
public class UserService extends JdbcDaoSupport{
	
	private BsService bsService;
	/**
	 * read-only
	 * 
	 * @param id
	 * @return
	 */
	public void getUserById(){
		String sql = "insert into ccc(id,name) values('8342','ccsz')";
		this.getJdbcTemplate().execute(sql);
	}
	
	/**
	 * 事务的原子性:  事务包含的全部操作,要么全部成功,要么全部失败,回滚所有的操作
	 * 
	 * 
	 */
	public void update(){
		String sql = "update ccc set name = 'wwwwwww' where id = '123' ";
		this.getJdbcTemplate().execute(sql);
		int i = 1/0;
		
	}
	
	public void start_required(){
		String sql = "update ccc set name = 'hhhh' where id = '123' ";
		this.getJdbcTemplate().execute(sql);
		try {
			int i = 1/0;
		} catch (Exception e) {
		}
	}
	
	
	public void start_never(){
		this.bsService.never();
	}
	
	public void start_mandatory(){
		this.bsService.mandatory();
	}
	
	public void start_supports(){
		this.bsService.supports();
	}
	
	public void start_not_supported(){
		this.bsService.notSupported();
	}

	public void start_requires_new(){
		String sql = "update ccc set name = 'sdfghppppp' where id = '123' ";
		this.getJdbcTemplate().execute(sql);
		try {
			this.bsService.requires_new();
		} catch (Exception e) {
		}
		
//		int i = 1/0;
	}
	
	
	public void start_nested(){
		String sql = "update ccc set name = 'GGGG111222' where id = '123' ";
		this.getJdbcTemplate().execute(sql);
		try {
			this.bsService.nested();
		} catch (Exception e) {
		}
//		this.bsService.nested();
//		int i = 1/0;
	}
	
	
	public BsService getBsService() {
		return bsService;
	}

	public void setBsService(BsService bsService) {
		this.bsService = bsService;
	}
	
}

  

package com.test.transaction;

import org.springframework.jdbc.core.support.JdbcDaoSupport;

public class BsService extends JdbcDaoSupport{
    
    /**
     * 以非事务的方式运行,如果当前存在事务,则抛出异常
     * 非事务:操作不具有原子性,即后面出现异常后,前面的操作不会回滚
     * 
     */
    public void never(){
        String sql = "update ccc set name = 'www.baidu.com1111' where id = '123'";
        this.getJdbcTemplate().execute(sql);
        int i = 1/0;
        
    }
    
    /**
     * 支持当前事务,如果当前没有事务,则抛出异常
     * 
     * 
     */
    public void mandatory(){
        
        
        
        
    }
    
    /**
     * 
     * 支持当前事务,如果当前没有事务,则以非事务的方式运行
     * 
     */
    public void supports(){
        
        
        
    }
    
    /**
     * 新建事务,如果当前存在事务,则将当前事务挂起
     * 外层事务失败,不会导致内层事务回滚
     * 内层事务失败,会导致外层事务回滚
     * 如果外层事务捕获了内层事务抛出的异常,在内层事务回滚,外层事务正常提交
     * 
     */
    public void requires_new(){
//        String sql = "update ccc set name = 'sdfgh' where id = '123' ";
//        this.getJdbcTemplate().execute(sql);
        int i = 1/0;
        
    }
    
    /**
     *以非事务的方式运行,如果当前存在事务,则将当前事务挂起 
     * 
     *
     */
    public void notSupported() {
        String sql = "update ccc set name = 'kkkooo111' where id = '123' ";
        this.getJdbcTemplate().execute(sql);
        int i = 1/0;
    }
    
    /**
     * 外层事务失败,会导致内层事务回滚
     * 内层事务失败,不会导致外层事务回滚
     * 
     */
    public void nested() {
//        String sql = "update ccc set name = 'sdfgh' where id = '123' ";
//        this.getJdbcTemplate().execute(sql);
        int i = 1/0;
    }
    
    
    
    
    
}

 

package com.test.transaction;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    
    
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("/resource/application-tx1.xml");
        UserService us = (UserService)context.getBean("userService");
        //us.getUserById();
        BsService bs = (BsService)context.getBean("bsService");
    //    bs.neverDo();
    //    us.start_required();
    //    us.start_never();
    //    us.update();
    //    bs.mandatory();
    //    us.start_mandatory();
    //    bs.supports();
    //    us.start_supports();
    //    bs.requires_new();
    //    us.start_requires_new();
    //    us.start_not_supported();
        us.start_nested();
        
    }
}

 

7.spring 事务在同一个类中互相调用不生效的原因分析

spring采用动态代理(AOP)实现对bean的管理和切片,它为我们的每个class生成一个代理对象。只有在代理对象之间进行调用时,可以触发切面逻辑。

而在同一个class中,方法B调用方法A,调用的是原对象的方法,而不通过代理对象。所以Spring无法切到这次调用,也就无法通过注解保证事务性了。

 也就是说,在同一个类中的方法调用,则不会被方法拦截器拦截到,因此事务不会起作用。

 

 

参考文档:https://www.cnblogs.com/ynyhl/p/12066530.html

https://blog.csdn.net/qq_42914528/article/details/83743726

 

posted @ 2020-03-05 17:18  兵哥无敌  阅读(210)  评论(0编辑  收藏  举报