# Spring事务

Spring事务详解

什么是事务

  • 事务是逻辑上的一组操作,要么都执行,要么都不执行。
  • savePerson() 中就有两个原子性的数据库操作。这些原子性的数据库操作是有依赖的,它们要么都执行,要不就都不执行。
public void savePerson() {
    personDao.save(person);
    personDetailDao.save(personDetail);
}

事务能否生效数据库引擎是否支持事务是关键,MySQL 数据库默认使用支持事务的 innodb引擎

事务实际业务

  • 例如银行存钱的时候,假如你的钱已经进入了Atm中但是,后面你查询余额的时候发现,你的钱并没有存进去。
  • 假如你给某人转账,你的账户显示转账成功,但是对方并没有收到钱,这种重要的操作就应该考虑使用事务操作。

事务的特性(ACID)

  • 原子性(Atomicity): 一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
  • 一致性(Consistency): 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
  • 隔离性(Isolation): 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable)。
  • 持久性(Durability): 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

事务隔离级别

  • 面对不同的事务
  • 脏读:读到的数据是未确认的数据 ( 未提交 ) , 如果另一个客户端在操作过程中,回滚了操作,那么用户读到的数据其实是无效数据。
  • 不可重复读:指一个客户端在同一个事务中多次读取相同的数据 , 结果不一致
  • 幻读:一个客户端多次读取相同的数据,每次得到的结果都跟第一次得到的数据一样,但其实数据已经发生了变化.但是查不到结果。
隔离级别说明脏读不可重复读幻读
DEFAULT使用数据库的默认级别,Mysql(可重复读),Oracle(读已提交)
READ_UNCOMMITTED读未提交(脏读)最低的隔离级别
READ_COMMITTED读已提交,ORACLE默认隔离级别,有幻读以及不可重复读风险
REPEATABLE_READ可重复读,解决不可重复读的隔离级别,但还是有幻读风险
SERIALIZABLE最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了

常用数据库事务

数据库类型支持事务
Oracle关系型数据库
MySQL关系型数据库
Microsoft SQL Server关系型数据库
PostgreSQL关系型数据库
MongoDB文档数据库
IBM Db2系型数据库

Spring 对事务的支持

编程式事务管理

使用TransactionTemplate或者使用底层的PlatformTransactionManager

使用TransactionTemplate
@Autowired
private TransactionTemplate transactionTemplate;

public void testTransaction() {

        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {

                try {

                    // ....  业务代码
                } catch (Exception e){
                    //回滚
                    transactionStatus.setRollbackOnly();
                }

            }
        });
}
使用 TransactionManager
@Autowired
private PlatformTransactionManager transactionManager;

public void testTransaction() {

  TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
        // ....  业务代码
        transactionManager.commit(status);
    } catch (Exception e) {
        transactionManager.rollback(status);
    }
}

声明式事务管理

声明式事务建立在AOP之上,对方法前后进行拦截,然后方法开始之前创建或者加入一个事务,在执行完目标方法后更具执行情况提交或者回滚事务。声明式事务一种基于txaopxml配置文件,另一种是基于@Transactional注解。

Spring 事务管理接口

Spring 框架中,事务管理相关最重要的 3 个接口如下:

  • PlatformTransactionManager: (平台)事务管理器,Spring 事务策略的核心。
  • TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
  • TransactionStatus: 事务运行状态。

我们可以把 PlatformTransactionManager 接口可以被看作是事务上层的管理者,而 TransactionDefinitionTransactionStatus 这两个接口可以看作是事务的描述。

PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离级别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。

PlatformTransactionManager:事务管理接口

Spring 并不直接管理事务,而是提供了多种事务管理器Spring 事务管理器的接口是: PlatformTransactionManager

通过这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

PlatformTransactionManager 接口的具体实现如下:

请添加图片描述

PlatformTransactionManager接口中定义了三个方法:

package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface PlatformTransactionManager {
    //获得事务
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    //提交事务
    void commit(TransactionStatus var1) throws TransactionException;
    //回滚事务
    void rollback(TransactionStatus var1) throws TransactionException;
}
  • PlatformTransactionManager接口:将事务管理行为抽象出来,然后不同的平台去实现它,保证提供给外部的行为不变,方便扩展。

