Spring事务@Transactional注解原理

一、思维导图

Spring事务@Transactional注解原理_第1张图片
Spring 事务管理分为编程式和声明式两种。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体的逻辑与事务处理解耦。

声明式事务有两种方式,一种是在配置文件(XML)中做相关的事务规则声明,另一种是基于 @Transactional 注解的方式。

默认配置下 Spring 只会回滚运行时、未检查异常(继承自 RuntimeException 的异常)或者 Error。
@Transactional 注解只能应用到 public 方法才有效。

1.1 声明式事务管理实现方式

基于tx和aop名字空间的xml配置文件

// 基本配置
<bean name="transactionManager"
	class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="shardingDataSource"></property>
	</bean>

<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />

// MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用
// 标签的声明,则是在Spring内部启用@Transactional来进行事务管理,使用 @Transactional 前需要配置

BPM项目中applicationContext.xml事务配置如下

	
	<bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
		<property name="sessionFactory" ref="sessionFactory" />
	bean>
	
	<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />
	
	<tx:advice id="txAdvice" transaction-manager="txManager">
		<tx:attributes>
			<tx:method name="save*" propagation="REQUIRED" />
			<tx:method name="import*" propagation="REQUIRED" />
			<tx:method name="add*" propagation="REQUIRED" />
			<tx:method name="create*" propagation="REQUIRED" />
			<tx:method name="insert*" propagation="REQUIRED" />
			<tx:method name="edit*" propagation="REQUIRED" />
			<tx:method name="update*" propagation="REQUIRED" />
			<tx:method name="parse*" propagation="REQUIRED" /> 
			<tx:method name="merge*" propagation="REQUIRED" />
			<tx:method name="del*" propagation="REQUIRED" />
			<tx:method name="remove*" propagation="REQUIRED" />
			<tx:method name="put*" propagation="REQUIRED" />
			<tx:method name="use*" propagation="REQUIRED" />
			<tx:method name="start*" propagation="REQUIRED" />
			<tx:method name="stop*" propagation="REQUIRED" />
			<tx:method name="set*" propagation="REQUIRED" />
			<tx:method name="execute*" propagation="REQUIRED"/>
		 	<tx:method name="get*" propagation="REQUIRED" read-only="true" />
			<tx:method name="count*" propagation="REQUIRED" read-only="true" />
			<tx:method name="find*" propagation="REQUIRED" read-only="true" />
			<tx:method name="list*" propagation="REQUIRED" read-only="true" /> 
			<tx:method name="*" propagation="REQUIRED" read-only="true"  />
		tx:attributes>
	tx:advice>
	<aop:config expose-proxy="true">
		
		<aop:pointcut id="txPointcut"
			expression="(
			execution(* com.gzsolartech.smartforms.service..*.*(..))
			|| execution(* com.gzsolartech.smartforms.ldap..*.*(..))
			|| execution(* com.gzsolartech.bpmportal.service..*.*(..))
			|| execution(* com.gzsolartech.portal.service..*.*(..))
			|| execution(* com.gzsolartech.smartforms.webload.DatDocumentSaveInterceptor.*(..))
			|| execution(* com.gzsolartech.bpmportal.service.extend.ExtendDatDocumentService.*(..))
			)" />
		<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
	aop:config>

        <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
		
		<property name="dataSource"><ref bean="dataSource"/>property>

		<property name="hibernateProperties">
			
			<props>
				<prop key="hibernate.dialect">${hibernate.dialect}prop>
				<prop key="hibernate.show_sql">${hibernate.show_sql}prop>
				<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}prop>
				<prop key="hibernate.format_sql">${hibernate.format_sql}prop>
				<prop key="hibernate.connection.SetBigStringTryClob">${hibernate.connection.SetBigStringTryClob}prop>
				<prop key="hibernate.temp.use_jdbc_metadata_defaults">falseprop>
				
		 		
				<prop key="hibernate.cache.use_query_cache">trueprop>
				
				<prop key="hibernate.cache.use_second_level_cache">trueprop>
				 
				
				<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactoryprop>
				
				<prop key="hibernate.default_schema">${database.username}prop>
		                
 			props>
		property>
		<property name="packagesToScan">
			<list>
				<value>com.gzsolartech.smartforms.entityvalue>
				<value>com.gzsolartech.portal.entityvalue>
				<value>com.gzsolartech.bpmportal.entityvalue>
				<value>com.gzsolartech.smartforms.entity.bpmvalue>
			list>
		property>
	bean>

1.2 基于@Transactional注解

@Transactional实质是使用了JDBC的事务来进行事务控制的
@Transactional基于Spring的动态代理的机制

1.3 @Transactional实现原理

1:事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入DataSource实例的某个与DataSourceTransactionManager相关的某处容器中。

在接下来的整个事务中,客户代码都应该使用该connection连接数据库,执行所有数据库命令[不使用该connection连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚](物理连接connection逻辑上新建一个会话session;DataSource与TransactionManager配置相同的数据源)

2:事务结束时,回滚在第1步骤中得到的代理connection对象上执行的数据库命令,然后关闭该代理connection对象(事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)

二、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;
}

三、事务的隔离级别

是指若干个并发的事务之间的隔离程度

@Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE):串行化

四、事务传播行为

4.1 事务传播机制

如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为

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。

