关于spring:Spring系列之事物是如何管理的

前言

咱们都晓得Spring给咱们提供了很多形象,比方咱们在操作数据库的过程中,它为咱们提供了事物方面的形象,让咱们能够十分不便的以事物形式操作数据库。不论你用JDBC、Mybatis、Hibernate等任何一种形式操作数据库,也不论你应用DataSource还是JTA的事物,Spring事物形象治理都能很好的把他对立在一起。接下来看一下事物的形象外围接口

Spring事务形象

PlatformTransactionManager是事物管理器接口

//事务管理器接口有以下几个接口,获取事物信息,提交和回滚
public interface PlatformTransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;

    void commit(TransactionStatus var1) throws TransactionException;

    void rollback(TransactionStatus var1) throws TransactionException;
}

常见的事物管理器有以下几种:

  • DataSourceTransactionManager
  • HibernateTransactionManager
  • JtaTransactionManager
    这些管理器都实现了PlatformTransactionManager中的三个接口,实现逻辑略有差异,然而对用户来讲区别不大

定义事物的一些参数:
一些事物的参数在TransactionDefinition.java中,详情如下:

public interface TransactionDefinition {
      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;
        //默认隔离级别,和数据库的隔离级别统一
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
        //默认不超时
    int TIMEOUT_DEFAULT = -1;
}

上面两张图对这些参数进行了阐明:
7种事务流传个性:

四种事务隔离级别:
在看事务隔离级别前须要先理解下什么是脏读、不可反复读、幻读
脏读: 脏读就是一个事物未提交的数据,被另外一个事物读到了,显然这种状况不可承受
不可反复读: 不可反复读是指在一个事务内,屡次读同一数据,前后读取的后果不统一。
幻读: 事务A对表中的一个数据进行了批改,这种批改波及到表中的全副数据行。同时事务B也批改了这个表中的数据,这种批改是向表中插入一行新数据。那么就会产生操作事务A的用户发现表中还存在没有批改的数据行,就如同产生了幻觉一样
晓得了以上几个概念,咱们来看看隔离级别:

这里咱们能够看到,Spring并不是提供了所有的事物治理的实现,而是提供了规范的事物管理器的操作接口PlatformTransactionManager, 并且标准了其行为,具体的事物实现由各个平台自行实现。这就是Spring的事物形象。

Spring之编程式事物

Spring提供了TransactionTemplate工具类能够很不便的应用编程式事务。默认状况下TransactionTemplate应用的是DataSourceTransactionManager。
在Spring上下文中,咱们不配置TransactionTemplate这个bean,也能获取到TransactionTemplate。比方上面的例子。

@Service
public class UserInfoService {

    @Resource
    private UserInfoDAO userInfoDAO;
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void updateUser1(){
        transactionTemplate.execute(transactionStatus -> {
            userInfoDAO.updateUserName(1,"zhangsanfeng");
            transactionTemplate.execute(transactionStatus2 -> {
                userInfoDAO.updateUserName(2,"lisi");
                return null;
            });
            return null;
        });
    }
}

因为Spring默认的事物流传个性是PROPAGATION_REQUIRED,咱们来做一下验证,看是不是这样


下面两幅图能够看出,TransactionStatus中的newTransaction属性,第一个是true,第二个是false,正好合乎PROPAGATION_REQUIRED所形容的状况。其余的流传个性能够本人去验证。

申明式事物

除了编程式事物外,Spring还为咱们提供了申明式事物。应用@Transactional注解。
@Transactional 能够作用于接口、接口办法、类以及类办法上。当作用于类上时,该类的所有 public 办法将都具备该类型的事务属性,同时,咱们也能够在办法级别应用该注解来笼罩类级别的定义。

尽管 @Transactional 注解能够作用于接口、接口办法、类以及类办法上,然而 Spring 倡议不要在接口或者接口办法上应用该注解,因为这只有在应用基于接口的代理时它才会失效。另外, @Transactional 注解应该只被利用到 public 办法上,这是由 Spring AOP 的实质决定的。如果你在 protected、private 或者默认可见性的办法上应用 @Transactional 注解,这将被疏忽,也不会抛出任何异样。

