• Spring事务管理


    个人博客网:https://wushaopei.github.io/    (你想要这里多有)

    笔记导读:

    1. Spring事务管理的一组API
    2. Spring的编程式事务管理
    3. Spring的声明式事务管理

    一、事务的概念

    1、什么是事务?

    事务指的是逻辑上 一组操作,这组操作要么全部成功,要么全部失败。

    例子:那么我们在这里以一个银行转账的案例来分析,

    那么我们假设有两个人,一个是张三,一个是李四,那么张三账户里有2千元,李四账户也有2千元,那现在张三要给李四进行转账1千元的操作,那么我们就会修改张三的账户,给张三的账户扣除掉1千元,然后我们要修改李四的账户,给李四的账户加1千元,那这样的话,我们就完成了一个转账的操作。

                      

    转账可能出现的异常问题:

           但这组操作呢,它不应该出现的情况就是张三转了1千元之后,比如说突然间断电了,或者出现了一些其他的特殊情况,那么这样的话,张三的钱转出去了,而李四没收到,那这种情况是不应该出现的,所以说在这种情况里边,我们的一组操作我们可以用一组事务来进行管理,那么这组操作一旦加入到了事务的管理操作里边了,那么它们就必须一起成功,或者一起失败,那么一起 成功的情况是什么呢

                     

    就是张三把钱转出去了,李四也收到钱了,那如果是一起失败呢,那一起失败指的是张三的钱也没转出去,李四也没收到钱,这种情况不允许出现张三钱转了,李四没收到的情况。所以说这是事务的概念。

                  

    2、事务的特性:

     事务的四个特性: 原子性、一致性、隔离性、持久性

    ①原子性:指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。那么我们都知道,物理中原子是最小的单位 ,那么它强调的是我们这一组单位是不能进行分割的,不能拿出来单独去运行的,因为单独运行的话,那都有可能会导致错误或者而失败的产生,那么我们要把这一组呢要放在一个事务里边,那么它们就能一起成功或者一起失败了。

    ②一致性:我们事务执行的前后,数据的完整性,要保持一致。

    例:也就是说我们在事务执行之前,张三里边有2千元,李四账户里也有2千元,那么总共是4千元,那当我们转账完成之后,那张三的账户里是1千元,而李四的账户里是3千元,那总共的金额也是4千元,所以说在执行的前后,我们的这种完整性,是一致的。它不能出现张三的钱被扣掉,李四没收到这种情况。

    ③隔离性:指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离;

    例:隔离性强调的是,多个用户并发访问数据库的时候,那么一个用户的事务的执行过程当中,不应该受到其他事务的干扰,比如说我们有两个事务在同时的操作数据库,那你这边比如说正在修改张三的记录,而又有一个记录进来修改张三这条记录,那最后修改完事之后呢,会导致你这个记录会被重复的修改,或者是一开始的事务改完的记录被第二个事务给覆盖掉了。那么我们的事务在执行的过程当中呢,尽量不要让它收到其他事务的干扰,那这怎么做到呢?那数据库里都有一个事务的隔离级别,我们可以通过设置隔离级别呢,来解决这种问题。

                 

    ④持久性:指一个事务一旦被提交,它对数据库中数据的改变是永久性的,及时数据库发生故障也不应该对其有任何影响。

    如果你的事务没有提交,那你在执行了一个语句之后呢,这个数据还没有被真正的修改到数据库,或者是进入到数据库,那么只有你的事务提交了之后,这条记录才会被真正的修改或者进入到我们的数据库当中,那这个就是事务的持久性。

    二、事务的API介绍

    1、事务的接口介绍

    Spring中的事务管理: Spring提供了一组接口进行事务的管理。

    Spring提供事务管理的3个接口:

    【1】PlatformTransactionManager:事务管理器,用来管理事务的接口,定义了事务的提交、回滚等方法。            

              

    【2】TransactionDefinition:事务定义信息(隔离级别、传播行为、是否超时、是否只读)

                    

    【3】TransactionStatus:事务具体运行状态(事务是否提交,事务是否有保存点,事务是否是新事物等状态)。

                    

    Spring事务管理时,这三个接口是有联系的,Spring首先会根据事务定义信息TransactionDefinition获取信息,然后由事务管理器PlatformTransactionManager进行管理,在事务管理过程中,会产生一个事务的状态,这个状态就保存在事务具体运行状态TransactionStatus中了。

    2、PlatformTransactionManager接口介绍:

    通过Spring的API可以知道该接口有许多实现类例如:DataSourceTransactionManager、HibernateTransactionManager等。Spring会为不同的持久化框架提供了不同PlatformTransactionManager接口实现。

    比如当我们使用SpringJDBC或者iBatis进行持久化数据时使用DataSourceTransactionManager。

    通常我们使用的是DataSourceTransactionManager和HibernateTransactionManager。

    3、TransactionDefinition定义事务隔离级别

    TransactionDefinition接口:通过Spring的API可以知道该接口提供了一组常量。

    如下图以ISOLATION开头的五个隔离级别。

    如下图以PROPAGATION_MANDATORY开头的7个传播行为。

    如下图以TIMEOUT开头的超时信息

    该接口还提供了一些方法,例如:获得隔离级别、获得超时信息、获得是否只是只读的等。

    如果不考虑隔离性,就会引发安全问题:脏读、不可重复读、以及虚读或者叫做幻读。

    脏读:一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。

    不可重复读:同一事务中,多次读取同一数据返回的结果有所不同(读取到另一个事务已经提交的更新的数据)。

    幻读:一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录。

    正常情况下,数据库提供了四种隔离级别(解决安全问题):

    READ_UNCOMMITED:安全级别最低,如果设置为该级别,就可能会发生脏读、不可重复读、幻读等。

    READ_COMMITED:如果设置该级别,可以避免脏读的发生,但是可能会发生不可重复读和幻读。

    REPEATABLE_READ:如果设置该级别,可以避免脏读和不可重复读,但是可能会发生幻读。

    SERIALIZABLE:事务是串行的,不会发生并发访问这种情况

    Spring提供了DEFAULT,它代表使用数据库默认的隔离级别(例如:Mysql默认采用REPEATABLE_READ隔离级别,Oracle默认采用READ_COMMITTED隔离级别)。

    4、TransactionDefinition定义事务传播行为

    服务器端分为三层:web层,业务层和持久层。

    事务加在业务层。

    事务的传播行为:解决业务层方法之间相互调用的问题(一个service层里的方法调用另一个service里中的方法,这两个service中又分属于两个不同的事务,传播行为就是为了解决方法调用时事务的传递)。

    事务的传播行为有7种,可以为3类:

    第一类为前三个,重点掌握第一个(在相同事务里):支持当前事务(Service中bbb()调用Service中aaa()方法时,如果aaa()有事务,则使用该事务。如果没有事务,则使用bbb()当前事务,如果当前bbb()也没有事务,就会新创建一个事务)

    第二类为接下来三个,重点掌握第一个(在不同事务中):如果aaa()有事务存在,挂起当前事务,创建一个新的事务(aaa()和bbb()不在一个事务中)。

    第三类:如果当前事务存在,则嵌套事务执行(执行aaa()完后,会使用事务的保存点,在执行bbb()时如果发生异常,可以回滚到设置的保存点,也可以回滚到最初始的状态)

    4、TransactionStatus接口介绍

    TransactionStatus接口:提供了获取事务状态的方法(例如:hasSavepoint()事务是否有保存点,isCompleted()事务是否已经完成,isNewTransaction()是否是新的事务)。

    三、转账环境搭建

    Spring支持两种方式事务管理:

      编程式的事务管理:

    •       在实际应用中很少使用
    •       通过TransactionTemplate手动管理事务

    使用XML配置声明式事务:

    •       开发中推荐使用(代码侵入性最小)
    •       Spring的声明式事务是通过AOP实现的

    1、编程式的事务管理:手动在程序中编写代码实现事务管理,实际应用中很少使用,通过TransactionTemplate管理事务。

    2、声明式的事务管理:使用XML配置实现事务管理,推荐使用(代码侵入性最小),Spring的声明式事务管理是通过AOP实现的(没有代码之前开启事务,代码完成后提交事务)。

    搭建事务管理环境(转账环境)

    【a】创建表及插入记录

    #创建数据表account
    CREATE TABLE `account` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(20) NOT NULL,
      `money` double DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    INSERT INTO `account` VALUES ('1', 'aaa', '1000');
    INSERT INTO `account` VALUES ('2', 'bbb', '1000');
    INSERT INTO `account` VALUES ('3', 'ccc', '1000');

    【b】创建项目并引入jar包

    【c】引入log4j.properties、applicationContext.xml、jdbc.properties配置文件。

    log4j.properties:

    # Global logging configuration
    log4j.rootLogger=DEBUG, stdout
    # Console output...
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

    jdbc.properties

    username=root
    password=admin
    url=jdbc:mysql://localhost:3306/spring_transcation
    driver=com.mysql.jdbc.Driver
    

    applicationContext.xml

    <?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:tx="http://www.springframework.org/schema/tx"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:aop="http://www.springframework.org/schema/aop"
    	xmlns:p="http://www.springframework.org/schema/p"
    	xmlns:task="http://www.springframework.org/schema/task"
    	xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd
    		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-3.2.xsd
    		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
    		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
    		http://www.alibaba.com/schema/stat http://www.alibaba.com/schema/stat.xsd">
    
          
    
    </beans>

    【e】创建包结构,编写Dao及Service

    //转账案例的DAO层的接口
    public interface AccountDao {
    	/**
    	 * @param out  : 转出账号
    	 * @param money :转账金额
    	 * */
    	public void outMoney(String out,Double money);
    	/**
    	 * @param in   : 转入账号
    	 * @param money :转账金额
    	 * */
    	public void inMoney(String in,Double money);
    }
    //转账案例的DAO层的接口
    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
    	/**
    	 * @param out  : 转出账号
    	 * @param money :转账金额
    	 * */
    	@Override
    	public void outMoney(String out, Double money) {
    		// TODO Auto-generated method stub
    		String sql = "update account set money = money - ? where name = ?";
    		this.getJdbcTemplate().update(sql,money,out);
    	}
    	/**
    	 * @param in   : 转入账号
    	 * @param money :转账金额
    	 * */
    	@Override
    	public void inMoney(String in, Double money) {
    		// TODO Auto-generated method stub
    		String sql = "update account set money = money + ? where name = ?";
    		this.getJdbcTemplate().update(sql,money,in);
    	}
    
    }
    public interface AccountService {
    
    	/**
    	 * @param out  : 转出账号
    	 * @param in   : 转入账号
    	 * @param money :转账金额
    	 * */
    	public void transfer(String out,String in,Double money);
    }
    //转账案例的业务层实现类
    public class AccountServiceImpl implements AccountService {
    	
    	//注入转账的DAO的类
    	private AccountDao accountDao;
    	
    	public void setAccountDao(AccountDao accountDao) {
    		this.accountDao = accountDao;
    	}
    
    	/**
    	 * @param out  : 转出账号
    	 * @param in   : 转入账号
    	 * @param money :转账金额
    	 * */
    	@Override
    	public void transfer(String out, String in, Double money) {
    		// TODO Auto-generated method stub
    		accountDao.outMoney(out, money);
    		accountDao.inMoney(in, money);
    	}
    
    }
    

    【f】spring配置文件编写

        <!-- 引入外部的属性文件 -->
    	<context:property-placeholder location="classpath:jdbc.properties"/>
    	
    	<!-- 配置c3p0连接池 -->
       <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
       		<property name="driverClass" value="${jdbc.driver}"/>
       		<property name="jdbcUrl" value="${jdbc.url}"/>
       		<property name="user" value="${jdbc.username}"/>
       		<property name="password" value="${jdbc.password}"/>
       </bean>
    
    	<!-- 配置业务层类 -->
    	<bean id="accountService" class="com.webcode.cn.AccountServiceImpl">
    		<property name="accountDao" ref="accountDao"/>
    	</bean>
    	
    	<!-- 配置Dao的类 -->
    	<bean id="accountDao" class="com.webcode.cn.AccountDaoImpl">
    		<property name="dataSource" ref="dataSource"></property>
    	</bean>

    测试:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class SpringDemo1 {
    
    	//²âÊÔÒµÎñ²ãÀࣺ
    	@Resource(name="accountService")
    	private AccountService accountService;
    	
    	@Test
    	public void demo1() {
    		accountService.transfer("aaa", "bbb", 200d);		
    	}
    }
    

    失败操作:


    四、编程式事务管理:

    Spring为简化编写代码,提供了事务管理模板TransactionTemplate,TransactionTemplate依赖DataSourceTransactionManager(使用SpirngJDBC时事务的管理类,它是PlatformTransactionManager接口的实现类),DataSourceTransactionManager依赖DataSource(例如service需要使用事务,只需要在service中注入改模板即可,JDBC是通过Connection对象来对事务进行管理,而SpringJDBC配置事务时也需要这样的对象进行管理,这里是在配置事务管理器DataSourceTransactionManager的bean标签里来注入连接池实现的,如图)。
    1、编程式的事务管理,需要在application.xml 文件配置对应的 配置模板,

    主要配置的有

    ①通用且默认的配置事务管理器:  org.springframeword.jdbc.datasource.DataSourceTransactionManager

    ②专属编程式事务管理的由Spring 提供的类:

                      org.springframeword.ransaction.support.TransactionTemplate

    ③并在业务层的配置中添加注入事务管理的模板:

                    <property name ="transactionTemplate" ref="transactionTemplate">

    编程式事务管理就是在需要使用事务的地方手动编写代码,所以需要在Service层里注入该模板,这时Service调用Dao中的两个方法就处于同一个事务中了。


    2、编程式事务管理的业务实现层,注入TransactionTemplate 的bean实例并   执行 execute(new 通过模板的方法就可以实现业务回滚,execute(TransactionCallBack<T> transactionCallBack),TransactionCallBack实际是一个接口,可以手动创建一个类实现给接口,在传入该对象。也可以使用匿名内部类的形式,匿名内部类的方法里就可以进行事务的操作了,该方法里的参数是事务的状态对象,而且一个方法里的参数中匿名内部类想使用外部传入的参数,需要使用final修饰该参数。如下图


    五、声明式事务管理

    1、基于TransactionProxyFactoryBean的XML方式实现:

    【1】默认需要配置 org.springframeword.jdbc.datasource.DataSourceTransactionManager 外;

    还要配置具体的业务层的代理,即事务代理的主要项:

    org.springframeword.transaction.interceptor.TransactionProxyFactoryBean


    【2】本方法需要在业务接口上注入代理类,如图中,

    @Resource(name="accountServicceProxy")

    private AccountService accountService;


    【3】此处的accountService类没有被增强过;实际增强的类是配置业务层的的代理中的 “id= accountServiceProxy”类


    【4】此处业务层的代理 的配置 基于  TransactionProxyFactoryBean 类进行了增强操作,主要操作,通过配置目标对象,将事务管理器注入并配置响应的事务属性,在 <property><props></props></property>中配置实际需要增强的功能,即事务的传播行为;隔离级别只读,异常回滚处理等....

    注意: 业务功能增强是在本配置中实现,通过配置目标对象<property name="target" ref="accountService">将业务接口引入,并在当前配置中做增强;因此真正具有增强功能的 是  "accountServiceProxy" 这个类,在引入 业务接口时需要通过 @Resource 引入 "accountServiceProxy"


    2、基于AspectJ的XML方式实现:

    实际开发中,不经常使用第一种方式(TransactionProxyFactoryBean代理类),因为这种方式需要为每一个需要事务管理的Service配置一个TransactionProxyFactoryBean,开发和维护带来不便。

    【1】引入AspectJ的jar包和整合AspectJ的包。


    【2】配置事物管理器,并注入数据源dataSource。


    默认需要配置 org.springframeword.jdbc.datasource.DataSourceTransactionManager 外;

    还需要配置事务的通知(即事务的增强):

    【3】配置事物的通知(事物的增强,通过<tx:advice id=""  transaction-manager="事物管理器Id"></tx:advice>,这个标签中需要配置事物相关的一些属性<tx:attributes></tx:attributes>,该属性的作用就是哪些方法执行事务,<tx:method="方法名"></tx:method>如果多个方法可以用英文单词*,该标签中还定义了事物的传播行为、隔离级别、超时信息、只读等等属性)

    <tx:advice> </tx:advice>

    其中所涉及的配置有

    <tx:method name ="transfer" propagation = "REQUIRED">

    这是事务的传播机制;

    注意: 基于AspectJ的事务管理,其Service接口在被实现过程中便自动进行了代理,起到了增强作用,不需要再进行其它的操作使其增强;


    【4】使用aop:config配置切入点,使用aop:pointcut   的 id 作为切入点的 坐标;调用 aop:advisor 配置切面,引入事务的增强  -- "txAdvice",将其指向要生效的切入点 -- "pointcut1";当excution 中对应的方法被调用时,该切入点会被配置的事务进行增强


    该声明式事务管理就不需要再注入代理了,这种方式属于自动代理,自动代理一般是基于BeanPostProcessor这个类,也就是类生成过程中(Serivice)本身就是一个代理。

    【5】事务的通知中,可以增强的具体内容有以下几点:

    • 事务传播行为;
    • 事务隔离级别;
    • 只读;
    • 发生哪些异常回滚;
    • 发生哪些异常不回滚


    3、基于注解的方式

    加载测试环境:Spring提供的,@RunWith(SpringJUnit4ClassRunner.class),只有加载它了,才可以使用@Test注解

    加载配置文件:@ContextConfiguration(classpath:相对路径)。

    【1】配置事物管理器


    【2】开启注解事物

    <tx:annotatioon-drivern transaction-manager="transactionManager">

    【3】添加注解@Transactional(哪个类上需要事物管理就在哪个类上添加注解)

    在需要使用事务的业务实现类前添加 @Transactional 注解,便可引入到事务管理中,同时事务的传播行为、隔离级别等都存在于@Transactional 注解的属性中,根据需求声明赋值便可!


    该注解中也包含一些属性(传播行为、隔离级别、超时、异常),如果不写,都会按默认值来处理。

    Spring事务总结:

    Spring将事物管理分成了两类:

    【1】编程式事务管理:手动编写代码进行事物管理(很少使用)

    【2】声明式事务管理:

    基于TransactionProxyFactoryBean的方式(很少使用)

    ——需要为每个进行事务管理的类,配置一个代理类,后期维护和管理不方便。

    基于AspectJ的XML方式(经常使用)

    ——可以清晰的在XML配置文件中,查看到哪些类哪些方法应用事物管理,业务层上不需要添加任何东西。

    基于注解方式(经常使用)

    ——使用简单,需要为每一个需要事务管理的业务层上添加注解@Transactional。

  • 相关阅读:
    成长篇之代码灵异事件
    idea快捷键
    java环境配置常用链接
    MySQL分区
    English 动词篇
    仿stl+函数模板
    java 数组复制
    拓扑排序(Topological Sorting)
    2017蓝桥杯第十题(k倍区间)
    翻译NYOJ
  • 原文地址:https://www.cnblogs.com/wushaopei/p/11718302.html
Copyright © 2020-2023  润新知