TransactionDefinition:事务属性

事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务,这个方法里面的参数是 TransactionDefinition 类 ,这个类就定义了一些基本的事务属性:事务的一些基本配置,描述了事务策略如何应用到方法上。

  • TransactionDefinition.java:接口中定义了 5 个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等
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;

    // 返回事务的传播行为,默认值为 REQUIRE
    default int getPropagationBehavior() {
        return 0;
    }
	//返回事务的隔离级别,默认值是 DEFAULT
    default int getIsolationLevel() {
        return -1;
    }
	// 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
    default int getTimeout() {
        return -1;
    }
	// 返回是否为只读事务,默认值为 false
    default boolean isReadOnly() {
        return false;
    }

    @Nullable
    default String getName() {
        return null;
    }

    static TransactionDefinition withDefaults() {
        return StaticTransactionDefinition.INSTANCE;
    }
}

TransactionStatus:事务状态

TransactionStatus接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息。

PlatformTransactionManager.getTransaction(…)方法返回一个 TransactionStatus 对象。

  • TransactionStatus.java
public interface TransactionStatus extends TransactionExecution{
    // 是否有恢复点
    boolean hasSavepoint();
}
public interface TransactionExecution {
	// 是否是新的事务
	boolean isNewTransaction();
	// 设置为只回滚
	void setRollbackOnly();
	// 是否为只回滚
	boolean isRollbackOnly();
	// 是否已完成
	boolean isCompleted();
}

事务传播机制

  • Spring 提供七种级别的事务传播机制,在开发中可以结合实际业务需要进行使用。
事务传播行为类型说明
REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。默认是这个级别。
SUPPORTS如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
MANDATORY如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起
NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER以非事务方式执行,如果当前存在事务,则抛出异常
NESTEDREQUIRES_NEW区别是,所有事务成功才提交,只要一个事务失败都回滚

@Transactional注解

  • 方法加上@Transactional(rollbackFor=Exception.class)肯定会回滚。
  • 如果@Transactional不加属性那么事务只有在遇到RuntimeException的时候才会回滚。
@Override
public boolean rollbackOn(Throwable ex) {
    return (ex instanceof RuntimeException || ex instanceof Error);
}

@Transactional 的作用范围

  • 方法 :推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。
  • :如果这个注解使用在类上的话,表明该注解对该类中所有的 public 方法都生效。
  • 接口 :不推荐在接口上使用。

@Transaction注解常用参数

参数名称功能描述
readOnly该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true)
rollbackFor该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
rollbackForClassName该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:指定单一异常类名称:@Transactional(rollbackForClassName="RuntimeException")指定多个异常类名称:@Transactional(rollbackForClassName={"RuntimeException","Exception"})
noRollbackFor该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})
noRollbackForClassName该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:指定单一异常类名称:@Transactional(noRollbackForClassName="RuntimeException")指定多个异常类名称:@Transactional(noRollbackForClassName={"RuntimeException","Exception"})
propagation该属性用于设置事务的传播行为
isolation该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置
timeout该属性用于设置事务的超时秒数,默认值为-1表示永不超时

@Transactional 事务注解原理

  • 基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    public DefaultAopProxyFactory() {
    }

    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (NativeDetector.inNativeImage() || !config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
            return new JdkDynamicAopProxy(config);
        } else {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
            } else {
                return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
            }
        }
    }
}
  • 如果一个类或者一个类中的 public 方法上被标注@Transactional 注解的话,Spring 容器就会在启动的时候为其创建一个代理类,在调用被@Transactional 注解的 public 方法的时候,实际调用的是,TransactionInterceptor 类中的 invoke()方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。

请添加图片描述

  • invokeWithinTransaction()

请添加图片描述

Spring AOP 自调用问题

  • 若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法,有@Transactional 注解的方法的事务会失效。
  • 这是由于Spring AOP代理的原因造成的,因为只有当 @Transactional 注解的方法在类以外被调用的时候,Spring 事务管理才生效。
