关于java:从阿里规约看Spring事务

45次阅读

共计 14101 个字符,预计需要花费 36 分钟才能阅读完成。

指标:事务生效引发的劫难

如下图(张三 —> 李四转账)

tips

下订单 ——- 订单领取 —– 减库存(失败)

超卖景象

代码回顾:

// 实现类
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Resource
    private LogService logService;
    
    
    @Override
    @Transactional
//    @Transactional(rollbackFor = Exception.class)
    public void insert() throws Exception {method1_Test();
    }




// 模仿转账 @Transactional
    private  void  method1_Test() throws Exception {System.out.println(">>>>>>>>>>> 进入到业务办法");
        User user = new User();
        user.setName("张三");
        userMapper.insertUser(user);// 张三扣减 500 元
        addPayment();// 模仿李四减少 500 元(查看异样)}
    
    
   //FileNotFoundException extends IOException
    private void addPayment() throws FileNotFoundException {FileInputStream in = new FileInputStream("a.txt");// 模仿查看异样


    }
    
}

 ...... 略

如果说你从从事务办法中抛出的是查看异样(io、sql),那么这个时候,Spring 将不能进行事务回滚。

是不是很恐怖呢??

所以说,阿里规定
1、让查看异样也回滚:你就须要在整个办法前加上 @Transactional(rollbackFor=Exception.class)

2、让非查看异样不回滚:
须要退出 @Transactional(notRollbackFor=RunTimeException.class)

3、不须要事务管理(or 日志失落)
须要退出 @Transactional(propagation=Propagation.NOT_SUPPORTED)

课程目标总结

1、解决事务生效:通过源码学习如何让查看异样也回滚(or 运行异样不回滚);从源码角度深刻底层原理

2、解决无需事务管制;查问 or 日志记录;通过流传属性如何管制;底层是如何实现的

3、失常的事务执行流程在源码中是如何实现的

1.1 Spring 事务总体介绍

在 Spring 中,事务有两种实现形式:

  1. 编程式事务管理: 编程式事务管理应用 TransactionTemplate 可实现更细粒度的事务管制。
  2. 申明式事务管理: 基于 Spring AOP 实现。

    其本质是对办法前后进行拦挡,而后在指标办法开始之前创立或者退出一个事务,在执行完指标办法之后依据执行状况提交或者回滚事务。

申明式事务益处:

申明式事务管理不须要入侵代码,通过 @Transactional 就能够进行事务操作,更快捷而且简略,且大部分业务都能够满足,举荐应用。

管是编程式事务还是申明式事务,最终调用的底层外围代码是统一的

1.1.1 编程式事务实现形式

编程式事务,Spring 曾经给咱们提供好了模板类 TransactionTemplate,能够很不便的应用,如下图

TransactionTemplate 全路径名是:org.springframework.transaction.support.TransactionTemplate。这是 spring 对事务的模板类

实现的接口

用来执行事务的回调办法,

public interface TransactionOperations {

    @Nullable
    <T> T execute(TransactionCallback<T> action) throws TransactionException;

}

InitializingBean 这个是典型的 spring bean 初始化流程中,用来在 bean 属性加载结束时执行的办法。

public interface InitializingBean {void afterPropertiesSet() throws Exception;

}

TransactionTemplate 的 2 个接口的 impl 办法做了什么?

afterPropertiesSet 如下

    //    只是校验了事务管理器不为空
@Override
    public void afterPropertiesSet() {if (this.transactionManager == null) {throw new IllegalArgumentException("Property'transactionManager'is required");
        }
    }

execute 办法如下

    public <T> T execute(TransactionCallback<T> action) throws TransactionException {Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
 
        if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
        } 
        else {//TODO  创立事务 (与申明事务调用同一个办法)
            TransactionStatus status = this.transactionManager.getTransaction(this);
            T result;
            try {// 2. 执行业务逻辑,这里就是用户自定义的业务代码。如果是没有返回值的,就是 doInTransactionWithoutResult()。result = action.doInTransaction(status);
            } catch (RuntimeException | Error ex) {
                // Transactional code threw application exception -> rollback
                // 利用运行时异样 / 谬误异样 -> 回滚,调用 AbstractPlatformTransactionManager 的 rollback(),事务提交回滚
                //TODO  回滚((与申明事务调用同一个办法)
                rollbackOnException(status, ex);
                throw ex;
            } catch (Throwable ex) {// 未知异样 -> 回滚,调用 AbstractPlatformTransactionManager 的 rollback(),事务提交回滚
                // Transactional code threw unexpected exception -> rollback
                rollbackOnException(status, ex);
                throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
            }
            // TODO  事务提交 (与申明事务调用同一个办法)
            this.transactionManager.commit(status);
            return result;
        }
    }

