Spring事务

Spring事务

1.事务的传播行为

1.1.Propagation.REQUIRED

定义:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值(default)

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@GetMapping("/test-required")
public void testRequired() {
    Transaction transaction = new Transaction();
    transaction.setName("zyf");
    transaction.setAge(20);
    try {
        transactionMapper.insert(transaction);
        // 在此处抛出异常,数据回滚
        int i = 1 / 0; 
    } catch (MathArithmeticException e) {
        e.printStackTrace();
    }
}
1.2.Propagation.REQUIRES_NEW

定义:创建一个新的事务,如果当前存在事务,则把当前事务挂起(开启独立事务)

讲人话呢就是,不管外部事务是否存在,内部都会创建一个新事务并独立运行

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
@GetMapping("/test-requires-new")
public void testRequiresNew() {
    Transaction transaction = new Transaction();
    transaction.setName("zyf");
    transaction.setAge(20);
	// 内部方法的调用是无法开启新的事务的,因为@Transactional是基于AOP实现的
    AspectTest o = (AspectTest)AopContext.currentProxy();
    o.independentTransaction();
    
    // 如果是调用外部方法(非当前类),则可以直接进行调用并且事物生效(xxxService为注入bean)
    xxxService.independentTransaction();
    try {
        int i = 1 / 0;
        transactionMapper.insert(transaction);
    } catch (MathArithmeticException e) {
        e.printStackTrace();
    }
}

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void independentTransaction() {
    Transaction transaction = new Transaction();
    transaction.setName("ls");
    transaction.setAge(32);
    try {
        transactionMapper.insert(transaction);
    } catch (MathArithmeticException e) {
        e.printStackTrace();
    }
}
1.3.Propagation.SUPPORTS

定义:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式运行

@Transactional(rollbackFor = Exception.class)
@GetMapping("/test-supports")
public void testSupports() {
    Transaction transaction = new Transaction();
    transaction.setName("supports");
    transaction.setAge(20);
    transactionMapper.insert(transaction);
    outerService.testSupports();
    try {
        int res = ONE / ZERO;
        System.out.println("一切正常...");
    } catch (MathArithmeticException e) {
        e.printStackTrace();
    }
}

@Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
public void testSupports() {
    TRANSACTION.setName("supports - outer");
    TRANSACTION.setAge(20);
    transactionManager.insert(TRANSACTION);
    try {
        int res = ONE / ZERO;
        System.out.println("内部一切正常...");
    } catch (MathArithmeticException e) {
        e.printStackTrace();
    }
}
1.4.Propagation.NOT_SUPPORTED

定义:以非事务方式运行,如果当前存在事务,则把当前事务挂起

(讲人话呢就是,不管外部是否存在事务,内部都以非事务方式运行)

@Transactional(rollbackFor = Exception.class)
@GetMapping("/test-not-supported")
public void testSupports() {
    Transaction transaction = new Transaction();
    transaction.setName("not - support");
    transaction.setAge(20);
    transactionMapper.insert(transaction);
    outerService.testNotSupported();
    try {
        int res = ONE / ZERO;
        System.out.println("一切正常...");
    } catch (MathArithmeticException e) {
        e.printStackTrace();
    }
}

@Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
public void testNotSupported() {
    TRANSACTION.setName("not_supported - outer");
    TRANSACTION.setAge(90);
    transactionManager.insert(TRANSACTION);
    try {
        int res = ONE / ZERO;
    } catch (MathArithmeticException e) {
        System.out.println("内部永远以非事务运行...");
        e.printStackTrace();
    }
}
1.5.Propagation.NEVER

定义:以非事务方式运行,如果当前存在事务,则抛出异常

@Transactional(rollbackFor = Exception.class)
@GetMapping("/test-never")
public void testSupports() {
    Transaction transaction = new Transaction();
    transaction.setName("never");
    transaction.setAge(20);
    transactionMapper.insert(transaction);
    outerService.testNever();
    try {
        System.out.println("一切正常...");
    } catch (MathArithmeticException e) {
        e.printStackTrace();
    }
}

@Transactional(rollbackFor = Exception.class, propagation = Propagation.NEVER)
public void testNever() {
    TRANSACTION.setName("never - outer");
    TRANSACTION.setAge(66);
    transactionManager.insert(TRANSACTION);
    try {
        System.out.println("内部一切正常...");
    } catch (MathArithmeticException e) {
        System.out.println("内部永远以非事务运行...");
        e.printStackTrace();
    }
}
// 执行程序将抛出异常IllegalTransactionStateException
// Existing transaction found for transaction marked with propagation 'never'
1.6.Propagation.MANDATORY