// MyService 类中的method1()调用method2()就会导致method2()的事务失效。

@Service
public class MyService {

private void method1() {
     method2();
     //......
}
@Transactional
 public void method2() {
     //......
  }
}

@Transactional 的使用注意事项总结

  • @Transactional 注解只有作用到 public 方法上事务才生效,不推荐在接口上使用;
  • 避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效;
  • 正确的设置 @TransactionalrollbackForpropagation 属性,否则事务可能会回滚失败;
  • @Transactional 注解的方法所在的类必须被 Spring 管理,否则不生效;
  • 底层使用的数据库必须支持事务机制,否则不生效;

@Transactional 示例代码

try catch捕获异常

  • 在执行的过程中,异常被捕获了所以,无法进行回滚。
@RequestMapping("/test1")
@Transactional(rollbackFor = Exception.class)
public String test1() {
    try {
        Menu menu = new Menu();
        menu.setName("事务测试2");
        menu.setFlavor("事务测试2");
        menu.setPrice((double) 34);
        menu.setCount(23);
        menu.setId(2);
        menuRepository.update(menu);
        int a=10/0;
        return "sucess";
    } catch (Exception e) {
        e.printStackTrace();
        return "fail";
    }
}
  • 要想在try catch中也能回滚操作,只需要在catch中加上如下的语句。此时不管有没有异常都会进行回滚操作。
@RequestMapping("/test1")
@Transactional(rollbackFor = Exception.class)
public String test1() {
    try {
        menuRepository.update(menu);
        int a=10/0;
        return "sucess";
    } catch (Exception e) {
        e.printStackTrace();
        //只要抛异常则回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        return "fail";
    }
}

不捕获异常

  • @Transaction加上roolbackfor参数,会进行回滚操作。
@RequestMapping("/test2")
@ApiOperation(value = "事务回滚测试2", notes = "不捕获异常不抛出异常")
@Transactional(rollbackFor = Exception.class)
public String test2() {
    menuRepository.update(menu);
    int a = 10 / 0;
    return "sucess";
}
  • 抛出RuntimeException,事务进行回滚
@RequestMapping("/test3")
@ApiOperation(value = "事务回滚测试3", notes = "不加参数")
@Transactional
public String test3() {
    menuRepository.update(menu);
    int a = 10 / 0;
    throw new RuntimeException();
}
  • 抛出其他异常事务不回滚
@RequestMapping("/test3")
@Transactional
public String test4() {
    menuRepository.update(menu);
    int a = 10 / 0;
    throw new ArithmeticException();
}

SpringBoot 手动提交事务示例代码

@Api("事务手动提交测试")
@RestController
@RequestMapping("/thing")
public class TransactionsController {

    @Autowired
    MenuRepository menuRepository;
    @Autowired
    DataSourceTransactionManager dataSourceTransactionManager;
    @Autowired
    TransactionDefinition transactionDefinition;


    @RequestMapping("/test1")
    @ApiOperation(value = "事务回滚测试1", notes = "加上Transactional的属性")
    public String test1() {
        TransactionStatus transactionStatus = null;
        try {
            //手动开启事务
            transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
            Menu menu = new Menu();
            menu.setName("事务测试2");
            menu.setFlavor("事务测试2");
            menu.setPrice((double) 34);
            menu.setCount(23);
            menu.setId(2);
            menuRepository.update(menu);
            //手动提交事务
            dataSourceTransactionManager.commit(transactionStatus);
            int a = 10 / 0;
            return "sucess";
        } catch (Exception e) {
            if (transactionStatus != null) {
                //手动回滚
                dataSourceTransactionManager.rollback(transactionStatus);
            }
            e.printStackTrace();
            return "fail";
        }
    }
}

Mybatis事务

  • mybatis中提供了两种事务管理机制:JDBC(jdbc) MANAGED(managed)
    请添加图片描述