@Transactional的rollbackFor属性能够设置一个 Throwable 的数组,用来表明如果办法抛出这些异样,则进行事务回滚。默认状况下如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚。
上面的代码事物就不会失效:

    @Transactional
    public void updateUser2() throws Exception {
        int r1 = userInfoDAO.updateUserName(1,"wanger");
        int r2 = userInfoDAO.updateUserName(2,"mawu");
        if (r2==1){
            throw new Exception();
        }
    }

如果咱们把抛出的异样改成RuntimeException,这时候事物就会失效了。或者指定异样让事物失效,比方 @Transactional(rollbackFor = Exception.class),这样碰到所有的异样事物都会失效了。

为什么加了@Transactional注解事物就失效了?

这是因为Spring容器会为加了这个注解的对象生成一个代理对象,理论调用的时候,实际上是调用的代理对象。 代理对象的实现了AOP的加强,实现了事物的实现。

通过注解怎么实现指定的流传个性和隔离级别的?

public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    String[] label() default {};

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    String timeoutString() default "";

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

代码中能够看出,咱们能够指定隔离级别和流传个性,在Spring为咱们生成代理类的时候,会读取这些属性,体现在加强逻辑中。

事物生效的8种状况及解决办法

数据库引擎不反对事务

这里以 MySQL 为例,其 MyISAM 引擎是不反对事务操作的,InnoDB 才是反对事务的引擎,个别要反对事务都会应用 InnoDB,这时候抉择反对事物的数据库即可(如同是废话,哈哈哈)

没有被 Spring 治理

这个如同没什么可说的,脱离了Spring的治理,还谈什么Spring事物治理。

办法不是 public 的

@Transactional 只能用于 public 的办法上,否则事务不会生效,如果要用在非 public 办法上,能够开启 AspectJ 代理模式。

数据源没有配置事务管理器

相当于没开启事务管理,如果不是Springboot状况须要进行如下操作。

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

如果是SpringBoot,在启动类上间接加上注解@EnableTransactionManagement即可。

流传个性配错了

流传个性配置成,Propagation.NOT_SUPPORTED或者Propagation.NOT_SUPPORTED,改成反对事物的流传个性即可。

异样类型谬误

因为默认的异样类型是运行时异样,如果抛出了其余异样就不失效。
解决形式:
1、将异样改成运行时异样
2、指定异样进行事物回滚,如:@Transactional(rollbackFor = Exception.class)

异样被吃掉了

如果你代码这么写,事物不失效:

    @Transactional(rollbackFor = Exception.class)
    public void updateUser2() {
        int r1 = userInfoDAO.updateUserName(1,"3");
        int r2 = userInfoDAO.updateUserName(2,"4");
        if (r2==1){
            throw new RuntimeException();
        }
        try {
            
        }catch (Exception e){
            
        }
    }

解决办法: 必须要抛出异样,否则Spring事务管理,不会走到回滚逻辑

类外部调用

@Service
public class UserInfoService {
    public void justUpdate(){
        updateUser2();
    }
    @Transactional(rollbackFor = Exception.class)
    public void updateUser2() {

    }
}

上述代码不失效,因为外部调用不会波及到代理类的调用,更不会有AOP的加强,因而不会失效。
解决办法:
1、自注入

@Service
public class UserInfoService {
   @Autowired
    private UserInfoService userInfoService;
    public void justUpdate(){
        userInfoService.updateUser2();
    }
    @Transactional(rollbackFor = Exception.class)
    public void updateUser2() {

    }
}

2、Spring上下文

@Service
public class UserInfoService {
    ApplicationContext applicationContext;
    public void justUpdate(){
           UserInfoService userInfoService = (UserInfoService) applicationContext.getBean("userInfoService");
        userInfoService.updateUser2();
    }
    @Transactional(rollbackFor = Exception.class)
    public void updateUser2() {
    }
}

3、获取他的代理类,间接调用代理类

@Service
public class UserInfoService {
    public void justUpdate(){
           ((UserInfoService) AopContext.currentProxy()).updateUser2();
    }
    @Transactional(rollbackFor = Exception.class)
    public void updateUser2() {
    }
}

—————————-END—————————
更多Spring相干常识,请关注我,各平台都是同一个ID

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理