spring事务

spring支持两种类型的事务管理:

  • 编程式事务管理:写在代码里
    • PlatformTransactionManager
    • TransactionTemplate
  • 声明式事务管理:将事务管理从业务代码中分离。
    • <tx:advice>标签
    • @Transactional
事务的类型
  1. 数据库分为本地事务跟全局事务
    1. 本地事务:普通事务,独立一个数据库,能保证在该数据库上操作的ACID
    2. 分布式事务:设计两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成),分布式事务旨在保证这些本地事务的所有操作的ACID,使事务可以跨越多台数据库
  2. java事务类型分为JDBC事务和JTA事务
    1. JDBC事务:即为上面说的数据库事务中的本地事务,通过connection对象控制管理
    2. JTA事务:
  3. 按是否通过编程分为声明式事务和编程式事务
    1. 声明式事务:通过XML配置或者注解实现
    2. 编程式事务:通过编程代码在业务逻辑时需要时自行实现,粒度更小
TransactionDefinition接口

在这里插入图片描述

五个隔离级别
  • Transaction.ISOLATION_DEFAULT:使用后端数据库默认的隔离级别MySQL默认采用的是REPEATABLE_READ隔离级别,Oracle默认采用的是READ_COMMIT级别
  • Transaction.ISOLATION_READ_UNCOMMIT:最低的隔离级别允许读取尚未提交的数据变更可能会导致脏读、幻读或不可重复读
  • Transaction.ISOLATION_READ_COMMIT:允许读取并发事务已经提交的数据可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • Transaction.ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务修改,可以阻止脏读或不可重复读,但幻读仍有可能发生
  • Transaction.ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。
事务传播行为(为了解决业务层方法之间互相调用的事务问题)

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。在TransactionDefinition自定义中包括了下面几个表示传播行为的常量
支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;
    如果当前没有事务,则创建一个新的事务
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
    不支持当前事务的情况:
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起
  • TransactionDefinition.PROPAGATION_NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常
    其他情况
  • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则创建一个新事务(后一情况与TransactionDefinition.PROPAGATION_REQUIRED一直)
    前六种事务传播行为是spring从EJB引入的,而PROPAGATION_NESTED是spring所特有的。
    内嵌事务并不是一个独立的事务,它依赖于外部事务存在,只有通过外部事务的提交,才能引起内部事务的提交,内嵌的子事务不能单独提交,外部事务的回滚也会导致嵌套子事务的回滚
事务超时属性(一个事务允许执行的最长时间)

指一个事务所允许执行的最长时间,如果
超过该时间限制但事务还没完成,则自动回滚事务。在TransactionDefinition中以int的值来表示超时时间,单位是秒

事务只读属性(对事务资源是否执行只读操作)

指事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、JMS资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在TransactionDefinition中以boolean类型来表示该事务是否只读

回滚规则(定义事务回顾规则)

这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行时异常时才会回滚,而遇到检查型异常时不会回滚。但是可以声明事务在遇到特定的检查型异常时也回滚。

Spring事务管理接口介绍
  • PlatformTransactionManager:(平台)事务管理器
  • TransactionDefinition:事务定义信息(上述隔离级别、传播行为、超时、只读、回滚)
  • TransactionStatus:事务运行状态
PlatformTransactionManager

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
Spring框架支持事务管理的核心就是事务管理器抽象,对于不同的数据访问框架通过实现策略接口PlatformTransactionManager,从而能支持多种数据访问框架的事务。
PlatformTransactionManager接口定义了三大方法

Public interface PlatformTransactionManager()...{  
    // Return a currently active transaction or create a new one, according to the specified propagation behavior(根据指定的传播行为,返回当前活动的事务或创建一个新事务。)
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 
    // Commit the given transaction, with regard to its status(使用事务目前的状态提交事务)
    Void commit(TransactionStatus status) throws TransactionException;  
    // Perform a rollback of the given transaction(对执行的事务进行回滚)
    Void rollback(TransactionStatus status) throws TransactionException;  
    } 
}

刚刚也说了Spring中PlatformTransactionManager根据不同持久层框架所对应的接口实现类,几个比较常见的如下图所示
在这里插入图片描述

编程式事务管理
PlatformTransactionManager

