Spring声明式事务

知识是枯燥的,耐心学习最重要

文章学习地址:Spring声明式事务原理

@EnableTransactionManagement

在Spring入口类加上@EnableTransactionManagement注解,以开启事务

q:@EnableTransactionManagement是不是多余的?
p:@EnableTransactionManagement是spring-tx(spring-tx模块负责在spring框架中实现事务管理功能。以aop切面的方式将事务注入到业务代码中,并实现不同类型的事务管理器。)的注解,不是spring-boot的,spring-boot会自动配置事务。参考地址

事务回滚例子

@Service
public class UserServiceImpl implements UserService {

    private final UserMapper userMapper;

    public UserServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Transactional
    @Override
    public void saveUser(User user) {
        userMapper.save(user);
        // 测试事务回滚
        if (!StringUtils.hasText(user.getUsername())) {
            throw new RuntimeException("username不能为空");
        }
    }
}

当插入的userusername为空时,数据库没有数据插入,说明事务控制成功。

注释掉UserServiceImplsaveUser方法上的@Transactional注解,数据被插入到数据库中,事务控制失效。

事务不生效场景

场景一

Service方法抛出的异常不是RuntimeException或者Error类型,并且@Transactional注解上没有指定回滚异常类型。

@Service
public class UserServiceImpl implements UserService {

    private final UserMapper userMapper;

    public UserServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Transactional
    @Override
    public void saveUser(User user) throws Exception {
        userMapper.save(user);
        // 测试事务回滚
        if (!StringUtils.hasText(user.getUsername())) {
            throw new Exception("username不能为空");
        }
    }
}

这种情况下,Spring并不会进行事务回滚操作。
@Transactional注解源码注释所述
在这里插入图片描述
默认情况下,Spring事务只对RuntimeException或者Error类型异常(错误)进行回滚,检查异常(通常为业务类异常)不会导致事务回滚。

解决上面事务不生效的问题,有两种方式:

1.手动在@Transactional注解上声明回滚的异常类型(方法抛出该异常及其所有子类型异常都能触发事务回滚)

@Service
public class UserServiceImpl implements UserService {

    private final UserMapper userMapper;

    public UserServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveUser(User user) throws Exception {
        userMapper.save(user);
        // 测试事务回滚
        if (!StringUtils.hasText(user.getUsername())) {
            throw new Exception("username不能为空");
        }
    }
}

2.方法内手动抛出的检查异常类型改为RuntimeException子类型

定义一个自定义异常类型ParamInvalidException

public class ParamInvalidException extends RuntimeException{

    public ParamInvalidException(String message) {
        super(message);
    }
}

修改UserServiceImplsavaUser方法

@Service
public class UserServiceImpl implements UserService {

    private final UserMapper userMapper;

    public UserServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Transactional
    @Override
    public void saveUser(User user) {
        userMapper.save(user);
        // 测试事务回滚
        if (!StringUtils.hasText(user.getUsername())) {
            throw new ParamInvalidException("username不能为空");
        }
    }
}

两种方式都能让事务按照我们的预期生效

场景二

非事务方法直接通过this调用本类事务方法。这种情况也是比较常见的。

@Service
public class UserServiceImpl implements UserService {

    private final UserMapper userMapper;

    public UserServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public void saveUserTest(User user) {
        this.saveUser(user);
    }

    @Transactional
    @Override
    public void saveUser(User user) {
        userMapper.save(user);
        // 测试事务回滚
        if (!StringUtils.hasText(user.getUsername())) {
            throw new ParamInvalidException("username不能为空");
        }
    }
}

UserServiceImpl中,我们新增了savaUserTest方法,该方法没有使用@Transactionl注解标注,为非事务方法,内部直接调用了saveUser事务方法。

这种情况下事务并没有回滚,数据被插到数据库中。

这种情况下事务失效的原因为:Spring事务控制使用AOP代理实现,通过对目标对象的代理来增强目标方法。而上面例子直接通过this调用本类方法的时候,this的指向并非代理类,而是该类本身。

在这里插入图片描述
这种情况下有2种解决办法(原理都是使用代理对象来代替this)

1.从IOC容器获取UserService Bean,然后调用他的saveUser方法

@Service
public class UserServiceImpl implements UserService, ApplicationContextAware {

    private final UserMapper userMapper;
    private ApplicationContext context;

    public UserServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public void saveUserTest(User user) {
        UserService userService = context.getBean(UserService.class);
        userService.saveUser(user);
    }

    @Transactional
    @Override
    public void saveUser(User user) {
        userMapper.save(user);
        // 测试事务回滚
        if (!StringUtils.hasText(user.getUsername())) {
            throw new ParamInvalidException("username不能为空");
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }
}

上面代码我们通过实现ApplicationContextAware接口注入了应用上下文ApplicationContext,然后从中取出UserService Bean来代替this

2.从AOP上下文中取出当前代理对象:

这种情况首先需要引入 AOP Starter

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

然后在SpringBoot入口类中通过注解@EnableAspectJAutoProxy(exposeProxy = true)将当前代理对象暴露到AOP上下文中(通过AopContextThreadLocal实现)

最后在UserServcieImplsaveUserTest方法中通过AopContext获取UserServce的代理对象

@Service
public class UserServiceImpl implements UserService {

    private final UserMapper userMapper;

    public UserServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public void saveUserTest(User user) {
        UserService userService = (UserService) AopContext.currentProxy();
        userService.saveUser(user);
    }

    @Transactional
    @Override
    public void saveUser(User user) {
        userMapper.save(user);
        // 测试事务回滚
        if (!StringUtils.hasText(user.getUsername())) {
            throw new ParamInvalidException("username不能为空");
        }
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值