总结

事务模板 TransactionTemplateI 外面的 execute 办法【创立事务】【提交事务】【回滚事务】和申明式事务调用的都是同一个底层办法

1.1.2 申明式事务实现形式

申明式事务的用法

@Transactional 注解能够加在类或办法上

1、类:在类上时是对该类的所有 public 办法开启事务。

2、办法:加在办法上时也是只对 public 办法起作用。

留神

@Transactional 注解也能够加在接口上,但只有在设置了基于接口的代理时才会失效,因为注解不能继承。所以该注解最好是加在类的实现上。

1.2 不容忽视的异样体系

指标:Java 异样体系(面试常问)与 Spring 事务存在什么分割

论断:

Spring 事务默认只回滚 运行时异样和 Error

伪代码如下

@Transaction
public  void   insert(){m_insert();
 in_insert();// 只有运行时异样 &  Error  才能够回滚}

Thorwable 类是所有异样和谬误的超类,有两个子类 Error 和 Exception,别离示意谬误和异样。

异样类 Exception 又分为运行时异样 (RuntimeException) 和非运行时异样,这两种异样有很大的区别,也称之为不查看异样(Unchecked Exception)和查看异样(Checked Exception)。

1、Error 与 Exception

Error 是程序无奈解决的谬误,比方 OutOfMemoryError、ThreadDeath 等。这些异样产生时,Java 虚拟机(JVM)个别会抉择线程终止。

Exception 是程序自身能够解决的异样,这种异样分两大类运行时异样和非运行时异样。程序中该当尽可能去解决这些异样。

2、运行时异样和非运行时异样

运行时异样都是 RuntimeException 类及其子类异样,如 NullPointerException、IndexOutOfBoundsException 等,这些异样是不查看异样,程序中能够抉择捕捉解决,也能够不解决。

这些异样个别是由程序逻辑谬误引起的,程序应该从逻辑角度尽可能防止这类异样的产生。

非运行时异样是 RuntimeException 以外的异样,类型上都属于 Exception 类及其子类。从程序语法角度讲是必须进行解决的异样,如果不解决,程序就不能编译通过。如 IOException、SQLException 等以及用户自定义的 Exception 异样,个别状况下不自定义查看异样。

1.3 事务流传行为与隔离级别

1.3.1 事务流传行为

什么是事务流传

事务流传用来形容由某一个事务流传行为润饰的办法被嵌套进另一个办法的时事务如何流传。

tips

两大类【有事务的状况】【没事务的状况】

代码了解概念

@Transactional 
    private void method5_Test() throws Exception {System.out.println(">>>>>>>>>>> 进入到业务办法");
        User user = new User();
        user.setName("张三");
        userMapper.insertUser(user);
        logService.insert(getLogEntity());
        throw new RuntimeException();}



    .............LogService.java
        
     @Override
//    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @Transactional
    public void insert(Log log) throws Exception {System.out.println(">>>>>>>>>>> 进入到日志办法");
//        Log log = new Log();
//        log.setName("业务日志记录");
        logMapper.insertLog(log);
    }

1.3.2 事务隔离级别

什么是事务的隔离

5 大类

隔离性是指多个用户的并发事务拜访同一个数据库时,一个用户的事务不应该被其余用户的事务烦扰,多个并发事务之间要互相隔

隔离级别 阐明
TransactionDefinition.ISOLATION_DEFAULT(默认) PlatformTransactionManager 的默认隔离级别,应用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别绝对应。
TransactionDefinition.ISOLATION_READ_UNCOMMITTED(读未提交) 这是事务最低的隔离级别,它容许另外一个事务能够看到这个事务未提交的数据。这种隔离级别会产生脏读,不可反复读和幻像读
TransactionDefinition.ISOLATION_READ_COMMITTED(读已提交) 保障一个事务批改的数据提交后能力被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别能够防止脏读呈现,然而可能会呈现不可反复读和幻像读。
TransactionDefinition.ISOLATION_REPEATABLE_READ(可反复读) 这种事务隔离级别能够避免脏读、不可反复读,然而可能呈现幻像读。它除了保障一个事务不能读取另一个事务未提交的数据外,还保障了不可反复读
TransactionDefinition. ISOLATION_SERIALIZABLE(串行化) 代价最大、可靠性最高的隔离级别,所有的事务都是按程序一个接一个地执行

如果不思考隔离性,会产生什么事呢?

脏读:

脏读是指一个事务在解决数据的过程中,读取到另一个为提交事务的数据

不可反复读:

不可反复读是指对于数据库中的某个数据,一个事务范畴内的屡次查问却返回了不同的后果,这是因为在查问过程中,数据被另外一个事务批改并提交了。

幻读

幻读是事务非独立执行时产生的一种景象。例如事务 T1 对一个表中所有的行的某个数据项做了从“1”批改为“2”的操作,这时事务 T2 又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务 T1 的用户如果再查看刚刚批改的数据,会发现还有一行没有批改,其实这行是从事务 T2 中增加的,就如同产生幻觉一样,这就是产生了幻读

1. 读未提交(Read uncommitted):

这种事务隔离级别下,select 语句不加锁。

此时,可能读取到不统一的数据,即“读脏”。这是并发最高,一致性最差的隔离级别。

2. 读已提交(Read committed):

可防止 脏读 的产生。

在互联网大数据量,高并发量的场景下,简直 不会应用 上述两种隔离级别。

3. 可反复读(Repeatable read):

MySql 默认隔离级别。

可防止 脏读 不可反复读 的产生。

4. 串行化(Serializable):

可防止 脏读、不可反复读、幻读 的产生

1.4 Spring 事务源码深度分析

1.4.1 Spring 事务环境介绍

指标:事务测试环境介绍

1.4.2 事务是何时被织入的

指标:事务在 Spring 哪个阶段织入的

思考:

程序在运行的时候【userService】为什么是代理对象

须要解决的问题:

1、代理对象是如何生成的

2、代理对象如何调用到了 invoke 办法

1.4.3 事务源码入口在哪里

1)事务三大接口介绍