定义:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常(好理解,不再举例说明)

1.7.Propagation.NESTED

定义:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于Propagation.REQUIRED

2.事务失效的场景

2.1. 抛出检查异常

比如你的事务控制代码如下:

java复制代码@Transactional
public void transactionTest() throws IOException{
    User user = new User();
    UserService.insert(user);
    throw new IOException();
}

如果@Transactional 没有特别指定,Spring 只会在遇到运行时异常RuntimeException或者error时进行回滚,而IOException等检查异常

不会影响回滚

解决方案

知道原因后,解决方法也很简单。配置rollbackFor属性,例如@Transactional(rollbackFor = Exception.class)

2.2 业务方法本身捕获了异常

@Transactional(rollbackFor = Exception.class)
public void transactionTest() {
    try {
        User user = new User();
        UserService.insert(user);
        int i = 1 / 0;
    }catch (Exception e) {
        e.printStackTrace();
    }
}

这种场景下,事务失败的原因也很简单,Spring是否进行回滚是根据你是否抛出异常决定的,所以如果你自己捕获了异常,Spring 也无能为力

解决方案

抛出异常throw new RuntimeException(e);

2.3 同一类中的方法调用

@Service
public class DefaultTransactionService implement Service {

    public void saveUser() throws Exception {
        //do something
        doInsert();
    }

    @Transactional(rollbackFor = Exception.class)
    public void doInsert() throws IOException {
        User user = new User();
        UserService.insert(user);
        throw new IOException();

    }
}

这也是一个容易出错的场景。事务失败的原因也很简单,因为Spring的事务管理功能是通过动态代理实现的,而Spring默认使用JDK动态代理,而JDK动态代理采用接口实现的方式,通过反射调用目标类。简单理解,就是saveUser()方法中调用this.doInsert(),这里的this是被真实对象,所以会直接走doInsert的业务逻辑,而不会走切面逻辑,所以事务失败

解决方案

可以把方法doInsert放到另外一个service或者dao,然后把这个server或者dao通过@Autowired注入到方法A的bean里面,这样即使方法A没用事务,方法B也可以执行自己的事务了

2.4 方法使用 final 或 static关键字

如果Spring使用了Cglib代理实现(比如你的代理类没有实现接口),而你的业务方法恰好使用了final或者static关键字,那么事务也会失败。更具体地说,它应该抛出异常,因为Cglib使用字节码增强技术生成被代理类的子类并重写被代理类的方法来实现代理。如果被代理的方法的方法使用finalstatic关键字,则子类不能重写被代理的方法。

如果Spring使用JDK动态代理实现,JDK动态代理是基于接口实现的,那么finalstatic修饰的方法也就无法被代理。

总而言之,方法连代理都没有,那么肯定无法实现事务回滚了。

解决方案

想办法去掉final或者static关键字

2.5 方法不是public

如果方法不是publicSpring事务也会失败,因为Spring的事务管理源码AbstractFallbackTransactionAttributeSource中有判断computeTransactionAttribute()。如果目标方法不是公共的,则TransactionAttribute返回null

// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
  return null;
}
解决方案

是将当前方法访问级别更改为public

2.6 错误使用传播机制

具体看以上的7种事物传播行为

2.7 没有被Spring管理

// @Service
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
}

如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了

解决方案

需要保证每个事务注解的每个Bean被Spring管理

2.8 多线程

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {

        userMapper.insertUser(userModel);
        new Thread(() -> {
             try {
                 test();
             } catch (Exception e) {
                roleService.doOtherThing();
             }
        }).start();
    }
}

@Service
public class RoleService {

    @Transactional
    public void doOtherThing() {
         try {
             int i = 1/0;
             System.out.println("保存role表数据");
         }catch (Exception e) {
            throw new RuntimeException();
        }
    }
}

add() 方法中调用了 doOtherThing() 方法,而 doOtherThing() 方法在一个新的线程中执行。由于每个线程都拥有自己的数据库连接,因此这两个方法实际上是在不同的事务中执行的

这意味着如果 doOtherThing() 方法中抛出异常,它会导致该线程的事务回滚,但不会影响 add() 方法中的事务。因为它们不共享相同的数据库连接,所以它们也不共享相同的事务上下文

解决方案

(分布式事物)

如果希望在 doOtherThing() 方法中抛出异常时回滚 add() 方法中的事务,可以通过其他手段来实现,例如将这两个方法合并在同一个事务内执行,或者通过其他同步机制来确保两个方法的事务性一致性

posted @   zhangyf1121  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
点击右上角即可分享
微信分享提示