2

Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题 - 程序员xiaozh...

 1 year ago
source link: https://www.cnblogs.com/scott1102/p/17133611.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Spring事务(Transaction)管理高级篇一栈式解决开发中遇到的事务问题

Spring是目前Java开发中最流行的框架了,它的事务管理我们在开发中常常的用到,但是很多人不理解它事务的原理,导致开发中遇到事务方面的问题往往都要用很长的时间才能解决,下面就带着大家去深入了解Spring的事务,然后文章的最后还会给出开发中常常遇到的问题以及解决方案。

如果单纯的用Spring框架进行开发(PS使用注解开发,不是XML配置的方式)。那么要使用Spring事物我们首先要加的就是Spring的这个【EnableTransactionManagement】注解(PS如果直接使用了Spingboot框架了,它已经使用自动配置相关的原理自动加了这个注解了)。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

上面注解的主要作用就是帮我们Import了TransactionManagementConfigurationSelector这个类,它的主要功能如下:

2591839-20230218204336490-864180352.png

这个类目前就是帮我们在Spring中注入了ProxyTransactionManagementConfiguration,大家一看名称就知道这是一个配置类(配置类一般都是帮我们注入各种需要的Bean)如下它帮我们做的事情。

  @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
    BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
    advisor.setTransactionAttributeSource(transactionAttributeSource());
    advisor.setAdvice(transactionInterceptor());
    return advisor;
  }

  @Bean
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  public TransactionAttributeSource transactionAttributeSource() {
    return new AnnotationTransactionAttributeSource();
  }

  @Bean
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  public TransactionInterceptor transactionInterceptor() {
    TransactionInterceptor interceptor = new TransactionInterceptor();
    interceptor.setTransactionAttributeSource(transactionAttributeSource());
    return interceptor;
  }

上面的代码可以看出它帮我们做了三件事,1:导入了一个增强器。2:导入了一个事务属性的资源。3:导入一个事务的拦截器。这三个类后面讲事务的时候都会用到。

如果方法上加了事物注解的类,Spring会创建它的一个代理类放到容器中,如果没加注解(PS本文只考虑事物注解不考虑其他的)那么Spring就会把它原始的对象放到容器中这个很重要,后面总结事物为什么会失效。(下面第一张图没加Transaction注解的对象,第二张图是加了的,然后Sping就把它转换为代理对象,具体怎么转换的是AOP相关功能,AOP很多功能点本文不做具体讲解)【上面说的一段话很是重要】。

 
2591839-20230218204517771-1307796025.png
2591839-20230218204557241-1200222095.png

重点来了,下面是我UserService中的主要的方法,很简单如下:

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao ;
    @Autowired
    private ApplicationContext applicationContext ;
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void addUser(User user) {
        user.setPersonName("xiaozhang");
        userDao.insert(user) ;
        user.setPersonName("xiaozhang02");
        UserService userService = applicationContext.getBean(UserService.class);
        userService.addUser02(user);
        int i= 1/0 ;  //让程序抛出异常

    }
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void addUser02(User user02) {
        userDao.insert(user02) ;
    }
}

然后就开始带着大家看相关的源码,首先打断点会进入到如下的方法(你如果想尝试就在这个类【TransactionAspectSupport】的如下方法中打一个断点)。

 
2591839-20230218204717874-2124238793.png

分析上面代码主要功能。

--1:这段代码是获取事务的属性,上面配置中配置了
final TransactionAttribute txAttr = (tas != null ? 
tas.getTransactionAttribute(method, targetClass) : null);
--2:决定使用那个事务管理器
final PlatformTransactionManager tm = 
determineTransactionManager(txAttr);
--3:找方法的切入点也就是加了事务注解的方法
final String joinpointIdentification = 
methodIdentification(method, targetClass, txAttr);

上面图中的三个方法就是注释中写的作用,后面就是事务的重点了,然后会走下面的方法。

