Spring事务

事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性;

Spring 实现事务管理有如下两种方式:

编程式事务管理:

  将事务管理代码嵌入到业务方法中来控制事务的提交和回滚,在编程式管理事务中,必须在每个事务操作中包含额外的事务管理代码。

声明式事务管理(推荐):

  大多数情况下比编程式事务管理更好用,它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理,Spring声明式事务管理建立在AOP基础之上,是一个典型的横切关注点,通过环绕增强来实现,其原理是对方法前后进行拦截,然后在目标方法开始之前创建或加入一个事务,在执行完毕之后根据执行情况提交或回滚事务,其模型如下:

public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    try {
        //开启事务
        returnjoinPoint.proceed();
        //提交事务
    } catch (Throwable e) {
        //回滚事务
        throwe;
    }finally {
        //释放资源
    }
}

如何实现声明式事务:

1、添加spring-aspects-4.3.10.RELEASE.jar包

2、在Spring配置文件中添加如下配置:

<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

3、在Service层public方法上添加事务注解——@Transactional

注意:

①、一个类含有@Transactional注解修饰的方法,则Spring框架自动为该类创建代理对象,默认使用JDK创建代理对象,可以通过添加<aop:aspectj-autoproxy proxy-target-class="true"/>使用CGLib创建代理对象,此时需要添加aspectjweaver-x.x.x.jar包,具体代码参见《@Transactional注解Java工程。

②、不能在protected、默认或者private的方法上使用@Transactional注解,否则无效。

@Transactional注解属性:

我们通过一个网上购书的模拟场景来举例介绍注解属性,购书过程会有两次修改数据库的操作,分别通过判断书籍是否足够和余额是否足够来操作,但我们希望的是当两次判断都确定后再执行完整的操作,于是就有了事务的回滚机制。

//立即购买
@Override
@Transactionalpublic boolean insert(String userId, String bookId, int count) {

    if(bookDao.enough(bookId,count)){//判断书籍
        bookDao.update(bookId,count);//修改书籍库存
    }
    
    double price = bookDao.getPrice(bookId);
    double total = count*price;
    if(moneyDao.enough(userId, total)){//余额是否足够
        //生成订单
        Coupon coupon = new Coupon();
        coupon.setId(UUID.randomUUID().toString());
        coupon.setUserId(userId);
        coupon.setBookId(bookId);
        coupon.setTotal(total);
        couponDao.insert(coupon);
        //修改余额
        moneyDao.update(userId,total);
    }
    
    return true;
}

rollbackFor和rollbackForClassName:指定对哪些异常回滚事务。默认情况下,如果在事务中抛出了运行时异常(继承自RuntimeException异常类),则回滚事务;如果没有抛出任何异常,或者抛出了检查时异常,则依然提交事务。这种处理方式是大多数开发者希望的处理方式,也是 EJB 中的默认处理方式;但可以根据需要人为控制事务在抛出某些运行时异常时仍然提交事务,或者在抛出某些检查时异常时回滚事务。

此例中当没有填写注释属性时,默认回滚运行时异常,当书籍足够,余额不够(抛出运行时异常)时,回滚事务,不操作数据库。

当书籍足够,余额不够(抛出检查时异常)时,事务不回滚,数据库书籍相应减少,余额不变。

当填写rollbackFor注释时,余额不够产生异常(运行时、检查时异常),事务回滚,不操作数据库。

特别注意的是,异常不能用try-catch处理,否则即便@Transactional注解中添加了rollbackFor=MoneyException.class,事务也不回滚。因为回滚的异常使用try-catch就会没法捕获,只能使用抛出throw的异常才能被捕获到

//回滚的异常不能使用try-catch没法捕获,只能使用抛出throw的异常才能回滚
@Transactional(rollbackFor=MoneyException.class)

noRollbackFor和noRollbackForClassName:指定对哪些异常不回滚事务。

@Transactional(noRollbackFor=MoneyException.class)

readOnly:事务只读,指对事务性资源进行只读操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。由于只读的优化措施是在一个事务启动时由后端数据库实施的, 因此,只有对于那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRES_NEW、PROPAGATION_REQUIRED、 ROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义。

//readOnly只读,只能查询数据,不能修改数据。用得少
@Transactional(readOnly=true)

timeout:设置一个事务所允许执行的最长时长(单位:秒),如果超过该时长且事务还没有完成,则自动回滚事务且出现org.springframework.transaction.TransactionTimedOutException异常

//timeout超时,当事务超过指定时间报错回滚。常用于电商平台,如双11的访问量过多,出现问题会一直占用资源。
@Transactional(timeout=3000)

propagation:指定事务传播行为,一个事务方法被另一个事务方法调用时,必须指定事务应该如何传播,例如:方法可能继承在现有事务中运行,也可能开启一个新事物,并在自己的事务中运行。Spring定义了如下7种事务传播行为:

REQUIRED:默认值,如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行;

REQUIRES_NEW:当前方法必须启动新事务,并在它自己的事务内运行,如果有事务在运行,则把当前事务挂起,直到新的事务提交或者回滚才恢复执行;

SUPPORTS:如果有事务在运行,当前的方法就在这个事务内运行,否则以非事务的方式运行;

NOT_SUPPORTED:当前的方法不应该运行在事务中,如果有运行的事务,则将它挂起;

NEVER:当前方法不应该运行在事务中,否则将抛出异常;

MANDATORY(mandatory [ˈmændətɔːri] adj.强制的):当前方法必须运行在事务内部,否则将抛出异常;

NESTED(nest[nest] v.嵌套):如果有事务在运行,当前的方法在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在它自己的事务内运行,此时等价于REQUIRED。

注意:对于NESTED内层事务而言,内层事务独立于外层事务,可以独立递交或者回滚,如果内层事务抛出的是运行异常,外层事务进行回滚,内层事务也会进行回滚。

//创建新事务,打断事务传播,上一级方法出现异常时回滚不会回滚此事务
@Transactional(propagation=Propagation.REQUIRES_NEW)
原文地址:https://www.cnblogs.com/yimengxianzhi/p/12271727.html