指标:理解十分外围的事务三大接口

Spring 事务三大接口介绍

1、PlatformTransactionManager: (平台)事务管理器接口

PlatformTransactionManager 接口是 Spring 提供的平台事务管理器顶级接口,用于治理事务。

Spring 并不间接治理事务,而是提供了多种事务管理器;他们将事务管理的职责委托给 Hibernate 或者 JTA 等长久化机制的事务框架来实现

通过这个接口,Spring 为各个平台,比方 JDBC 等都提供了对应的事务管理器;然而具体实现就是上游事务框架了

public interface PlatformTransactionManager {

TransactionStatus getTransaction(TransactionDefinition definition):用于获取事务状态信息。void commit(TransactionStatus status):用于提交事务。void rollback(TransactionStatus status):用于回滚事务。}

该接口中提供了三个事务操作方法,具体如下。

  • TransactionStatus getTransaction(TransactionDefinition definition):用于获取事务状态信息。
  • void commit(TransactionStatus status):用于提交事务。
  • void rollback(TransactionStatus status):用于回滚事务。

上面是 PlatformTransactionManager 各种实现

2、TransactionDefinition: 事务定义信息接口

比方:事务流传行为、隔离级别、超时、只读、回滚规定)

// 7 大流传 + 5 大隔离 + 超时、只读、回滚
public interface TransactionDefinition {

int PROPAGATION_REQUIRED = 0;// 默认:如果存在一个事务,则反对以后事务。如果没有事务则开启一个新的事务


..... 略

}

3、TransactionStatus: 事务运行状态接口

public interface TransactionStatus extends SavepointManager, Flushable {boolean isNewTransaction();// 获取是否是新事务
    
    
    .... 略


}

TransactionStatus 接口是事务的状态,它形容了某一时间点上事务的状态信息。其中蕴含六个操作

名称 阐明
void flush() 刷新事务
boolean hasSavepoint() 获取是否存在保留点
boolean isCompleted() 获取事务是否实现
boolean isNewTransaction() 获取是否是新事务
boolean isRollbackOnly() 获取是否回滚
void setRollbackOnly() 设置事务回滚

2)事务源码调用入口

指标:找到 Spring 事务的调用入口

tips

入口拦截器 org.springframework.transaction.interceptor.TransactionInterceptor#invoke

TransactionInterceptor 源码如下

    // 获取指标类
    @Override
    @Nullable
    public Object invoke(final 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);

        // 开始调用父类办法
        return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
    }

Transactional 注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {@AliasFor("transactionManager")
    String value() default "";
    @AliasFor("value")
    String transactionManager() default "";
    //    流传行为
// 一个开启了事务的办法 A,调用了另一个开启了事务的办法 B,此时会呈现什么状况?这就要看流传行为的设置了
    Propagation propagation() default Propagation.REQUIRED;
    //isolation 属性是用来设置事务的隔离级别,数据库有四种隔离级别:// 读未提交、读已提交、可反复读、可串行化。MySQL 的默认隔离级别是可反复读
    Isolation isolation() default Isolation.DEFAULT;
    //timtout 是用来设置事务的超时工夫,能够看到默认为 -1,不会超时。int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
    //​ readOnly 属性用来设置该属性是否是只读事务,只读事务要从两方面来了解:// 它的性能是设置了只读事务后在整个事务的过程中,其余事务提交的内容对以后事务是不可见的
//    只读事务中只能有读操作,不能含有写操作,否则会报错
    boolean readOnly() default false;
    // 当办法内抛出指定的异样时,进行事务回滚。默认状况下只对 RuntimeException 回滚。Class<? extends Throwable>[] rollbackFor() default {};
    String[] rollbackForClassName() default {};
//     用来设置呈现指定的异样时,不进行回滚。Class<? extends Throwable>[] noRollbackFor() default {};
    String[] noRollbackForClassName() default {};

1.4.4 Spring 事务源码深刻分析

1)事务失常执行流程

指标:

1、失常流程测试(不抛运行时异样)

2、有运行时异样的状况

测试代码

// 实现类
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
    @Override
    @Transactional
    public void insert() throws Exception {method2_Test();// 失常状况下执行
    method3_Test() ;// 有运行时异样的状况}
}

找到 Spring 事务拦截器入口

org.springframework.transaction.interceptor.TransactionInterceptor#invoke

    // 获取指标类
    @Override
    @Nullable
    public Object invoke(final 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);

        // 开始调用父类办法
        return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
    }

进入到 invokeWithinTransaction

tips:具体流程

1、获取事务属性

2、创立事务

3、调用指标办法

4、回滚 事务 or 提交事务

//    蕴含了事务执行的整个流程,这里是应用了模板模式,具体的实现交给子类去实现
    @Nullable
    protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
                                             final InvocationCallback invocation) throws Throwable {

        // 获取事务属性, 如果事务属性为空,则没有事务
        TransactionAttributeSource tas = getTransactionAttributeSource();
        // 继承 TransactionDefinition
        final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        // 获取实现:DataSourceTransactionManager 治理 JDBC 的 Connection。final PlatformTransactionManager tm = determineTransactionManager(txAttr);
        // 切点标识 -->com.tx.test.impl.UserServiceImpl.insert
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

        if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {// TODO: 创立 (开启) 事务(依据事务的流传行为属性去判断是否创立一个事务)TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
            Object retVal = null;
            try {
                // TODO: 调用指标办法
                retVal = invocation.proceedWithInvocation();} catch (Throwable ex) {
                // TODO: 回滚事务  指标办法调用产生了异样(后置加强),completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            } finally {
                // 清理信息
                cleanupTransactionInfo(txInfo);
            }
            //TODO: 提交事务
            commitTransactionAfterReturning(txInfo);
            return retVal;
            
            
            ............. 略

TransactionAttribute 继承了 TransactionDefinition

提交

    protected void doCommit(DefaultTransactionStatus status) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        if (status.isDebug()) {logger.debug("Committing JDBC transaction on Connection [" + con + "]");
        }
        try {
            //TODO :jdbc 链接提交事务
            con.commit();}
        catch (SQLException ex) {throw new TransactionSystemException("Could not commit JDBC transaction", ex);
        }
    }

回滚

protected void doRollback(DefaultTransactionStatus status) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        if (status.isDebug()) {logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
        }
        try {
            //TODO  jdbc 回滚事务
            con.rollback();}
        catch (SQLException ex) {throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
        }
    }

