基于@Transactional注解的Spring事务

  • @Transactional注解概述

@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

@Transactional属性

属性 类型 描述
value String 可选的限定描述符,指定使用的事务管理器
propagation enum: Propagation 可选的事务传播行为设置
isolation enum: Isolation 可选的事务隔离级别设置
readOnly boolean 读写或只读事务,默认读写
timeout int (in seconds granularity) 事务超时时间设置
rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组

用法

@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,有时某些方法是不需要事务管理的,可通过@Transactional(propagation=Propagation.NOT_SUPPORTED)这个属性指定它不支持事务。同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

// 类的事务注解将使该类的所有public方法将都具有该类型的事务属性
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

	// 方法的事务注解属性将会覆盖类的事务注解定义
	@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
	public void updateFoo(Foo foo) {
		// do something
	}

	// 该注解属性指定该方法执行前不会开启事务
	@Transactional(propagation = Propagation.NOT_SUPPORTED)
	public Foo getFoo(String fooName) {
		// do something
	}
	
	// rollbackFor属性指定事务针对哪种异常进行回滚
	@Transactional(rollbackFor=NullPointerException.class) 
	public void deleteFooById(Integer personid) {
		// do something
	}
	
	// noRollbackFor属性指定事务针对哪种异常不进行回滚 
	@Transactional(noRollbackFor=ArithmeticException.class) 
	public void deleteFooByName(Integer personid) {
		// do something
	}
}

基于@Transactional注解的声明式事务管理配置

spring-servlet.xml(需要添加添加tx命名空间)

...  
    xmlns:tx="http://www.springframework.org/schema/tx"  
    xmlns:aop="http://www.springframework.org/schema/aop"  
    xsi:schemaLocation="  
    ...  
http://www.springframework.org/schema/tx  
http://www.springframework.org/schema/tx/spring-tx.xsd  
 ...

<!-- transaction support-->

<!-- PlatformTransactionMnager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 非JTA事务(即非分布式事务)需指定dataSource属性(非分布式事务是在数据库创建的链接上开启) -->
    <!-- JTA事务(分布式事务)不能指定dataSource属性(分布式事务是由全局事务来管理数据库链接的) -->
    <property name="dataSource" ref="dataSource" />
</bean>

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

<!-- 属性proxy-target-class="true"控制是基于接口的还是基于类的事务代理被创建 -->
<!-- true:基于cglib的代理将起作用;false(默认):标准的JDK基于接口的代理将起作用 
     基于JDK动态代理 ,可以将@Transactional放置在接口和具体类上。
     基于CGLIB类代理,只能将@Transactional放置在具体类上。-->

MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。

另外需要下载依赖包aopalliance.jar放置到WEB-INF/lib目录下。否则spring初始化时会报异常
java.lang.NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor

spring事务特性

spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口

public interface PlatformTransactionManager {
 
  TransactionStatus getTransaction(TransactionDefinition definition)
    throws TransactionException;
 
  void commit(TransactionStatus status) throws TransactionException;
 
  void rollback(TransactionStatus status) throws TransactionException;
}
其中TransactionDefinition接口定义以下特性:

事务隔离级别

隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:

  • TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
  • TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

事务传播行为

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

  • TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

事务超时

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

默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。

事务只读属性

只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。
默认为读写事务。

  • Spring事务回滚规则

(1) spring使用声明式事务处理,指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。默认情况下,如果被注解的数据库操作方法中发生了unchecked异常(即RuntimeException及其子类,Errors也会导致事务回滚),所有的数据库操作将rollback;如果发生的异常是checked异常(即Exception及非RuntimeException子类),默认情况下发生异常前的数据库操作不会回滚仍然会被提交。


checked异常: 
表示无效,不是程序中可以预测的。比如无效的用户输入,文件读取不存在,网络或者数据库链接错误。这些都是外在的原因,都不是程序内部可以控制的。 必须在代码中显式地处理。比如try-catch块处理,或者给所在的方法加上throws说明,将异常抛到调用栈的上一层。 
继承自Java.lang.Exception(java.lang.RuntimeException除外)。

unchecked异常: 
表示错误,程序的逻辑错误。是RuntimeException的子类,比如IllegalArgumentException, NullPointerException和IllegalStateException。不需要在代码中显式地捕获unchecked异常做处理。 
继承自java.lang.RuntimeException(而java.lang.RuntimeException继承自java.lang.Exception)。

	@Transactional
	public int txUpdateUser(User u1, User u2) {// 事务性方法
		int ret1 = userMapper.updateUserById(u1);// 因后面的unchecked异常而回滚
		int i = 1 / 0;// 抛出运行时异常,事务回滚
		int ret2 = userMapper.updateUserById(u2);// 未执行
		if(ret1 == 1 && ret2 == 1){
			return 1;
		}
		return 0;
	}
	@Transactional
	public int txUpdateUser(User u1, User u2) throws FileNotFoundException {// 事务性方法,抛出编译时(checked)异常
		int ret1 = userMapper.updateUserById(u1);// 并不会因后面的checked异常而回滚
		FileInputStream fis = new FileInputStream(new File("D:/test/a.txt"));//此处不进行try..catch..
		int ret2 = userMapper.updateUserById(u2);// 未执行
		if(ret1 == 1 && ret2 == 1){
			return 1;
		}
		return 0;
	}