首先配置事务管理器

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="jdbcUrl" value="${db.jdbcUrl}" />
	<property name="user" value="${user}" />
	<property name="password" value="${password}" />
	<property name="driverClass" value="${db.driverClass}" />
	 <!--连接池中保留的最小连接数。 --> 
     <property name="minPoolSize"> 
         <value>5</value> 
     </property> 
     <!--连接池中保留的最大连接数。Default: 15 --> 
     <property name="maxPoolSize"> 
         <value>30</value> 
     </property> 
     <!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 --> 
     <property name="initialPoolSize"> 
         <value>10</value> 
     </property> 
     <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 --> 
     <property name="maxIdleTime"> 
         <value>60</value> 
     </property> 
     <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 --> 
     <property name="acquireIncrement"> 
         <value>5</value> 
     </property> 
     <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。  如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 --> 
     <property name="maxStatements"> 
         <value>0</value> 
     </property> 
     <!--每60秒检查所有连接池中的空闲连接。Default: 0 --> 
     <property name="idleConnectionTestPeriod"> 
         <value>60</value> 
     </property> 
     <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 --> 
     <property name="acquireRetryAttempts"> 
         <value>30</value> 
     </property> 
     <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效 保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。Default: false --> 
     <property name="breakAfterAcquireFailure"> 
         <value>true</value> 
     </property> 
     <!--因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的 时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable等方法来提升连接测试的性能。Default: false --> 
     <property name="testConnectionOnCheckout"> 
         <value>false</value> 
     </property> 
</bean>
<!--DataSourceTransactionManager位于org.springframework.jdbc.datasource包下,数据源事务管理类,提供对单个javax.sql.DataSource数据源的事务管理,主要用于JDBC,Mybatis框架事务管理。 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring-public.xml" })
public class test {
	@Resource
	private PlatformTransactionManager txManager;
	@Resource
	private  DataSource dataSource;
	private static JdbcTemplate jdbcTemplate;
	Logger logger=Logger.getLogger(test.class);
    private static final String INSERT_SQL = "insert into testtranstation(sd) values(?)";
    private static final String COUNT_SQL = "select count(*) from testtranstation";
	@Test
	public void testdelivery(){
		//定义事务隔离级别,传播行为,
	    DefaultTransactionDefinition def = new DefaultTransactionDefinition();  
	    def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);  
	    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);  
	    //事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取;获取事务状态后,Spring根据传播行为来决定如何开启事务
	    TransactionStatus status = txManager.getTransaction(def);  
	    jdbcTemplate = new JdbcTemplate(dataSource);
	    int i = jdbcTemplate.queryForInt(COUNT_SQL);  
	    System.out.println("表中记录总数:"+i);
	    try {  
	        jdbcTemplate.update(INSERT_SQL, "1");  
	        txManager.commit(status);  //提交status中绑定的事务
	    } catch (RuntimeException e) {  
	        txManager.rollback(status);  //回滚
	    }  
	    i = jdbcTemplate.queryForInt(COUNT_SQL);  
	    System.out.println("表中记录总数:"+i);
	}
	
}
TransactionTemplate

该类继承接口DefaultTransactionDefinition,用于简化事务管理,事务管理由模版类指定,主要通过TransactionCallback回调接口或TransactionCallbackWithoutResult回调接口指定,通过调用模版类的参数类型为TransactionCallback或TransactionCallbackWithoutResult的execute方法来自动享受事务管理

TransactionTemplate模板类使用的回调接口:

  • TransactionCallback:通过实现该接口的“T doInTransaction(TransactionStatus status) ”方法来定义需要事务管理的操作代码;
  • TransactionCallbackWithoutResult:继承TransactionCallback接口,提供“void doInTransactionWithoutResult(TransactionStatus status)”便利接口用于方便那些不需要返回值的事务操作代码。
@Test
public void testTransactionTemplate(){
	jdbcTemplate = new JdbcTemplate(dataSource);
    int i = jdbcTemplate.queryForInt(COUNT_SQL);  
    System.out.println("表中记录总数:"+i);
	//构造函数初始化TransactionTemplate
	TransactionTemplate template = new TransactionTemplate(txManager);
	template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);  
	//重写execute方法实现事务管理
	template.execute(new TransactionCallbackWithoutResult() {
		@Override
		protected void doInTransactionWithoutResult(TransactionStatus status) {
			jdbcTemplate.update(INSERT_SQL, "饿死");   //字段sd为int型,所以插入肯定失败报异常,自动回滚,代表TransactionTemplate自动管理事务
		}}
	);
	i = jdbcTemplate.queryForInt(COUNT_SQL);  
    System.out.println("表中记录总数:"+i);
}
声明式事务管理

编程式事务管理每次实现都要单独实现,代码冗余度高,而声明式事务属于无侵入式,不影响业务逻辑的实现。声明式事务主要有两种使用<tx:advice>标签或@Transactional注解

<tx:advice>标签

配置文件如下

<!-- 
<tx:advice>定义事务通知,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过<tx:attributes>指定具体需要拦截的方法
	<tx:method>拦截方法,其中参数有:
	name:方法名称,将匹配的方法注入事务管理,可用通配符
	propagation:事务传播行为,
	isolation:事务隔离级别定义;默认为“DEFAULT”
	timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
	read-only:事务只读设置,默认为false,表示不是只读;
    rollback-for:需要触发回滚的异常定义,可定义多个,以“,”分割,默认任何RuntimeException都将导致事务回滚,而任何Checked Exception将不导致事务回滚;
    no-rollback-for:不被触发进行回滚的 Exception(s);可定义多个,以“,”分割;
 -->
 
<tx:advice id="advice" transaction-manager="transactionManager">
	<tx:attributes>
	    <!-- 拦截save开头的方法,事务传播行为为:REQUIRED:必须要有事务, 如果没有就在上下文创建一个 -->
		<tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="" read-only="false" no-rollback-for="" rollback-for=""/>
		<!-- 支持,如果有就有,没有就没有 -->
		<tx:method name="*" propagation="SUPPORTS"/>
	</tx:attributes>
</tx:advice>
<!-- 定义切入点,expression为切人点表达式,如下是指定impl包下的所有方法,具体以自身实际要求自定义  -->
<aop:config>
    <aop:pointcut expression="execution(* com.kaizhi.*.service.impl.*.*(..))" id="pointcut"/>
    <!--<aop:advisor>定义切入点,与通知,把tx与aop的配置关联,才是完整的声明事务配置 -->
    <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>

注意事项:

  • 事务回滚异常只能为RuntimeException,而Checked Exception异常不回滚,捕获异常不抛出也不会回滚,但可以强制事务回滚(配置事务回滚规则)
  • 解决“自我调节”而导致的不能设置正确的事务属性问题,可参考
    http://www.iteye.com/topic/1122740
@Transactional
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">   
     <property name="dataSource" ref="dataSource"/>
</bean>    
<tx:annotation-driven transaction-manager="txManager"/> //开启事务注解

@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.READ_COMMITTED),具体参数跟上面tx:method中一样
Spring提供的@Transaction注解事务管理,内部同样是利用环绕通知TransactionInterceptor实现事务的开启及关闭。
使用@Transactional注意点:

  • 如果在接口/实现类或方法上都指定了@Transactional竹节,则优先级顺序为方法>实现类>接口
  • 建议只在实现类或实现类的方法上使用@Transactional,而不要在接口上使用,这是因为如果使用CGLIB代理就会失效,因为CGLIB是基于类的代理(继承),而接口上的@Transactional注解是不能继承的;JDK代理则没有问题
编写自定义注解时未写@Inherited的运行结果:编写自定义注解时写了@Inherited的运行结果:
子类的类上能否继承到父类的类上的注解?
子类方法,实现了父类上的抽象方法,这个方法能否继承到注解?
子类方法,继承了父类上的方法,这个方法能否继承到注解?
子类方法,覆盖了父类上的方法,这个方法能否继承到注解?

我们知道在编写自定义注解时,可以通过指定@Inherited注解,指明自定义注解是否可以被继承。通过测试结果来看,@Inherited 只是可控制 对类名上注解是否可以被继承。不能控制方法上的注解是否可以被继承。
只能继承父类(普通类,抽象类)的注解, 接口的都不行
并且只能父类的class级别的注解才能继承:@Target(value=
ElementType.Type,
ElementType.TYPE_PARAMETER )

参考:
spring事务隔离级别、传播行为以及spring+mybatis+atomikos实现分布式事务管理
Spring事务管理实现方式之编程式事务与声明式事务详解
Spring事务配置的五种方式 巨全!不看后悔,一看必懂!
一些问题

  1. 在 XML 里面配置了一个 SqlSessionFactoryBean 后,其究竟做了什么?
  2. 在 XML 里面配置了一个 MapperScannerConfigurer 后,其究竟做了什么?
  3. 在执行 Mapper 接口的查询方法后,发生了什么?
  4. tx:advice/、aop:config 标签如何创建事务切面的?
  5. 标签添加后为何就可以使用注解式事务了?
  6. 为什么会报 Transaction rolled back because it has been marked as rollback-only 异常?
  7. Transactional 注解是否可以加在 private、protected 方法上?
  8. 事务的传播属性到底有什么用,嵌套事务到底又是怎么一回事?
  9. 为什么抛出了异常,事务却没有回滚?
  10. Spring 事务是如何保证线程安全的?
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值