2591839-20230218204843179-1866172250.png
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
  @Nullable TransactionAttribute txAttr,  final String joinpointIdentification) {
    TransactionStatus status = null;
    if (txAttr != null) {
      if (tm != null) {
        status = tm.getTransaction(txAttr); //这个重点方法
      }

tm.getTransaction(txAttr),这个方法会调用如下方法:

//AbstractPlatformTransactionManager这个类的方法
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
  //获取事务管理器,第一次获取的为空
    Object transaction = doGetTransaction();
   //看看是否存在事务,第一次不存在。第二次会进入到这个方法,后面会重点讲。
    if (isExistingTransaction(transaction)) {
      // Existing transaction found -> check propagation behavior to find out how to behave.
      return handleExistingTransaction(definition, transaction, debugEnabled);
    }
    //我们addUser的方法事务传播行为是PROPAGATION_REQUIRED,所以走这个分支
    else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
        definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
        definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
      //挂起事务,后面PROPAGATION_REQUIRES_NEW这个方法很重要
      SuspendedResourcesHolder suspendedResources = suspend(null);
      try {
        boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
       //创建一个事务状态
        DefaultTransactionStatus status = newTransactionStatus(
            definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
       //重点来了,开启事务
        doBegin(transaction, definition);
        prepareSynchronization(status, definition);
        return status;
      }

开启事务方法(doBegin)(这个方法做了如下6件很重要的事情)

protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
   //1:获取事务连接,我们写JDBC的时候也是需要自己获取事务连接的
    Connection con = null;
      if (!txObject.hasConnectionHolder() ||
          txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
        Connection newCon = obtainDataSource().getConnection();
        txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
      }
      //2:设置数据库的隔离级别
      Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
      txObject.setPreviousIsolationLevel(previousIsolationLevel);
       //3:关闭数据库的自动提交
      // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
      // so we don't want to do it unnecessarily (for example if we've explicitly
      // configured the connection pool to set it already).
      if (con.getAutoCommit()) {
        txObject.setMustRestoreAutoCommit(true);
        con.setAutoCommit(false);
      }
      prepareTransactionalConnection(con, definition);
      // 4:激活事务
      txObject.getConnectionHolder().setTransactionActive(true);
      // 5:设置超时的时间
      int timeout = determineTimeout(definition);
      if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
        txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
      }
       //6:绑定数据源和连接持有器。
      // Bind the connection holder to the thread.
      if (txObject.isNewConnectionHolder()) {
        TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
      }
    }

上面方法完成后,开始执行下面这个方法:

 try {
        这个方法的主要意思是去执行目标方法,也就是(addUser)
        // 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();
      }

去执行目标方法(addUser)后,就又回到前面那个第一次进入Spring源码中的方法(因为从上面的代码可知我们的addUser方法又调用了其他事务方法)如下图:

 
2591839-20230218205211328-1454452014.png

注意此时的addUser()方法还没执行完,又进入同样的方法如下:

2591839-20230218205256126-1965284641.png

上面很多重复的就不讲了,前面由于已经存在了事务所以会进入如下方法。

//前面说要重点说的
private TransactionStatus handleExistingTransaction(
      TransactionDefinition definition, Object transaction, boolean debugEnabled)
      throws TransactionException {
  //不允许有事务就抛出异常 
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
      throw new IllegalTransactionStateException(
          "Existing transaction found for transaction marked with propagation 'never'");
    }
    //重点也是我们开发中常用的事务传播行为(新建一个事务) 
    // PROPAGATION_REQUIRES_NEW
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
      //先把旧的事务挂起,并且缓存起来,
      // 怎么缓存旧的事务的自己可走进去看一下,因为前面事务还没提交,还需要用
      SuspendedResourcesHolder suspendedResources = suspend(transaction);
      try {
        boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
        DefaultTransactionStatus status = newTransactionStatus(
            definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
       //又开启新的事务了,和上面讲过的同样的套路了
        doBegin(transaction, definition);
        prepareSynchronization(status, definition);
        return status;
      }

后面就和前面一样了流程。最后数据库结果如下(PS忽略我的数据库表设计,只是为了简单,用的Mysql数据库也是为了自己电脑方便安装)。

 
2591839-20230218205412642-2112822779.png

结果addUser插入失败了,addUser02插入成功了。(这也就是事务传播属性,Propagation.REQUIRED和Propagation.REQUIRES_NEW的区别)。

好了我们进行总结在工作中事务方面的问题。

1:事务注解使用不当,造成事务不生效(PS只举列开发中常用的)。

1.1 :没有用代理对象调用,直接用原始对象(文章开始说了加了事务注解的会生成一个代理对象),如下的使用事务会失效(也是大多数开发者常犯的错误)。

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao ;
    @Autowired
    private ApplicationContext applicationContext ;
    @Override
    public void addUser(User user) {
        user.setPersonName("xiaozhang");
        userDao.insert(user) ;
        user.setPersonName("xiaozhang03");
        //用this没有AOP代理对象,造成事务失效,2个user都会插入数据库
        this.addUser02(user) ;
    }
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void addUser02(User user02) {
        userDao.insert(user02) ;
        int i= 1/0 ;  //让程序抛出异常
    }
}

1.2 rollbackFor = Exception.class如果不加,Spring只回滚运行时候的异常和程序中的Error(这是很细节的代码) 。

2591839-20230218205535036-1261166480.png

上面2个问题解决方案大家看完也就很明白怎样解决了。

2:事务嵌套使用不当导致事务都回滚了,如下有时候会写下面的业务。

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao ;
    @Autowired
    private ApplicationContext applicationContext ;
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void addUser(User user) {
        user.setPersonName("xiaozhang");
        userDao.insert(user) ;
        user.setPersonName("xiaozhang02");
        UserServiceImpl userService = (UserServiceImpl)applicationContext.getBean(UserService.class);
        try {
            userService.addUser02(user);
        }catch (Exception e){
            System.out.println(e);
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    public void addUser02(User user02) {
        userDao.insert(user02) ;
        int i= 1/0 ;  //让程序抛出异常
    }
}

很多时候我们一看代码也没什么问题addUser()这个方法会新增数据的,因为把addUser02()方法抛出的异常捕获了。开发的时候(PS开发的时候调用的其他服务的业务方法,我为了演示简单这样写了)我遇到这方面的问题找了很久才找到原因。会抛出如下异常:

2591839-20230218205738036-1029948817.png

然后我们拿Spring源码分析,上面写的两个事务传播属性都是propagation = Propagation.REQUIRED (PS如果存在事务就使用原来的事务,不存在就新启一个事务)。

简单的说一下为什么会出现上面的原因,addUser02()这个方法抛出异常了它会把一个全局的rollback-only变量标记为true(默认为false)。由于它和addUser()方法共用事务,所以后面这个方法事务提交的时候会检查这个变量,如果看到这个变量为true。就把它自己也回滚了。如下:

2591839-20230218205821869-2010433779.png

下面addUser()方法要进行提交事务了如下:

2591839-20230218205901780-1281900091.png

然后会进入AbstractPlatformTransactionManager这个类的commit方法如下图。

 
2591839-20230218205950023-584268259.png

然后会进入processRollback这个方法,为什么会进入这个方法还是因为rollback-only这个变量上一步设置为true了。(图片中可以看到是那个类的这个方法,自己Debug打断点的时候可参考),这个就是我们前面图中抛出的异常(Transaction rolled back because it has been marked as rollback-only)。

 private void processRollback(DefaultTransactionStatus status, boolean unexpected){
  try {
    //unexpected 为true 。
      boolean unexpectedRollback = unexpected;
          // Unexpected rollback only matters here if we're asked to fail early
         //这个方法不会进入
          if (!isFailEarlyOnGlobalRollbackOnly()) { 
            unexpectedRollback = false;
          }
        }
      }
      triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
      // Raise UnexpectedRollbackException if we had a global rollback-only marker
      // unexpectedRollback 为true 就会抛出开始说的那个异常。
      if (unexpectedRollback) {
        throw new UnexpectedRollbackException(
            "Transaction rolled back because it has been marked as rollback-only");
      }
    }

遇到上面的这个问题怎么解决的?

2591839-20230218210114515-1208372995.png

1:addUser02()这个方法新建一个事物也就是使用事物传播属性propagation = Propagation.REQUIRES_NEW(PS如果你写的业务逻辑可以这样做)。

2:不用Transaction这个事物注解,事物的提交和回滚自己控制,Sping提供的有事物手动提交和回滚的方法,自己可以查找一下。

文章中很多地方我都标注了那个类的那个方法,跟着上面一步步看完源码,事物方面开发中如果再遇到问题应该很快就能解决,事物那些传播属性也很快就能理解,开发中用嵌套事物也不必太担心有Bug了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK