Spring事务管理 && 事务传播行为

Spring----事务管理

回顾事务

事务的做用

  1. 当数据库操做序列中个别操做失败时,提供一种方式使数据库状态恢复到正常状态(A),保障数据库
    即便在异常状态下仍能保持数据一致性(C)(要么操做前状态,要么操做后状态)。
  2. 当出现并发访问数据库时,在多个访问间进行相互隔离,防止并发访问操做结果互相干扰(I)。
    事务特征(ACID)
    ◆ 原子性(Atomicity)指事务是一个不可分割的总体,其中的操做要么全执行或全不执行
    ◆ 一致性(Consistency)事务先后数据的完整性必须保持一致
    ◆ 隔离性(Isolation)事务的隔离性是多个用户并发访问数据库时,数据库为每个用户开启的
    事务,不能被其余事务的操做数据所干扰,多个并发事务之间要相互隔离
    ◆ 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性
    的,接下来即便数据库发生故障也不该该对其有任何影响

事务隔离级

  • 脏读:容许读取未提交的信息java

    ◆ 缘由:Read uncommittedweb

    ◆ 解决方案: Read committed(表级读锁)spring

  • 不可重复读:读取过程当中单个数据发生了变化shell

    ◆ 解决方案: Repeatable read (行级写锁)数据库

  • 幻读:读取过程当中数据条目发生了变化express

    ◆ 解决方案: Serializable(表级写锁)编程

spring中的事务

设想一个场景,模拟银行的转帐业务,用户A给用户B转款100,咱们在业务层须要调用 加钱的数据操做 和 减钱的数据操做,这是两个会话,两个独立的事务。
案例代码:并发

web层:app

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService) ctx.getBean("accountService");
accountService.transfer("Jock1","Jock2",100D);

service:svg

public void transfer(String outName, String inName, Double money) {
        accountDao.inMoney(outName,money); 
    	int i = 1 / 0;   //手动模拟出现异常
        accountDao.outMoney(inName,money);
}

dao:采用配置文件映射实体类方式

<update id="inMoney">
        update account set money = money + #{money} where name = #{name}
</update>

<update id="outMoney">
    update account set money = money - #{money} where name = #{name}
</update>

由此看以看出两个对数据库的操做是独立的。若是在两个事务之间还有其余代码,那么中间的代码出现异常,那么就会出现一个事务执行成功,一个事务执行失败的状况。(假设是事务自动提交前提下)

在spring中,dao的实现类是交给spring进行赞成管理的,那么spring有没有对业务进行赞成管理呢?将多个业务赞成进行管理,让指定的多个业务之间拥有原子类的特性。下面的东西spring的解决方案。

Spring事务核心对象

J2EE开发使用分层设计的思想进行,对于简单的业务层转调数据层的单一操做,事务开启在业务层或者
数据层并没有太大差异,当业务中包含多个数据层的调用时,须要在业务层开启事务,对数据层中多个操
做进行组合并归属于同一个事务进行处理。

  • Spring为业务层提供了整套的事务解决方案

    ◆ PlatformTransactionManager 平台事务管理器实现类

    ◆ TransactionDefinition 事务的基本信息

    ◆ TransactionStatus

PlatformTransactionManager

平台事务管理器实现类
◆ DataSourceTransactionManager 适用于Spring JDBC或MyBatis
◆ HibernateTransactionManager 适用于Hibernate3.0及以上版本
◆ JpaTransactionManager 适用于JPA
◆ JdoTransactionManager 适用于JDO
◆ JtaTransactionManager 适用于JTA

这是一个接口,定义了spring对事务管理的规范。对于不一样数据库有不一样的实现类。咱们使用的是 DataSourceTransactionManager

这个接口的主要功能是:开启事务,提交事务。

此接口定义了事务的基本操做

获取事务 :

TransactionStatus getTransaction(TransactionDefinition definition)

提交事务 :

void commit(TransactionStatus status)

回滚事务 :

void rollback(TransactionStatus status)

TransactionDefinition

此接口定义了事务的基本信息

◆ 获取事务定义名称

String getName()

◆ 获取事务的读写属性

boolean isReadOnly()

◆ 获取事务隔离级别

int getIsolationLevel()
事务隔离级别取值
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1
int ISOLATION_READ_COMMITTED = 2
int ISOLATION_REPEATABLE_READ = 4
int ISOLATION_SERIALIZABLE = 8◆ 获取事务超时时间
int getTimeout()

◆ 获取事务传播行为特征

int getPropagationBehavior()

TransactionStatus

此接口定义了事务在执行过程当中某个时间点上的状态信息及对应的状态操做

◆ 获取事务是否处于新开启事务状态

boolean isNewTransaction()

◆ 获取事务是否处于已完成状态

boolean isCompleted()

◆ 获取事务是否处于回滚状态

boolean isRollbackOnly()

◆ 刷新事务状态

void flush()

◆ 获取事务是否具备回滚存储点

boolean hasSavepoint()

◆ 设置事务处于回滚状态

void setRollbackOnly()

管理事务-----编程式事务

使用java代码的方式对须要统一的事务进行管理

private AccountDao accountDao;
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
}
public void transfer(String outName,String inName,Double money){
    //建立事务管理器
    DataSourceTransactionManager dstm = new DataSourceTransactionManager();
    //为事务管理器设置与数据层相同的数据源
    dstm.setDataSource(dataSource);
    //建立事务定义对象
    TransactionDefinition td = new DefaultTransactionDefinition();
    //建立事务状态对象,用于控制事务执行
    TransactionStatus ts = dstm.getTransaction(td);
    accountDao.inMoney(outName,money);
    int i = 1/0; //模拟业务层事务过程当中出现错误
    accountDao.outMoney(inName,money);
    //提交事务
    dstm.commit(ts);
}

这个方法中本来有两个独立的事务,可是如今,多了一个平台事务管理类,那么这两个事务就会被外面的事务进行统一的管理。那么这两个事务就具备了原子类的特性。当第一个事务执行后不会提交,中间代码出现异常,会将事务所有回滚。至于前面介绍了这么方法和属性,这些都是有默认值的。
另一个须要注意点:事务管理器是须要一个链接池的。很好理解为何须要链接池,你连会话对象都没有,你怎么将这些事务进行提交。事务管理器会自动从链接池中获取链接对事务进行提交。

如今咱们思考,若是多个方法都须要对事务进行管理,那么这些代码都是固定统一的。因此咱们可使用AOP将这些共性的代码统一抽取出来。

管理事务-----AOP改造编程事务

配置AOP通知类,并注入dataSource

<bean id="txAdvice" class="com.itheima.aop.TxAdvice">
	<property name="dataSource" ref="dataSource"/>
</bean>

使用环绕通知将通知类织入到原始业务对象执行过程当中

<bean id="txAdvice" class="com.MyDemo.aop.TxAdvice">
    <property name="dataSource" ref="dataSource"/>
</bean <aop:config>
    <aop:pointcut id="pt" expression="execution(* *..transfer(..))"/>
    <aop:aspect ref="txAdvice">
    	<aop:around method="tx" pointcut-ref="pt"/>
	</aop:aspect>
</aop:config>

在重复代码中,咱们须要建立 PlatformTransactionManager (平 台事务管理类),因此咱们使用Bean标签进行单独配置。

管理事务-----声明式事务(XML)

spring见这些代码都这么固定,因此它定义了一套标签,使用这些标签来带体这些固定重复的代码,具体实现有spring帮你完成。