嵌套事务:带有事务的方法调用其他事务的方法,此时执行的情况取决配置的事务的传播属性
PROPAGATION_REQUIRES_NEW
启动一个新的,不依赖于环境的 “内部” 事务。这个事务将被完全 commited 或 rolled back 而不依赖于外部事务,它拥有自己的隔离范围,自己的锁等等。当内部事务开始执行时,外部事务将被挂起,内务事务结束时,外部事务将继续执行。
PROPAGATION_NESTED
如果外部事务 commit,嵌套事务也会被commit;如果外部事务roll back,嵌套事务也会被roll back。
开始一个 “嵌套的” 事务,它是已经存在事务的一个真正的子事务。嵌套事务开始执行时,它将取得一个 savepoint。如果这个嵌套事务失败,我们将回滚到此 savepoint。嵌套事务是外部事务的一部分,只有外部事务结束后它才会被提交。

4.2 @Transactional 的 propagation属性代码示例

比如如下代码,UserService 方法首先调用 updateUserById方法,再调用insertUser方法,但是抛出了异常,就会导致事务回滚,如下updateUserById和insertUser都不会插入数据库。

@Service
public class UserService {
 //更新
    @Transactional(propagation = Propagation.REQUIRED)
    public boolean updateUserById(User user) {
    	userMapper.updateUserById(user);
    	if(true) {
    		throw new RuntimeException("updateUserById 抛异常了");
    	}
    	
    	insertUser();
    	
        return true;
    }
    
    //新增
    public boolean insertUser() {
    	 /*
    	 //普通for循环插入
    	 for (int i = 0; i <20; i++) {
    	    User user = new User();
    		user.setId(i);
    		user.setUserName("testusername" + i);
    		user.setPassWord("testpassword" + i);
	        user.setRealName("testrealname"+i);
    		userMapper.insertUser(user);
    	  }
    	  */
    	//userMapper.insertUser(user);
    	List<User> list = new ArrayList<>();
    	for (int i = 0; i <2; i++) {
    	    User user = new User();
    		user.setId(i);
    		user.setUserName("*username" + i);
    		user.setPassWord("*password*"+i);
    		user.setRealName("*realname*"+i);
    		list.add(user);
        }
    	userMapper.insertUser(list);
        return true;
    }
}    

访问http://localhost:8086/testBoot/updateUser?id=1&realName=zhangsannfeng
报错如下
在这里插入图片描述
现在有需求如下,就算UserService 方法的后面抛异常了,也不能影响 updateUserById方法的数据插入。
为了解决这个问题,我们可以新建一个类OtherService:

package org.spring.springboot.service;

import java.util.ArrayList;
import java.util.List;

import org.spring.springboot.entity.User;
import org.spring.springboot.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OtherService  {
	@Autowired
	UserMapper userMapper;
	//新增
	@Transactional(propagation = Propagation.REQUIRES_NEW)
    public boolean insertUser() {
    	 /*
    	 //普通for循环插入
    	 for (int i = 0; i <20; i++) {
    	    User user = new User();
    		user.setId(i);
    		user.setUserName("testusername" + i);
    		user.setPassWord("testpassword" + i);
	        user.setRealName("testrealname"+i);
    		userMapper.insertUser(user);
    	  }
    	  */
    	//userMapper.insertUser(user);
    	List<User> list = new ArrayList<>();
    	for (int i = 10; i <15; i++) {
    	    User user = new User();
    		user.setId(i);
    		user.setUserName("*username" + i);
    		user.setPassWord("*password*"+i);
    		user.setRealName("*realname*"+i);
    		list.add(user);
        }
    	userMapper.insertUser(list);
        return true;
    }
}

而UserService更新如下

  //更新
    @Transactional(propagation = Propagation.REQUIRED)
    public boolean updateUserById(User user) {
    	userMapper.updateUserById(user);
    	
    	otherService.insertUser();
    	if(true) {
    		throw new RuntimeException("updateUserById 抛异常了");
    	}	
        return true;
    }

执行前数据库查询所有数据
Spring事务@Transactional注解原理_第2张图片
执行后http://localhost:8086/testBoot/updateUser?id=1&realName=zhangsanfeng结果如下:
Spring事务@Transactional注解原理_第3张图片
后台控制台报错
Spring事务@Transactional注解原理_第4张图片
可以看到,UserService类中userMapper.updateUserById(user);方法未生效,事务回滚,而otherService.insertUser();方法生效。

五、Spring事务回滚规则

指Spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。
Spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

默认配置下,Spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
用Spring事务管理器,由Spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException(“注释”);)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception(“注释”);)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .如果让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)

六、注意事项

@Transactional 使用位置类上方、方法上方
Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效
当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

方法的访问权限为 public
@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常

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

例如一:同一个类中方法,A方法未使用此标签,B使用了,C未使用,A 调用 B , B 调用 C ;则外部调用A之后,B的事务是不会起作用的
例如二:若是有上层(按照 Controller层、Service层、DAO层的顺序)由Action 调用 Service 直接调用,发生异常会发生回滚;若间接调用,Action 调用 Service 中 的A 方法,A无@Transactional 注解,B有,A调用B,B的注解无效。

参考文章
https://blog.csdn.net/mingyundezuoan/article/details/79017659
https://blog.csdn.net/weixin_39845113/article/details/110510730

你可能感兴趣的:(Spring,spring,java)