总结

1、获取事务属性

2、创立事务

1、获取事务管理器 DataSourceTransactionManager(通过数据源、拿到链接、在设置事务管理器)2、判断是否存在事务,如果有就去判断流传属性!!!(第一次不存在)
3、不存在事务的状况
   如果事务超时工夫小于默认(-1)或者没有事务,则抛出异样
   新建一个事务(doBegin 开启事务)留神:第一次进入则是新建一个事务

3、调用指标办法

4、回滚 事务 or 提交事务

提交事务
1、调用 doCommit 提交事务
   通过事务管理器对象 DataSourceTransactionObject 拿到 con 执行 con.commit()
   
   
回滚事务
1、调用 doRollback 回滚
    通过事务管理器对象 DataSourceTransactionObject 拿到 con 执行 con.rollback()

2)生产事务生效之谜

指标:

1、事务生效的起因是什么

2、如何解决事务生效

3、在源码中什么中央判断的

测试代码

    @Override
    @Transactional
    public void insert() throws Exception {method4_Test();// 事务生效
 
    }

解决方案

@Transactional(rollbackFor = Exception.class)

重点关注源码

org.springframework.transaction.interceptor.TransactionAspectSupport#completeTransactionAfterThrowing 中的 rollbackOn 办法

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {if (txInfo != null && txInfo.getTransactionStatus() != null) {if (logger.isTraceEnabled()) {logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
                        "] after exception:" + ex);
            }
            //TODO rollbackOn 获取回滚规定; 能够自定义设置回滚规定,默认会判断 RuntimeException 和 Error,!!!!!!!if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
                try {
                    // TODO 回滚事务
                    txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
                } catch (TransactionSystemException ex2) {................ 略

3)线上日志数据失落

指标:通过流传属性解决业务中日志失落问题

测试代码入口

    public static void main(String[] args) {ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:application.xml");
        UserService userService = (UserService) context.getBean("userService");
//        LogService logService = (LogService) context.getBean("logService");

        try {userService.insert();// 用户
//            logService.insert();// 日志} catch (Exception e) {e.printStackTrace();
        }
    }
}

日志插入插入失败

应用默认的流传属性和隔离级别

// 实现类
public class LogServiceImpl implements LogService {
    @Autowired
    private LogMapper logMapper;
    @Override
//    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    @Transactional
    public void insert(Log log) throws Exception {System.out.println(">>>>>>>>>>> 进入到日志办法");
//        Log log = new Log();
//        log.setName("业务日志记录");
        logMapper.insertLog(log);
    }

tips

@Transactional(propagation = Propagation.NOT_SUPPORTED)

这样批改则批改胜利

用户信息插入,调用日志插入(

    @Override
    @Transactional
//    @Transactional(rollbackFor = Exception.class)
    public void insert() throws Exception {method5_Test();
    }

总结

1、景象

​ 用户插入的时候;调用 log 的 service 插入;如果出现异常;两者全副回滚

​ 也就是用户插入失败、日志插入失败

2、需要

​ 失常的业务状况;都是在事务失败的时候;同时会要求日志也要插入胜利

3、过程

​ 目前;用户和日志的 service 应用的都是默认的流传属性和隔离级别

4、改良

​ 将日志的流传属性批改成 Propagation.NOT_SUPPORTED【如果以后存在事务;就挂起以后事务】

如果本文对您有帮忙,欢送 关注 点赞`,您的反对是我保持创作的能源。

转载请注明出处!

正文完
 0