zoukankan      html  css  js  c++  java
  • 2017.4.18 慕课网-spring事务管理总结

    1.课程目标

    • 事务回顾

    • spring中的事务管理的api

    • spring中编程式事务管理

    • spring中声明式事务管理

     

    2.事务回顾

    2.1 事务的概念

    事务是指逻辑上的一组操作,要么全成功,要么全失败。以银行转账为例,详细说明,略。

     

    2.2 事务的特性

    原子性:事务是一个不可分割的工作单位。事务中的操作要么一起成功,要么一起失败。

    一致性:事务前后数据的完整性必须保持一致。

    隔离性:多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务干扰。可以通过设置隔离级别来实现。

    持久性:一个事务一旦被提交,那么数据库中数据的改变是永久性的。即使数据库发生故障也不该对其有影响。

     

    3.spring中事务管理的api

    • PlatformTransactionManager:包含了事务的提交、回滚等管理。

    • TransactionDefinition:包含了事务的隔离级别、传播、超时信息等。

    • TransactionStatus:包含了事务是否有保存点、是不是新事务等状态信息。

     

    3.1 PlatformTransactionManager

    是interface,有一些具体的实现类,比如datasourceTransactionManager。

    spring为不同的持久化框架提供了不同的PlatformTransactionManager。以最常用的举例。

    说明 事务
    使用spring jdbc或ibatis、mybatis进行持久化数据时使用 org.springframework.jdbc.datasource.DataSourceTransactionManager
    使用Hibernate进行持久化数据时使用 org.springframework.orm.hibernate3.HibernateTransactionManager

     

    3.2 TransactionDefinition

    (1) 隔离级别相关的常量,以ISOLATION开头。

    不考虑隔离性会造成的问题:脏读、幻读、不可重复读。

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

    幻读:一个事务读取了几行数据后,另一个事务插入了一些记录。在后来的查询中,第一个事务就会发现一些原来不存在的记录。

    不可重复读:在同一个事务中,多次读取同一个数据返回的结果不同。

     

    事务的隔离级别:

    隔离级别 含义
    DEFAULT 使用后端数据库默认的隔离级别(spring中得选择项)
    READ_UNCOMMITED 允许读取还没提交的已改变了的数据,可能导致脏读、幻读、不可重复读。
    READ_COMMITED 允许在并发事务已经提交后读取,可防止脏读。但不可防止幻读和不可重复读。
    REPEATABLE_READ 对相同字段的多次读取,结果是一致的,除非数据被事务本身改变。可防止脏读、不可重复读,但不可防止幻读。
    SERIALIZABLE 完全服从ACID的隔离级别,可防止脏读、幻读、不可重复读。这在所有隔离级别中是最慢的,因为它是通过完全锁定在事务中涉及的数据表来完成的。是不可能出现并发访问的情况。

     

    默认隔离级别举例:

    mysql:REPEATABLE_READ

    oracle:READ_COMMITED

     

    (2) 传播行为相关的常量,以PROPAGATION开头。

    事务的传播行为是什么?

    用来解决业务层方法之间的相互调用问题。

     

    web层-->业务层-->持久层

    假设持久层有两个dao,dao1,dao2。业务层有两个service,service1和service2。

     1 Dao1{
     2 xxx(){}
     3 }
     4 
     5 Dao2{
     6 yyy(){}
     7 }
     8 
     9 Service1{
    10     aaa(){
    11         dao1.xxx();
    12         dao2.yyy();
    13     }
    14 }
    15  
    16 Service2{
    17     bbb();
    18 }        
    View Code

    事务是加在业务层的。若此时业务复杂,需要调用service1和service2的方法来共同完成业务功能。而此时service1和service2都有自己的事务管理,那么事务到底使用service1里的还是service2里的呢?此时就用到了业务的传播行为。

    事务的传播行为 说明
    PROPAGATION_REQUIRED 支持当前事务,如果不存在,就新建一个。
    PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务。
    PROPAGTION_MANDATORY 支持当前事务,如果不存在,就抛出异常。
    PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建 一个新的事务。
    PROPAGATION_NOT_SUPPORTED 如果有事务存在,挂起当前事务。以非事务的方式进行。
    PROPAGATION_NEVER 如果有事务存在,抛出异常。以非事务的方式进行。
    PROPAGATION_NESTED 如果有事务存在,则嵌套事务执行。

     

    重点记忆这三种:PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED。

    PROPAGATION_REQUIRED:保证service1的aaa()和service2的bbb()在同一个事务里。

    PROPAGATION_REQUIRES_NEW:保证service1的aaa()和service2的bbb()不在同一个事务里。

    PROPAGATION_NESTED:service1的aaa()执行完后可以设置一个保存点,如果service2的bbb()执行时出了问题,可以选择回滚到保存点,也可以选择回滚到最初点。是一种嵌套事务。

     

    3.3 TransactionStatus

    提供一组方法来获取、设置事务的一些状态信息。比如isCompleted(),isRollBackOnly()。

     

    4.为后面事务管理建设场景--转账示例

    4.1创建表

    数据库名:spring_transaction

    表名:account

     

    4.2 创建项目

    eclipse-->new web project-->输入project name为spring_transaction

     

    项目结构:

     1 spring_transaction
     3     src
     5         cn.muke.spring.*
     7         log4j.properties
     9         applicationContext.xml
    11         jdbc.properties
    13     WebRoot
    15         META-INF
    17         WEB-INF
    19             lib
    21                ***.jar
    23             web.xml                      

    4.3 引入jar包

    数据库驱动:mysql-connector-java-5.0.8-bin.jar

    连接池:com.springsource.com.mchange.v2.c3p0.jar

    spring:com.springsource.org.apache.commons.logging-1.1.1.jar

    com.springsource.org.apache.log4j-1.2.15.jar

    spring-beans-3.2.0.RELEASE.jar

    spring-context-3.2.0.RELEASE.jar

    spring-core-3.2.0.RELEASE.jar

    spring-expression-3.2.0.RELEASE.jarr

     

    spring-jdbc-3.2.0.RELEASE.jar(数据库)

    spring-tx-3.2.0.RELEASE.jar(事务管理)

    spring-test-3.2.0.RELEASE.jar(单元测试)

     

    4.4 配置文件

    (1) log4j.properties

     

    (2) applicationContext.xml

    <beans>

    <!--引入外部属性文件-->

    <context:property-placeholder location:"classpath:jdbc.properties"/>

     

    <--配置c3p0连接池-->

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">

    <property name="driverClass" value="${jdbc.driverClass}"/>

    <property name="jdbcUrl" value="${jdbc.url}"/>

    <property name="user" value="${jdbc.username}"/>

    <property name="password" value="${jdbc.password}"/>

    </bean>

     

    <!--配置业务层类-->

    <bean id="AccountService" class="cn.muke.spring.demo1.AccountServiceImpl"/>

    <!--为业务层注入dao-->

    <property name="accountDao" ref="AccountDao"/>

    </bean>

     

    <!--配置DAO的类,方法1-->

    <bean id="AccountDao" class="cn.muke.spring.demo1.AccountDaoImpl">

    <!--为dao注入jdbc连接池,就能自动创建jdbcTemplate-->

    <property name="dataSource" ref="dataSource"/>

    </bean>

     

    <!--配置DAO的类,方法2-->

    <bean id="AccountDao" class="cn.muke.spring.demo1.AccountDaoImpl">

    <!--为dao注入jdbc模板-->

    <property name="jdbcTemplate" ref="jdbcTemplate"/>

    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">

    <property name="dataSource" ref="dataSource"/>

    </bean>

    </beans>

     

    (3) jdbc.properties

    jdbc.driverClass=com.mysql.jdbc.Driver

    jdbc.url=jdbc:mysql:///spring_transaction

    jdbc.username=root

    jdbc.password=123

     

    4.5 代码

    package cn.muke.spring.demo1;

     

    (1) AccountService 和 AccountServiceImpl

    public interface AccountService{

    //转出账户,转入账户,转账金额

    public void transfer(String out, String in, Double money);

    }

     

    public class AccountServiceImpl implements AccountService{

    private AccountDao accountDao;

    public void SetAccountDao()AccountDao accountDao{

    this.accountDao = accountDao;

    }

     

    //转出账户,转入账户,转账金额

    public void transfer(String out, String in, Double money){

    accountDao.outMoney(out,money);

    accountDao.outMoney(in,money);

    }

    }

     

    (2) AccountDao 和 AccountDaoImpl

    public interface AccountDao{

    public void outMoney(String out, Double money);

    public void InMoney(String in, Double money);

    }

     

    //jdbcDaoSupport提供了一个属性:jdbcTemplate,查看jdbcDaoSupport源代码可知,只要提供一个连接池,就能自动创建一个jdbcTemplate。

    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao{

    public void outMoney(String out, Double money){

    String sql = "update account set money = money - ? where name = ? ";

    this.getJdbcTemplate().update(sql,money,out);

    }

     

    public void InMoney(String in, Double money){

    String sql = "update account set money = money + ? where name = ? ";

    this.getJdbcTemplate().update(sql,money,in);

    }

    }

     

    (3 ) 测试类SpringDemo

    import org.junit.Test;

     

    @RunWith(SpringJunit4ClassRunner.class)

    @ContextConfiguration("classpath:applcationContext.xml")

    public class SpringDemo{

     

    //被测试的业务层类

    @Resource("name=accountService")

    private AccountService accountService;

     

    @Test

    public void demo1(){

    accountService.transfer("aaa","bbb",200d);

    }

    }

     

    选中方法demo1(),右键,选择run as junit4 test。

     

    4.6 事务管理

    前面的代码没有进行事务管理,如果在这里加一个会产生异常的代码。那么运行后,结果是:aaa从800变成了600,而bbb却仍然是1000,没有增加。

     

    public class AccountServiceImpl implements AccountService{

    //...

     

    //转出账户,转入账户,转账金额

    public void transfer(String out, String in, Double money){

    accountDao.outMoney(out,money);

    int i = 1/0 ; // 产生异常,后面的outMoney不会执行。

    accountDao.outMoney(in,money);

    }

    }

     

    5.编程式事务管理

    (1)配置文件applicationContext.xml

    <!--配置业务层类-->

    <bean id="AccountService" class="cn.muke.spring.demo1.AccountServiceImpl"/>

    <!--为业务层注入dao-->

    <property name="accountDao" ref="AccountDao"/>

    <!--为业务层配置事务管理的模板-->

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

    </bean>

     

    <!--配置事务管理的模板,是spring为了简化事务管理的代码而提供的类,需要提供事务管理器属性-->

    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">

    <property name="transactionManager" ref="transactionManager"/>

    </bean>

     

    <!--配置事务管理器,需要提供连接池属性-->

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

    <property name="dataSource" ref="dataSource"/>

    </bean>

     

    (2)代码

    package cn.muke.spring.demo1;

     

    public class AccountServiceImpl implements AccountService{

    //...

     

    private TransactionTemplate transactionTemplate;

    public void SetTransactionTemplate(TransactionTemplate transactionTemplate){

    this.transactionTemplate = transactionTemplate;

    }

     

    //这里要加上final,是因为在匿名内部类里使用了方法中的参数。所以方法中的参数要加上final,否则会报错。

    public void transfer(final String out, final String in, final Double money){

    transactionTemplate.execute(new TransactionCallbackWithoutResult(){// 匿名内部类

    @Override

    protected void doInTransactionWithoutResult( TransactionStatus transactionStatus ){

    accountDao.outMoney(out,money);

    int i = 1/0;

    accountDao.outMoney(in,money);

    }

    });

    }

    }

     

    (3)运行测试类

    执行完,aaa还是800,没有变成600。因为事务回滚了。

     

    5.声明式事务管理方式1--基于TransactionProxyFactoryBean

    方式1实际中不适合使用,因为要为每一个类配置一个TransactionProxyFactoryBean,开发和维护都很麻烦。

    (1)jar包

    声明式事务管理是基于Spring的AOP思想,因此需要先引入AOP相关的jar包:

    spring-aop-3.2.0.RELEASE.jar

    com.springframework.org.aopalliance-1.0.0.jar

     

    (2)配置文件applicationContext.xml

    <!--配置业务层类-->

    <bean id="AccountService" class="cn.muke.spring.demo1.AccountServiceImpl"/>

    <!--为业务层注入dao-->

    <property name="accountDao" ref="AccountDao"/>

    </bean>

     

    <!--配置事务管理器,需要提供连接池属性-->

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

    <property name="dataSource" ref="dataSource"/>

    </bean>

     

    <!--配置业务层的代理-->

    <bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

    <!--配置目标对象-->

    <property name="target" ref="accountService"/>

    <!--配置事务管理器-->

    <property name="transactionManager" ref="transactionManager"/>

    <!--注入事务属性-->

    <property name="transactionAttributes" value="transactionManager">

    <props>

    <!--

    key表示方法名,支持*的写法,比如tran*,*。

    中间的值:

    * PROPAGATION : 事务的传播行为

    * ISOLATION:事务的隔离级别

    * readOnly:只读,数据库不可以进行插入、修改、删除等操作。

    * -Exception:发生哪些异常时进行事务回滚,比如 -java.lang.ArithmeticException。

    * +Exception:发生哪些异常时不进行事务回滚

    -->

    <prop key="transfer"> PROPAGATION_REQUIRED <prop>

    </props>

    </property>

    </bean>

     

    ps: ref 和 value 的区别:ref里写的是类,value里写的是值。

     

    (3)代码

    package cn.muke.spring.demo1;

     

    import org.junit.Test;

     

    @RunWith(SpringJunit4ClassRunner.class)

    @ContextConfiguration("classpath:applcationContext.xml")

    public class SpringDemo1{

     

    //被测试的业务层代理类

    @Resource("name=accountServiceProxy")

    private AccountService accountService;

     

    @Test

    public void demo1(){

    accountService.transfer("aaa","bbb",200d);

    }

    }

     

    (4)运行测试类

    执行完后,报异常。查看数据库信息,aaa还是800,没有变成600。因为事务回滚了。

     

     

    5.声明式事务管理方式2--基于AspectJ的xml方式

    这种配置方式,不需要配置AccountServiceImpl的代理类。因为实现了自动代理,类AccountServiceimpl在生成过程中,这个类本身就已经是代理对象了。

    在实际的使用中,也是比较多的。

     

    (1)jar包

    aspectJ是一个开源的、第三方的aop框架。需要引入aspectJ的相关jar包:

    spring-aspects-3.2.0.RELEASE.jar

    com.springframesource.org.aspectj.weaver-1.6.8.RELEASE.jar

     

    (2)配置文件applicationContext.xml

    注意命名空间的引入。

    <beans ...

    xmlns:aop="http://www.springframework.org/schema/aop"

    xmlns:aop="http://www.springframework.org/schema/tx"

     

    xsi:schemaLocation="...

    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"

    ...

    /beans>

     

    <!--配置业务层类-->

    <bean id="AccountService" class="cn.muke.spring.demo1.AccountServiceImpl"/>

    <!--为业务层注入dao-->

    <property name="accountDao" ref="AccountDao"/>

    </bean>

     

    <!--配置事务管理器,需要提供连接池属性-->

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

    <property name="dataSource" ref="dataSource"/>

    </bean>

     

    <!--配置事务的通知(事务的增强)-->

    <tx:addvice id="txAdvice" transaction-manager="transactionManager">

    <tx:attributes>

    <!--

    name表示方法名,支持*的写法,比如tran*,*。

    属性key:

    * propagation : 事务的传播行为。

    * isolation:事务的隔离级别。

    * read-only:false/true,只读,数据库不可以进行插入、修改、删除等操作。

    * rollback-for:发生哪些异常时进行事务回滚。

    * no-rollback-for:发生哪些异常时不进行事务回滚。

    * time-out:过期信息。

    -->

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

    </tx:attributes>

    </tx:addvice>

     

    <!-- 配置aop的切面-->

    <aop:config>

    <!--配置切入点,+表示AccountService的子类,*表示任何方法,..表示任何参数-->

    <aop:pointcut expression="execution(* cn.muke.spring.demo.AccountService+.*(..))" id="pointcut1>"/>

    <!--配置切面,下面这段配置表示在切入点pointcut1上,应用txAdvice增强。切入点的位置是AccountService所有子类的所有方法。-->

    <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>

    </aop:config>

     

    (3)代码

    无任何修改。

     

    (4)运行测试类

    执行完后,报异常。查看数据库信息,aaa还是800,没有变成600。因为事务回滚了。

     

    6.声明式事务管理方式3--基于注解方式

    (1)jar包

    ???

     

    (2)配置文件applicationContext.xml

    同样注意命名空间的声明。

    <beans ...

    xmlns:aop="http://www.springframework.org/schema/aop"

    xmlns:aop="http://www.springframework.org/schema/tx"

     

    xsi:schemaLocation="...

    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"

    ...

    /beans>

     

    <!--配置业务层类-->

    <bean id="AccountService" class="cn.muke.spring.demo1.AccountServiceImpl"/>

    <!--为业务层注入dao-->

    <property name="accountDao" ref="AccountDao"/>

    </bean>

     

    <!--配置事务管理器,需要提供连接池属性-->

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

    <property name="dataSource" ref="dataSource"/>

    </bean>

     

    <!--开启注解事务,开启之后就可以使用@Transactional注解了-->

    <!--写成<tx:annotation-driven/>时默认加载名为transactionManager的事务管理器-->

    <tx:annotation-driven transaction-mananger="transactionManager"/>

     

    (3)代码

    @Transactional

    public class AccountServiceImpl implements AccountService{

    //...

    public void transfer( String out, String in, Double money){

    accountDao.outMoney(out,money);

    int i = 1/0;

    accountDao.outMoney(in,money);

    }

    }

     

    @Transactional注解也有属性配置:不写的时候按默认值配置。

    * propagation : 事务的传播行为。

    * isolation:事务的隔离级别。

    * readOnly:false/true,只读,数据库不可以进行插入、修改、删除等操作。

    * rollbackFor:发生哪些异常时进行事务回滚。

    * noRollbackFor:发生哪些异常时不进行事务回滚。

    * time-out:过期信息。

    @Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.DEFAULT, readOnly=false,...)

     

    (4)运行测试类

    执行完后,报异常。查看数据库信息,aaa还是800,没有变成600。因为事务回滚了。

     

    7.总结

    spring事务管理分为:编程式事务管理(很少使用)、声明式事务管理。

    不论是声明式还是编程式,transactionManager都是必须配置的。

     

    声明式事务管理:

    基于TransactionFactoryProxyBean(很少使用,需要为每一个进行事务管理的类,配置一个增强生成代理。)

    基于AspectJ的xml方式(常用,配置比xml复杂,但在配置文件中可以清晰看到增强的位置,并且类上不需要做任何修改。)

    基于注解方式(常用,配置简单,但是需要修改业务层的类,为它们加上@Transactional注解。)

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    原创 计算机系学生大学四年应该这样过
    ff3f34fq34f
    指针 引用
    POJ3352Road Construction
    POJ3308Paratroopers
    北大ACM试题分类 实时更新我所有的解题报告链接
    POJ2516Minimum Cost
    【转】一位ACMer过来人的心得
    POJ2528Mayor's posters
    POJ2186Popular Cows
  • 原文地址:https://www.cnblogs.com/lyh421/p/6726319.html
Copyright © 2011-2022 走看看