细节知多少 - spring boot transaction解析之阶段一 带有@Transactional方法的service类生成代理类的阶段

本文深入探讨了Spring Boot Transaction的第一阶段,即带有@Transactional注解的服务类如何生成代理类。通过分析源码,揭示了在AOP机制下,如何判断是否需要生成事务代理类,并详细介绍了生成代理类的条件和过程。文章还提到了关键组件如BeanFactoryTransactionAttributeSourceAdvisor的作用,以及代理类的创建与方法匹配逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

细节知多少 - spring boot transaction解析之阶段一 带有@Transactional方法的service类生成代理类的阶段

date: 2019-06-04

原文:spring boot transaction解析之阶段一 带有@Transactional方法的service类生成代理类的阶段

概述

spring boot transaction我们平时用着非常简单,只需要在service方法上声明@Transactional就可以了。但是要知道,简单的背后是不简单,只是很多东西spring框架帮我们做好了。如果想进阶,想进步,想弄懂,想学习,需要接近它,了解它,弄懂它,最好的方法莫过于debug它的源码了。

笔者把spring boot transaction的整个过程分为两个阶段

  1. 带有@Transactional方法的service类生成代理类的阶段
  2. 访问service类的@Transactional方法实现事务的阶段

本文说下第一阶段,用过spring的人差不多都是知道,spring 事务的实现是通过aop的方式。所以本系列的前提是基于注解的aop实现解析你要知道些,这部分请参考:spring boot aop过程解析之阶段一 AnnotationAwareAspectJAutoProxyCreator加载和初始化, AnnotationAwareAspectJAutoProxyCreator是aop动态代理的起始点和入口处,所有的beanClass经过通过他的实例化前置方法和所有的bean Object经过他的初始化后置方法时都会判断是否生成代理类。笔者假定你了解了这部分。

本文用到的代码项目:springboot_mybatis

技术栈

spring boot 1.5.10
spring framework 4.3.14

用例代码

@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {

	@Resource
	private IUserService userService;

	/**
	 * @param schoolName
	 * @param studentId
	 * @return
	 */
    @GetMapping("/updateSchoolName")
    public ResponseResult updateSchoolName(String schoolName, long studentId) {
        log.info("updateSchoolName param schoolName:{} studentId:{}", schoolName, studentId);
        userService.updateSchoolName(schoolName, studentId);
        return ResponseResult.ok();
    }
}

public interface IUserService {
	int insert(User user);
    int updateScore(long score, long id);
    void updateSchoolName(String schoolName, long studentId);
}

@Service
public class UserServiceImpl implements IUserService {
	@Resource
	private UserMapper userMapper;
    @Resource
    private StudentMapper studentMapper;

	@Override
    @Transactional(rollbackFor = Throwable.class)
	public int insert(User user) {
        int count = userMapper.insert(user);
		return count;
	}

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public int updateScore(long score, long id) {
        int count = userMapper.updateScore(iii, id);
        return count;
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void updateSchoolName(String schoolName, long studentId) {
        User user = userMapper.fingByStudentId(studentId);
        studentMapper.updateSchoolName(schoolName, user.getStudentId());
    }
}

public interface StudentMapper {
    Student findById(@Param("id") long id);
	int updateSchoolName(@Param("schoolName") String schoolName, @Param("id") long id);
}

public interface UserMapper {
	int insert(User user);
	int updateScore(@Param("score") long score, @Param("id") long id);
}

以上代码模拟了实际开发中mvc层级调用关系,事务在service层。我们以此代码为例阐述spring事务相关知识点

带着问题学习

带着问题学习往往起到更好的效果

问题:

1. service类只要有方法加了@Transactional注解就会生成代理类吗,此处的方法有条件吗
2. spring boot 涉及transaction事务的xxxAutoConfiguration都有哪些
3. spring boot 默认生成代理的方式是cglib还是jdk
4. 什么样的方法可以生成代理方法

下面开始和笔者一起走入spring aop生成代理类的小溪,从本文开头处我们知道,生成代理类的入口是AnnotationAwareAspectJAutoProxyCreator(使用父类的方法)实例化前置方法和初始化后置方法(如果此处不太了解,请参考:spring boot aop过程解析之阶段一 AnnotationAwareAspectJAutoProxyCreator加载和初始化)。我们直接从此处展开

生成代理类条件判断

我们看下前后置方法的代码。同样,因为这里在spring boot aop过程解析之阶段一 AnnotationAwareAspectJAutoProxyCreator加载和初始化)已经阐述过。所以这里我们只关注和spring transaction有关的地方

