• 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 UserTransactionAPI,尽管异常处理不那么麻烦。

    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);
                }
           ...
        }

     

  • 相关阅读:
    power designer 水电费缴纳系统的设计
    水电费管理系统需求分析----表格的建立
    GUID
    Java对象的序列化
    模拟银行自动终端系统
    随便选择两个城市作为预选旅游目标。实现两个独立的线程分别显示10次城市名,每次显示后休眠一段随机时间(1000ms以内),哪个先显示完毕,就决定去哪个城市。分别用Runnable接口和Thread类实现。
    Cookie的简易用法
    工作任务:题目一:网页输出九九乘法表;题目二:网页输出三角形和菱形
    简单的sql注入
    10-18 Oracle 基础练习
  • 原文地址:https://www.cnblogs.com/wqff-biubiu/p/12404095.html
Copyright © 2020-2023  润新知