通过以上两个例子可以发现,事务对于checked异常默认不会回滚,如果需要它进行回滚可以通过注解的rollbackFor属性来修改它的默认行为:

	@Transactional(rollbackFor=Exception.class)
	//rollbackFor这属性指定了,既使你出现了checked异常这种例外,那么它也会对事务进行回滚
	public int txUpdateUser(User u1, User u2) throws FileNotFoundException {// 事务性方法,抛出编译时(checked)异常
		int ret1 = userMapper.updateUserById(u1);// 因后面的异常而回滚
		FileInputStream fis = new FileInputStream(new File("D:/test/a.txt"));//此处不进行try..catch..
		int ret2 = userMapper.updateUserById(u2);// 未执行
		if(ret1 == 1 && ret2 == 1){
			return 1;
		}
		return 0;
	}

(2) 对整个事务性方法使用try..catch..异常捕获,在catch中没有进行任何事务处理,则这个业务方法也就等于脱离了spring事务的管理,因为没有任何异常会从业务方法中抛出,全被捕获并吞掉,导致Spring事务回滚策略失效,发生异常时数据库操作得不到回滚。

	@Transactional
	// 事务性操作,不可catch Exception或RuntimeException而不抛出,导致外围框架捕获不到异常,认为执行正确而提交。
	public int txUpdateUserAndCatch(User u1, User u2){  
		try {
			int ret1 = userMapper.updateUserById(u1);// 执行成功,没有回滚
			int i = 1 / 0;// 运行时异常
			int ret2 = userMapper.updateUserById(u2);// 未执行
			if(ret1 == 1 && ret2 == 1){
				return 1;
			}
		} catch (Exception e) { // 所有异常被捕获而未抛出
			//不做事务处理
		}
		return 0;
	}
如果一定要对程序做try..catch..处理,例如日志打印等,可在catch中将异常再次抛出,以便spring事务管理器可接受到异常,从而可以回滚。

	@Transactional
	// 事务性操作,不可catch Exception或RuntimeException而不抛出,导致外围框架捕获不到异常,认为执行正确而提交。
	public int txUpdateUserAndCatch(User u1, User u2){  
		try {
			int ret1 = userMapper.updateUserById(u1);// 操作回滚
			int i = 1 / 0;// 运行时异常
			int ret2 = userMapper.updateUserById(u2);// 未执行
			if(ret1 == 1 && ret2 == 1){
				return 1;
			}
		} catch (Exception e) { 
			// 在此手动抛出[非编译性异常]使spring接收到异常可回滚
			throw e;// 或throw new RuntimeException();
		}
		return 0;
	}

另外一种解决方案:在catch中手动回滚事务,使用这种方式需要注意catch一定要在最后执行,避免catch后还有数据库操作,违反事务的一致性。此方法适合所有异常情况,包括checked和unchecked异常。

	@Transactional
	// 事务性操作,不可catch Exception或RuntimeException而不抛出,导致外围框架捕获不到异常,认为执行正确而提交。
	public int txUpdateUserAndCatch(User u1, User u2){  
		try {
			int ret1 = userMapper.updateUserById(u1);// 操作回滚
			int i = 1 / 0;// 运行时异常
			int ret2 = userMapper.updateUserById(u2);// 未执行
			if(ret1 == 1 && ret2 == 1){
				return 1;
			}
		} catch (Exception e) { 
			// 手动回滚
//			TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
		}
		return 0;
	}

(3) 默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类的内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。


个人理解:通过@Transactional修饰的方法在其他类中的调用是通过所在类的代理类对象代为执行的,代理类会对事务标志的方法进行"事务包装",发生异常时可进行回滚;在同一个类的非事务性方法中调用一个事务性方法时,由于非事务性方法并不是由其代理类对象执行的,所以执行事务性方法的对象自然不是代理类对象,因此没有得到"事务包装",不会异常回滚。
	// 非事务方法中调用事务方法测试:事务不会回滚
	@Override
	public int NonTransactionUpdate(User u1, User u2){
		// 1.txUpdate方法内发生异常,事务没有回滚.
//		return this.txUpdateUser(u1, u2);
		// 2.txUpdateUserAndCatch方法内捕获并抛出运行时异常,事务没有回滚.
		// 3.txUpdateUserAndCatch方法catch中手动回滚事务失败:org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope
		return this.txUpdateUserAndCatch(u1, u2);
	}

(4)数据库建表类型对事务的影响

以MySQL为例,MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持。MyISAM类型的表强调的是性能,其执行数度比InnoDB类型更快,但是不提供事务支持,而InnoDB提供事务支持已经外部键等高级数据库功能。参考:http://blog.sina.com.cn/s/blog_6ac4c6cb01018pb1.html

可使用下述语句之一检查表的标类型: 
SHOW TABLE STATUS LIKE 'tbl_name';
SHOW CREATE TABLE tbl_name;
使用下述语句,可检查mysql服务器支持的存储引擎:   
SHOW ENGINES;
也可使用下述语句,检查与存储引擎有关的变量值: 
SHOW VARIABLES LIKE 'have_%';
例如,要想确定InnoDB存储引擎是否可用,可检查have_innodb变量的值。

猜你喜欢

转载自blog.csdn.net/w47_csdn/article/details/77573912