public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
	······
	// shouldSkip方法是重点,判断是否应该为beanClass生成代理类
	if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) { // (1)
		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return null;
	}
	 

	// Create proxy here if we have a custom TargetSource
	if (beanName != null) {
		TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
		if (targetSource != null) {
			this.targetSourcedBeans.add(beanName);
			Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); // (2)
			Object proxy = createProxy(beanClass,
<think>我们正在讨论SpringBoot@Transactional方法执行时抛出异常的问题。根据用户需求,我们需要查找相关解决方案或调试信息。结合提供的引用内容,我们可以总结以下几点关键信息:1.**事务生效原则**:根据引用[3],只有当异常传播出标记了@Transactional注解的方法时,事务才能回滚。如果在方法内部捕获了异常,则事务不会回滚。2.**异常型**:默认情况下,Spring事务只对运行时异常(RuntimeException)和错误(Error)进行回滚(引用[4])。如果需要让非运行时异常(如受检异常)也触发回滚,需要明确配置`@Transactional(rollbackFor=Exception.class)`。3.**自调用问题**:如果同中的方法调用另个有@Transactional注解的方法,由于代理机制(SpringAOP)的限制,事务不会生效(引用[5])。解决方法是注入自身代理对象,通过代理对象调用方法。4.**事务方法必须是public**:SpringAOP要求代理的方法必须是public的,否则事务注解不会生效。5.**数据库引擎支持事务**:确保使用的数据库引擎支持事务(如InnoDB)。6.**异常被捕获**:在事务方法中,如果捕获了异常但没有重新抛出,则事务不会回滚(引用[3])。**解决方案步骤:**1.**检查异常型**:确认抛出的异常是否是运行时异常(RuntimeException)或错误(Error)。如果不是,需要在@Transactional注解中指定`rollbackFor`属性,例如`@Transactional(rollbackFor=Exception.class)`。2.**检查是否在方法内捕获异常**:确保在事务方法内部没有捕获异常而不抛出。如果必须捕获异常,可以在捕获后抛出运行时异常(如`thrownewRuntimeException(e);`)或使用`TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();`手动回滚。3.**检查方法调用方式**:如果是在同中调用事务方法,确保是通过代理对象调用的。例如:```java@ServicepublicclassUserService{@AutowiredprivateUserServiceself;//注入自身代理publicvoidouterMethod(){//通过代理调用内部事务方法self.innerTransactionalMethod();}@TransactionalpublicvoidinnerTransactionalMethod(){//事务操作}}```4.**检查方法可见性**:确保事务方法是public的。5.**检查数据库引擎**:确认数据库表使用的是支持事务的引擎(如InnoDB)。6.**调试事务拦截器**:可以开启Spring的调试日志,查看事务拦截器是否执行。在`application.properties`中添加:```logging.level.org.springframework.transaction.interceptor=TRACElogging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG```**示例代码调整:**假设原代码如下:```java@Override@TransactionalpublicvoidaddUser(UserParamparam){try{Useruser=PtcBeanUtils.copy(param,User.class);userDAO.insert(user);if(!CollectionUtils.isEmpty(param.getRoleIds())){userRoleService.addUserRole(user.getId(),param.getRoleIds());}}catch(Exceptione){//捕获异常但没有抛出,导致事务不回滚log.error("添加用户失败",e);}}```调整后:```java@Override@Transactional(rollbackFor=Exception.class)publicvoidaddUser(UserParamparam)throwsException{Useruser=PtcBeanUtils.copy(param,User.class);userDAO.insert(user);if(!CollectionUtils.isEmpty(param.getRoleIds())){userRoleService.addUserRole(user.getId(),param.getRoleIds());}}```或者,如果需要在方法内处理异常并回滚:```java@Override@Transactional(rollbackFor=Exception.class)publicvoidaddUser(UserParamparam){try{//...业务代码}catch(Exceptione){log.error("添加用户失败",e);//手动设置回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();thrownewRuntimeException(e);//或者根据业务需要抛出}}```**注意事项**:-确保事务方法所在的Spring管理(如使用@Service注解)。-避免在事务方法中执行耗时的非数据库操作,以减少事务持有时间。通过以上步骤,应该能够解决大部分@Transactional方法执行时抛出异常的事务回滚问题。§§相关问题§§1.Spring事务管理默认回滚哪些异常?2.如何解决Spring事务自调用失效的问题?3.在SpringBoot中如何手动回滚事务?4.为什么@Transactional注解的方法必须是public的?5.如何配置Spring事务的传播行为?</think>### Spring Boot @Transactional 方法执行时抛出异常的解决方案 当 Spring Boot使用 `@Transactional` 注解的方法执行时抛出异常,事务可能未按预期回滚。以下是关键原因和解决方案: #### 、核心问题分析 1. **默认回滚规则限制** Spring 事务默认只回滚 **运行时异常(RuntimeException)** 和 **错误(Error)**。若抛出受检异常(如 `IOException`),事务不会回滚[^4]。 *示例:* ```java @Transactional public void process() throws IOException { // 若此处抛出IOException,事务不会回滚 } ``` 2. **异常被内部捕获** 根据 AOP 实现原理,只有异常**传播出`@Transactional`方法**时才会触发回滚。若在方法内部捕获异常,事务不会回滚[^3]: ```java @Transactional public void updateData() { try { // 数据库操作 } catch (Exception e) { // 捕获异常但未重新抛出 → 事务不回滚 } } ``` 3. **自调用问题(常见陷阱)** 在同中,非事务方法直接调用事务方法会**绕过代理机制**,导致事务失效[^5]: ```java public class UserService { public void outerMethod() { innerTransactionalMethod(); // 自调用事务失效 } @Transactional public void innerTransactionalMethod() { ... } } ``` 4. **方法访问权限问题** `@Transactional` 注解仅对 **public 方法** 生效,protected/private 方法无效。 --- #### 二、解决方案 1. **明确指定回滚异常型** 使用 `rollbackFor` 属性声明需要回滚的异常: ```java @Transactional(rollbackFor = Exception.class) // 任何异常都回滚 public void criticalOperation() throws CustomException { // ... } ``` 2. **正确处理异常捕获** - 方案1:重新抛出运行时异常 ```java @Transactional public void saveData() { try { // 数据库操作 } catch (SQLException e) { throw new DataAccessException(e); // 包装为运行时异常 } } ``` - 方案2:手动标记回滚 ```java @Transactional public void importFile() { try { // 文件操作 } catch (IOException e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } } ``` 3. **解决自调用问题** 通过代理对象调用事务方法: ```java @Service public class OrderService { @Autowired private OrderService selfProxy; // 注入自身代理 public void createOrder() { selfProxy.processPayment(); // 通过代理调用 } @Transactional public void processPayment() { ... } } ``` 4. **启用事务调试日志** 在 `application.properties` 中增加配置: ```properties logging.level.org.springframework.transaction.interceptor=TRACE logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG ``` 日志会显示事务开启/提交/回滚状态,帮助定位问题。 --- #### 三、验证事务是否生效 1. **检查数据库引擎** 确认使用支持事务的引擎(如 InnoDB),非 MyISAM。 2. **单元测试验证** ```java @SpringBootTest class TransactionTest { @Autowired private UserService userService; @Test void testRollback() { assertThrows(RuntimeException.class, () -> { userService.failOperation(); // 应抛出异常 }); // 验证数据库无变更 } } ``` --- #### 四、最佳实践总结 | 要点 | 正确做法 | 错误做法 | |------|----------|----------| | **异常处理** | 用 `rollbackFor=Exception.class` | 依赖默认回滚规则 | | **方法调用** | 通过代理对象调用事务方法 | 内部直接调用 | | **访问权限** | 仅用于 public 方法 | 用于 private 方法 | | **日志调试** | 启用 `TRACE` 级事务日志 | 无日志排查 | > 参考 Spring 官方文档:当异常未被传播出事务边界时,事务拦截器无法捕获异常信号,导致回滚机制失效[^3]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值