当前位置: 代码迷 >> 综合 >> Spring Boot数据访问:事务处理
  详细解决方案

Spring Boot数据访问:事务处理

热度:35   发布时间:2024-03-07 04:05:20.0

事务管理对于企业应用来说是至关重要的,当出现异常情况时,它也可以保证数据的一致性。

事务管理的两种方式

  • 编程式事务指的是通过编码方式实现事务;编程式事务使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
  • 声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

注意事项

  • 默认配置下 Spring 只会回滚运行时、未检查异常(继承自 RuntimeException 的异常)或者 Error。
  • @Transactional 注解只能应用到 public 方法才有效。

spring事务特性

事务传播行为

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在Propagation定义中包括了如下几个表示传播行为的常量:

  • Propagation.REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  • Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • Propagation.MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • Propagation.REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • Propagation.NESTED:如果当前事务存在,在嵌套事务中执行,就像需要的传播一样。标识为nested的方法调用其他标有:Mandatory,supports,required的事务方法时使用的是一个事务管理,但内部事务方法异常回滚并不会影响外部方法。

Isolation事务隔离级别

  • Isolation.DEFAULT:数据源(数据库)的默认隔离级别,以目前常用的MySQL为例,默认的隔离级别通常为REPEATABLE_READ。
  • Isolation.READ_UNCOMMITTED未授权读取级别:这是最低的隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全,可能出现丢失更新、脏读、不可重复读、幻读。
  • Isolation.READ_COMMITTED授权读取级别:以操作同一行数据为前提,读事务允许其他读事务和写事务,未提交的写事务禁止其他读事务和写事务。此隔离级别可以防止更新丢失、脏读,但不能防止不可重复读、幻读。此隔离级别可以通过“瞬间共享读锁”和“排他写锁”实现。
  • Isolation.REPEATABLE_READ可重复读取级别:保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响。以操作同一行数据为前提,读事务禁止其他写事务,但允许其他读事务,未提交的写事务禁止其他读事务和写事务。此隔离级别可以防止更新丢失、脏读、不可重复读,但不能防止幻读。
  • Isolation.SERIALIZABLE序列化级别:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰。提供严格的事务隔离,此隔离级别可以防止更新丢失、脏读、不可重复读、幻读。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免更新丢失、脏读,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

事务超时

  • 所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

  • 默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。

spring事务回滚规则

  • 默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。可以明确的配置在抛出哪些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。

Spring事务Transactional解析

@Target({
    ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")String value() default "";@AliasFor("value")String transactionManager() default "";//默认为存在事务则加入,没有事务则创建新的事务Propagation propagation() default Propagation.REQUIRED; //隔离级别,默认为数据库隔离级别Isolation isolation() default Isolation.DEFAULT;//事务超时回滚,-1位不设置超时时间int timeout() default -1;boolean readOnly() default false;Class<? extends Throwable>[] rollbackFor() default {
    };String[] rollbackForClassName() default {
    };Class<? extends Throwable>[] noRollbackFor() default {
    };String[] noRollbackForClassName() default {
    };
}

以MyBatis为例,基于注解的声明式事务配置

1、sql脚本

#创建表
create table `user`(id int PRIMARY key auto_increment,`name` VARCHAR(20) not null comment '名字',age int,credits int,create_time datetime
);#初始化数据
insert into `user`(`name`, age, credits, create_time) VALUES
('May', 5, 100, SYSDATE()),
('June', 6, 100, SYSDATE());

2、mapper.xml与Dao

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.yanyuan.first.dao.mybatis.UserMapper"><resultMap id="BaseResult" type="com.yanyuan.first.entity.User"><result property="id" column="id" jdbcType="INTEGER"/><result property="name" column="name" jdbcType="VARCHAR"/><result property="age" column="age" jdbcType="INTEGER"/><result property="credits" column="credits" jdbcType="INTEGER"/><result property="createTime" column="create_time" jdbcType="TIMESTAMP"/></resultMap><select id="getById" resultMap="BaseResult">SELECT * FROM user where id = #{id, jdbcType=INTEGER}</select><update id="updateById" parameterType="com.yanyuan.first.entity.User">update userset name = #{name, jdbcType=VARCHAR} ,age = #{age, jdbcType=INTEGER},credits = #{credits, jdbcType=INTEGER}where id = #{id, jdbcType=INTEGER}</update></mapper>
@Mapper
public interface UserMapper {
    User getById(@Param("id") Integer id);Integer updateById(User user);
}

3、service方法

@Overridepublic User getById(Integer id) {
    return userMapper.getById(id);}/*** 赠送积分,开启事务**/@Override@Transactional //开启事务public boolean donateCreditsOpenTransactional(Integer fromUid, Integer toUid, Integer credits){
    return donateCredits(fromUid, toUid, credits);}@Overridepublic boolean donateCredits(Integer fromUid, Integer toUid, Integer credits){
    User formUser = getById(fromUid);User toUser = getById(toUid);formUser.setCredits(formUser.getCredits() - credits);toUser.setCredits(toUser.getCredits() + credits);updateById(formUser);//模拟异常int test = 1/0;updateById(toUser);return true;}

4、Junit测试

赠送积分:开启事务

 /*** 赠送积分-开启事务*/@Testvoid donateCreditsOpenTransactional() {
    try {
    userService.donateCreditsOpenTransactional(1, 2, 5);}catch (Exception e){
    log.error(e.getMessage());}log.info("open Transactional formUser : {}", userService.getById(1));log.info("open Transactional toUser : {}", userService.getById(2));}

执行结果:发生异常,用户1未减少积分用户2未增加积分

/ by zero
open Transactional formUser : User(id=1, name=May五月, age=5, credits=100, createTime=Wed Oct 14 00:44:49 CST 2020)
open Transactional toUser : User(id=2, name=June, age=6, credits=100, createTime=Wed Oct 14 00:44:49 CST 2020)

赠送积分:未开启事务

  /*** 赠送积分*/@Testvoid donateCredits() {
    try {
    userService.donateCredits(1, 2, 5);}catch (Exception e){
    log.error(e.getMessage());}log.info("formUser : {}", userService.getById(1));log.info("toUser : {}", userService.getById(2));}

执行结果:发生异常,用户1减少了5积分用户2未增加积分

/ by zero
formUser : User(id=1, name=May五月, age=5, credits=95, createTime=Wed Oct 14 00:44:49 CST 2020)
toUser : User(id=2, name=June, age=6, credits=100, createTime=Wed Oct 14 00:44:49 CST 2020)

资料下载

  • 源码地址:my-spring-boot
  • 对应分支:data-transaction
  相关解决方案