使用tx命名空间配置事务专属通知类

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
</bean>

    <!--定义事务管理的通知类-->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!--定义控制的事务-->
        <tx:attributes>
            <tx:method name="*" read-only="false"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="a" read-only="false" propagation="REQUIRED"/>
            <tx:method name="b" read-only="false" propagation="NEVER"/>

            <tx:method name="transfer" read-only="false" timeout="-1" isolation="DEFAULT" no-rollback-for="" rollback-for="" propagation="REQUIRED" />
            <!--<tx:method name="transfer" read-only="false"/>-->
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="pt" expression="execution(* com.itheima.service.*Service.*(..))"/>
        <aop:pointcut id="pt2" expression="execution(* com.itheima.dao.*.b(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt2"/>
    </aop:config>

在重复代码中,咱们须要建立 PlatformTransactionManager (平台事务管理类),因此咱们使用Bean标签进行单独配置。

tx:advice 标签就至关于咱们的通知类,将通知类的代码所有由标签代替,spring看见这些标签就知道咱们要进行事务的管理。

transaction-manager=“txManager” 可是平台事务管理类,这里仍是须要咱们本身建立传进去,由于PlatformTransactionManager是一个接口,不一样的数据库有不一样的实现类,因此须要咱们本身手动指定,其余两个接口TransactionDefinition, TransactionStatus 都是固定的,就由spring帮咱们进行处理了。

<tx:method name="*" 待添加事务的方法名表达式(支持*号通配符),例如get* 、* 、…… read-only="false" 设置事务的读写属性,true为只读,false为读写 timeout="-1" 设置事务超时时长,单位秒 isolation="DEFAULT" 设置事务隔离级别,该隔离级设定是基于Spring的设定,非数据库端 no-rollback-for="" 设置事务中不回滚的异常,多个异常间使用,分割 rollback-for="" 设置事务中必回滚的异常,多个异常间使用,分割 propagation="REQUIRED" 设置事务的传播行为 />

这个配置语句的做用等同于

//建立事务定义对象
 TransactionDefinition td = new DefaultTransactionDefinition();

这个对象的方法,如今转换为标签属性进行设置。

只是咱们在前面没有进行设置,采用的默认设置,并且之后基础编程式事务用的比较少。你不设置也会有默认值

name 属性,具体指定哪些方法进行事务管理。在原有的切入点上进行过滤。由于原有的挂载点可能会统一挂载其余共性方法,可是里面个别方法才须要对事务进行管理

事务传播行为

在这里插入图片描述

在这里插入图片描述

设置多个事务之间对 事务平台管理器的态度,我是用你的仍是使用本身的。默认REQUIRED

管理事务-----声明式事务(注解+XML)

名称:@Transactional
类型:方法注解,类注解,接口注解
位置:方法定义上方,类定义上方,接口定义上方
做用:设置当前类/接口中全部方法或具体方法开启事务,并指定相关事务属性
范例:

@Transactional(
    readOnly = false,
    timeout = -1,
    isolation = Isolation.DEFAULT,
    rollbackFor = {ArithmeticException.class, IOException.class},
    noRollbackFor = {},
    propagation = Propagation.REQUIRES_NEW
)

将tx标签和aop标签所有变成注解进行声明

可是须要xml配置文件声明,让spring认识这些注解

<tx:annotation-driven transaction-manager="txManager"/>

那么咱们如今只须要把这个注解加载须要进行事务管理的方法上,咱们以前方法的多事务就会由spring进行统一的管理

纯注解

名称:@EnableTransactionManagement
类型:类注解
位置:Spring注解配置类上方
做用:开启注解驱动,等同XML格式中的注解驱动
范例:

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JDBCConfig.class,MyBatisConfig.class,TransactionManagerConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
public class TransactionManagerConfig {
    @Bean
    public PlatformTransactionManager getTransactionManager(@Autowired DataSource dataSource){
   	 	return new DataSourceTransactionManager(dataSource);
    }
}
public interface AccountService {
    @Transactional
    public void transfer(String outName, String inName, Double money);

}

咱们通常会加到 须要对多个事务进行统一管理方法的接口方法上,那么实现类的方法也会进行事务的统一管理,由于当你实现类更换,那么注解不须要进行更改。