Jdbc 事务管理器

  • mybatis框架自己管理事务,自己采用原生的JDBC代码去管理事务:
  • 使用JDBC事务管理器的话,底层创建的事务管理器对象JdbcTransaction对象。
protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
        log.debug("Opening JDBC Connection");
    }

    this.connection = this.dataSource.getConnection();
    if (this.level != null) {
        this.connection.setTransactionIsolation(this.level.getLevel());
    }
	// 设置是否自动提交事务
    this.setDesiredAutoCommit(this.autoCommit);
}

// 提交和回滚自己处理
@Override
public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
        if (log.isDebugEnabled()) {
            log.debug("Committing JDBC Connection [" + connection + "]");
        }
        connection.commit();
    }
}

@Override
public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
        if (log.isDebugEnabled()) {
            log.debug("Rolling back JDBC Connection [" + connection + "]");
        }
        connection.rollback();
    }
}

Managed事务管理器

  • mybatis不再负责事务的管理了,事务管理交给其它容器来负责。例如:spring
  • 对于我们当前的单纯的只有mybatis的情况下,如果配置为MANAGED ,那么事务这块是没人管的。没有人管理事务表示事务压根没有开启。
  • SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH,false);执行过程
// openSession
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
    return openSessionFromDataSource(execType, null, autoCommit);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    // Silently ignores autocommit and isolation level, as managed transactions are entirely
    // controlled by an external manager.  It's silently ignored so that
    // code remains portable between managed and unmanaged configurations.
    return new ManagedTransaction(ds, level, closeConnection);
}
protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
        log.debug("Opening JDBC Connection");
    }

    this.connection = this.dataSource.getConnection();
    if (this.level != null) {
        this.connection.setTransactionIsolation(this.level.getLevel());
    }
}

// 提交回滚没有具体的实现
@Override
public void commit() throws SQLException {
    // Does nothing
}

@Override
public void rollback() throws SQLException {
    // Does nothing
}

事务提交报错

两个不同的事务提交报错

  • 操作代码:通过不同的事务更新一张表中的数据
 @Override
    @Transactional(rollbackFor = Throwable.class)
    public void transactionFour() {
        User user = new User();
        user.setUsername("张三");
        user.setId(1);
        userMapper.updateUser(user);

        DataSource dataSource = SpringContextUtils.getBean(DataSource.class);
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
            connection.prepareStatement("update user set username ='王五', gender='2' where id ='1'").executeUpdate();
        } catch (SQLException throwables) {
            logger.error("Error Occur:{}", throwables.getMessage(), throwables);
        }
    }
  • 报错如下:
------------------------------------------------------------------------------------------------------------------
2021-07-17 14:13:51.532 ERROR 22436 --- [io-8001-exec-10] c.l.s.s.t.TransactionServiceImpl         : Error Occur:Lock wait timeout exceeded; try restarting transaction

com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_201]
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_201]
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_201]
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_201]
  • 错误原因:对同一张表没有进行原子性的操作发生锁表。
  • 解决思路:想操作放在同一个原子操作内。
  • 解决方案:手动提交第一个事务,第二个事务读已经提交的数据
/**
  * 手动提交事务
  * 读已经提交的数据
  */
@Override
public void transactionFive() {
    TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
    User user = new User();
    user.setUsername("张三");
    user.setId(1);
    userMapper.updateUser(user);
    // 手动提交
    transactionManager.commit(transaction);
    updateUserOne(user);
}

public void updateUserOne(User user) {
    DataSource dataSource = SpringContextUtils.getBean(DataSource.class);
    Connection connection = null;
    try {
        connection = dataSource.getConnection();
        connection.setAutoCommit(true);
        // 设置为读已经提交了的
        connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
        connection.prepareStatement("update user set username ='王五', gender='2' where id ='1'").executeUpdate();
    } catch (SQLException throwables) {
        logger.error("Error Occur:{}", throwables.getMessage(), throwables);
    } finally {
        if (Objects.nonNull(connection)) {
            try {
                connection.close();
            } catch (SQLException throwables) {
                logger.error("Error Occur:{}", throwables.getMessage(), throwables);
            }
        }
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值