SpringAOP——事务
参考官网 + 百度翻译...
一、简单介绍
官网介绍Spring框架提供的事务管理有以下优点:
- 跨不同事务API(例如Java事务API(JTA),JDBC,Hibernate和Java Persistence API(JPA))的一致编程模型。
- 支持声明式事务
- 支持代码式事务API相对简单,不像JTA等API复杂
- 与Spring Data Access的出色集成。
1、一致编程模型
两种事务模型:
全局事务:可以使用多个事务资源,通常是关系数据库和消息队列。应用服务器通过JTA管理全局事务,而JTA是一个繁琐的API(部分是由于其异常模型)。此外,UserTransaction
通常需要从JNDI 派生JTA ,这意味着还需要使用JNDI才能使用JTA。全局事务的使用限制了应用程序代码的任何潜在重用,因为JTA通常仅在应用程序服务器环境中可用。
局部事务:本地事务是特定于资源的,例如与JDBC连接关联的事务。本地事务可能更易于使用,但有一个明显的缺点:它们不能跨多个事务资源工作。例如,使用JDBC连接管理事务的代码不能在全局JTA事务中运行。因为应用程序服务器不参与事务管理,所以它无法帮助确保多个资源之间的正确性。(值得注意的是,大多数应用程序使用单个事务资源。)另一个缺点是本地事务侵入了编程模型。
spring的一致编程模型:Spring解决了全局和本地交易的弊端。它使应用程序开发人员可以在任何环境中使用一致的编程模型。您只需编写一次代码,它就可以从不同环境中的不同事务管理策略中受益。Spring框架提供了声明式和程序化事务管理。官方建议使用声明式事务管理。通过程序化事务管理,开发人员可以使用Spring Framework事务抽象,该抽象可以在任何基础事务基础架构上运行。使用首选的声明性模型,开发人员通常只需编写很少或没有编写与事务管理相关的代码,因此,它们不依赖于Spring Framework事务API或任何其他事务API。
2、声明式事务
Spring面向方面的编程(AOP)使Spring框架的声明式事务管理成为可能。但是,由于事务方面的代码随Spring Framework发行版一起提供并且可以以样板方式使用,因此通常不必理解AOP概念即可有效地使用此代码。声明式事务的特点:
-
可在任何环境中工作。通过调整配置文件,它可以使用JDBC,JPA或Hibernate来处理JTA事务或本地事务。
-
您可以将Spring Framework声明式事务管理应用于任何类,而不仅限于EJB之类的特殊类。
-
Spring框架提供了声明性回退规则。提供了对回滚规则的编程和声明性支持。
-
Spring Framework允许您使用AOP自定义事务行为。例如,在事务回滚的情况下,您可以插入自定义行为。您还可以添加任意建议以及事务建议。
-
Spring框架不支持跨远程调用传播事务上下文。如果需要此功能,建议您使用EJB。但是,在使用这种功能之前,请仔细考虑,因为通常情况下,您不希望事务跨越远程调用。
事务方法调用视图:
3、代码式事务
Spring框架通过使用以下两种方式提供程序化事务管理的方法:
-
TransactionTemplate
。 -
PlatformTransactionManager。
Spring团队通常建议TransactionTemplate
针对程序化事务管理。第二种方法类似于使用JTA UserTransaction
API,尽管异常处理不那么麻烦。
TransactionTemplate实现
public class SimpleService implements Service { private final TransactionTemplate transactionTemplate; public SimpleService(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); } public Object someServiceMethod() { return transactionTemplate.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { updateOperation1(); return resultOfUpdateOperation2(); } }); } }
PlatformTransactionManager实现:
DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setName("SomeTxName"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try { // execute your business logic here } catch (MyException ex) { txManager.rollback(status); throw ex; } txManager.commit(status);
声明式事务与代码式事务的选择:
代码式事务:执行少量事务操作时,或者不想依靠Spring事务使用其他事务技术。代码式事务,需要编码所以代码侵入性。
声明式事务:具有许多事务操作时。声明式事务管理脱离业务逻辑,使用SpringAOP服务,配置简单,代码侵入性不强。
二、声明式事务的抽象
1、PlatformTransactionManager:定义transaction流程接口
public interface PlatformTransactionManager extends TransactionManager { //开启事务 TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; //事务提交 void commit(TransactionStatus status) throws TransactionException; //事务回滚 void rollback(TransactionStatus status) throws TransactionException; }
2、TransactionDefinition:定义事务属性接口
① spring7个传播行为
② spring5个事务隔离级别(实际还是4个数据库系统事务隔离级别)
③ 事务超时
④ 只读事务
public interface TransactionDefinition { /** * Spring定义了7个传播行为 */ int PROPAGATION_REQUIRED = 0;//如果已存在事务,加入到当前事务中;如果不存在,创建新事务 (默认传播行为)(实际一个事务,外层内层异常,都会事务回滚) int PROPAGATION_SUPPORTS = 1;//如果已存在事务,加入到当前事务中;如果不存在,不创建新事务,以非事务方式执行 int PROPAGATION_MANDATORY = 2;//如果已存在事务,加入到当前事务中;如果不存在,抛出异常 int PROPAGATION_REQUIRES_NEW = 3;//如果已存在事务,暂停当前事务,创建新事务;如果不存在,创建新事务(实际两个事务,外层挂起,等内层执行完,后执行,回退没有联系) int PROPAGATION_NOT_SUPPORTED = 4;//如果已存在事务,暂停当前事务,不创建事务,按非事务方式执行;如果不存在,不创建事务,(实际一个事务,内层异常,会引发回滚) int PROPAGATION_NEVER = 5;//如果已存在事务,抛出异常;如果不存在,不创建新事务,以非事务方式执行。 int PROPAGATION_NESTED = 6;//如果已存在事务,则在嵌套事务中执行,如果不存在,创建新事务(实际两个事务,外层异常,都回滚,内层异常,内层回滚,外层不需要回滚) /** * spring5个事务隔离级别 * @see java.sql.Connection */ int ISOLATION_DEFAULT = -1;//默认使用数据库系统存储引擎的隔离级别(MySQL默认隔离级别:可重复读) int ISOLATION_READ_UNCOMMITTED = 1; //未提交读 same as java.sql.Connection.TRANSACTION_READ_UNCOMMITTED; int ISOLATION_READ_COMMITTED = 2; //提交读 same as java.sql.Connection.TRANSACTION_READ_COMMITTED; int ISOLATION_REPEATABLE_READ = 4; //可重复读 same as java.sql.Connection.TRANSACTION_REPEATABLE_READ; int ISOLATION_SERIALIZABLE = 8; //串行 same as java.sql.Connection.TRANSACTION_SERIALIZABLE;
/** * 超时 * @see java.sql.Connection */ int TIMEOUT_DEFAULT = -1;//默认使用数据库系统的事务超时时间 //返回传播行为:默认PROPAGATION_REQUIRED default int getPropagationBehavior() { return PROPAGATION_REQUIRED; } //返回隔离级别:默认返回数据库默认的隔离级别 default int getIsolationLevel() { return ISOLATION_DEFAULT; } //返回事务超时时间:默认为数据库系统的事务超时时间 default int getTimeout() { return TIMEOUT_DEFAULT; } //判断事务是否只读:默认为非只读事务 default boolean isReadOnly() { return false; } @Nullable default String getName() { return null; } //创建一个final实例 static TransactionDefinition withDefaults() { return StaticTransactionDefinition.INSTANCE; } }
3、TransactionStatus:定义事务的状态接口
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable { /** * extends TransactionExecution */ /** * 是否新事务 */ boolean isNewTransaction(); /** * 仅设置事务回滚,说明事务唯一可能结构是回滚,而不是异常引发的回滚 */ void setRollbackOnly(); /** * 判断事务是否仅回滚 */ boolean isRollbackOnly(); /** * 判断事务是否已完成(已提交或者已回滚) */ boolean isCompleted(); /** * extends TransactionExecution */ /** * 创建一个保存点, * 可通过rollbackToSavepoint,回滚到这个保存点 * 可通过releaseSavepoint,释放这个保存点 */ Object createSavepoint() throws TransactionException; /** * 回滚到事务保存点*/ void rollbackToSavepoint(Object savepoint) throws TransactionException; /** * 释放(删除)事务保存点,大多数事务管理器会自动释放*/ void releaseSavepoint(Object savepoint) throws TransactionException; /** * 判断事务内部是否有保存点 */ boolean hasSavepoint(); /** * 将底层会话刷新到数据存储 */ @Override void flush(); }
三、声明式事务的简单实现
spring5+mybatis
目录结构:
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- IOC容器自动扫描package --> <context:component-scan base-package="com.app.*"/> <!-- 开启AOP支持 --> <aop:aspectj-autoproxy/> <!-- 属性文件 --> <context:property-placeholder location="db.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${mysql.driver}"/> <property name="url" value="${mysql.url}"/> <property name="username" value="${mysql.username}"/> <property name="password" value="${mysql.password}"/> <property name="initialSize" value="${mysql.initialSize}"/> <property name="minIdle" value="${mysql.minIdle}"/> <property name="maxActive" value="${mysql.maxActive}"/> <property name="maxWait" value="${mysql.maxWait}"/> </bean> <!-- mybatis的SQLSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="mapper/UserMapper.xml"/> </bean> <!-- mybatis的mapper接口映射器 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <property name="basePackage" value="com.app.aop.transactional.mapper"/> </bean> <!-- 事务管理 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 启用声明式事务管理 支持注解@Transaction--> <tx:annotation-driven proxy-target-class="false" transaction-manager="transactionManager"/> </beans>
UserServiceImp.java
@Service("userService") public class UserServiceImp implements UserService { @Autowired private UserMapper userMapper; @Override public void insert(User user) { userMapper.insert(user); } @Override public User getUserByUserId(Long userId) { return userMapper.selectByPrimaryKey(userId); } @Override @Transactional public void transactionalTest(User user,boolean flag){ userMapper.insert(user); if(flag){ throw new NullPointerException(); } userMapper.insert(user); } }
测试类:TransactionalTests.java
public class TransactionalTests { @Test public void trancationalTest(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("mybatis-config.xml"); UserService userService = (UserService) context.getBean("userService"); User user = userService.getUserByUserId(1L); user.setUserId(2011221104210100L); user.setUserName("rollback"); try { //flag == true ,throw Exception userService.transactionalTest(user,true); } catch (Exception e) { e.printStackTrace(); } user.setUserId(2011221104210200L); user.setUserName("commit"); //flag == false,successs userService.transactionalTest(user,false); System.out.println(userService.getUserByUserId(1L)); } }
四、声明式事务源码跟踪
1、声明式事务支持
<!-- 启用声明式事务管理 支持注解@Transaction--> <tx:annotation-driven proxy-target-class="false" transaction-manager="transactionManager"/>
如果用的idea直接点击标签看看,或者直接打开spring-tx.xsd;
<xsd:schema xmlns="http://www.springframework.org/schema/tx" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:tool="http://www.springframework.org/schema/tool" targetNamespace="http://www.springframework.org/schema/tx" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xsd:element name="annotation-driven"> <xsd:complexType> <xsd:annotation> <xsd:documentation source="java:org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"><![CDATA[ <!-- 简化说明:开启Spring @Transactional --> ]]></xsd:documentation> <!-- 省去标签属性说明 --> </xsd:annotation> </xsd:element> </xsd:schema>
标签命名空间targetNamespace="http://www.springframework.org/schema/tx"在spring.handers中找到了标签命名空间TxNamespaceHandle
在TxNamespaceHandle中找到了<tx:annotiation-driven>的解析类AnnotationDrivenBeanDefinitionParser
//spring-tx/resourcesMETA-INF/spring.handlers中找到了命名空间解析 http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler /* org.springframework.transaction.config.TxNamespaceHandler */ public void init() { registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser()); registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser()); }
AnnotationDrivenBeanDefinitionParser解析
/* org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser#parse */ public BeanDefinition parse(Element element, ParserContext parserContext) { //注册一个事务事件监听器的RootBeandefinition //beanClass = TransactionalEventListenerFactory.class //beanName = org.springframework.transaction.config.internalTransactionalEventListenerFactory registerTransactionalEventListenerFactory(parserContext); String mode = element.getAttribute("mode"); //aspectj实现AOP if ("aspectj".equals(mode)) { // mode="aspectj" registerTransactionAspect(element, parserContext); if (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader())) { registerJtaTransactionAspect(element, parserContext); } } else { //SpringAOP实现AOP // mode="proxy" AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext); } return null; } /* org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser.AopAutoProxyConfigurer */ private static class AopAutoProxyConfigurer { public static void configureAutoProxyCreator(Element element, ParserContext parserContext) { //若未注册,先注册SpringAOP相关Beandefinition AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element); String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME; if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) { Object eleSource = parserContext.extractSource(element); //注册AnnotationTransactionAttributeSource的RootBeanDefinition //beanName = org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0 RootBeanDefinition sourceDef = new RootBeanDefinition( "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"); sourceDef.setSource(eleSource); sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef); //注册TransactionInterceptor的RootBeanDefinition到beanfactory中 //beanName=org.springframework.transaction.interceptor.TransactionInterceptor#0 RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class); interceptorDef.setSource(eleSource); interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registerTransactionManager(element, interceptorDef); interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName)); String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef); //注册BeanFactoryTransactionAttributeSourceAdvisor到beanfactory中 //beanName = org.springframework.transaction.config.internalTransactionAdvisor RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class); advisorDef.setSource(eleSource); advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName)); advisorDef.getPropertyValues().add("adviceBeanName", interceptorName); if (element.hasAttribute("order")) { advisorDef.getPropertyValues().add("order", element.getAttribute("order")); } parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef); CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource); compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName)); compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName)); compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName)); parserContext.registerComponent(compositeDef); } } }
总结:<tx:annotiation-driven>向beanfactory中注册的4个RootBeanDefinition
internalTransactionalEventListenerFactory AnnotationTransactionAttributeSource TransactionInterceptor//SpringAOP方法执行时的责任链拦截器 BeanFactoryTransactionAttributeSourceAdvisor//直接用bean创建的Advisor
pointCut:切点:@transactional注解
/* org.springframework.transaction.annotation.SpringTransactionAnnotationParser#isCandidateClass */ public boolean isCandidateClass(Class<?> targetClass) { return AnnotationUtils.isCandidateClass(targetClass, Transactional.class); }
Advisor:TransactionInterceptor
/* org.springframework.transaction.interceptor.TransactionInterceptor#invoke */ public Object invoke(MethodInvocation invocation) throws Throwable { // Work out the target class: may be {@code null}. // The TransactionAttributeSource should be passed the target class // as well as the method, which may be from an interface. Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); // Adapt to TransactionAspectSupport's invokeWithinTransaction... return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed); } /* org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction */ protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable { ...if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction and commit/rollback calls. //开启事务 TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); Object retVal; try { // This is an around advice: Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. //执行方法 retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // target invocation exception //报错回滚 completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { cleanupTransactionInfo(txInfo); } ... }