如果你正打算深入学习Spring,但是不知从何学起,那么我强烈推荐你可以按照这个系列做一遍。本系列将Spring框架的各个部分从它庞杂的代码体系中抽取出来,然后对每一个部分进行讲解,并最终搭建成简易版Spring。我以人格保证:如果你可以坚持做下来,那么你对Spring这块的知识就基本都掌握清楚了! 附上该系列地址:https://blog.csdn.net/zhang_qing_yun/article/details/120084497
Spring事务管理
Spring事务的配置方式
Spring支持两种事务配置方式:
- 编程式事务配置:使用TransactionTemplate(推荐)或者直接使用PlatformTransactionManager通过硬编码的方式在业务代码中来管理事务。
- 声明式事务配置:通过XML配置或者注解的方式来声明开启事务,然后让Spring来接管事务的实现逻辑,通过这种方式,我们只需要声明事务而不用去管理事务。声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
对比:编程式事务是侵入式的,对业务代码的编写有影响,并且每次使用都需要单独实现,非常繁琐;声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要通过配置文件或者注解的方式声明事务便可以将事务规则应用到业务逻辑中。但是,声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的。
Spring的事务传播
首先,事务传播都是在一个线程内完成的;其次,所谓的事务传播行为是相对于内层被调用函数来说的,Spring提供了7种事务传播机制:
@Transactional的实现原理
首先我们需要在主配置类上添加@EnableTransactionManagement注解来开启事务,这个注解通过@Import注解向容器中引入了两个类:AutoProxyRegistrar和ProxyTransactionManagementConfiguration。
AutoProxyRegistrar向容器中导入了InfrastructureAdvisorAutoProxyCreator,该类继承于AbstractAutoProxyCreator,作用类似于AOP,只不过是用于为加了@Transactional注解的类生成代理对象。
ProxyTransactionManagementConfiguration向容器中导入了3个Bean:AnnotationTransactionAttributeSource、TransactionInterceptor、BeanFactoryTransactionAttributeSourceAdvisor,其中前两个都是第三个的属性,而第三个是一个Advisor,前两者一个相当于切点,用于判断是否加了@Transactional注解,另一个是一个拦截器,具体事务管理的逻辑就是在该拦截器的invoke()方法中实现的。
整体的执行流程:当创建一个Bean时,会去执行AbstractAutoProxyCreator的postProcessAfterInitialization,在这个方法中会去判断是否需要为该Bean生成代理对象。这时会去获取所有的Advisor,然后遍历并从中找出与该Bean相匹配的,此时就会用BeanFactoryTransactionAttributeSourceAdvisor的属性AnnotationTransactionAttributeSource去对该Bean进行判断,如果该Bean或它的方法加了@Transactional注解则匹配成功(注意:非public方法加了该注解也没用,不匹配),将该BeanFactoryTransactionAttributeSourceAdvisor返回。由于返回值不为空,所以就会为该Bean创建代理对象。
当调用该代理对象的方法时,会先去获取该方法的拦截器链(遍历该Bean的所有Advisor,然后找到和该方法相匹配的Advisor)。BeanFactoryTransactionAttributeSourceAdvisor的属性AnnotationTransactionAttributeSource会去判断该方法是否有@Transactional注解,如果有则匹配成功,则将该Advisor的属性TransactionInterceptor添加到拦截器链中。然后从头开始遍历拦截器链(通过递归调用proceed()方法完成遍历),当执行TransactionInterceptor的invoke()方法时,会先去开启一个事务,然后再去递归调用proceed()方法向下遍历,直至执行完业务方法,如果在这个过程中出现了异常就回滚事务,否则就提交事务。
在整个事务过程中,如何保证操作数据库时使用的是同一个连接?在开启事务时,首先会从数据库连接池中获得一个connection,然后将这个连接与一个ThradLocal对象绑定起来,以后需要操作数据库时都通过该ThradLocal对象来获取connection,最后在事务提交或回滚后释放绑定关系,并将connection归还到数据库连接池中。这样,通过ThradLocal对象,我们就保证了操作的是同一个connection。(ThradLocal相关知识移步:https://blog.csdn.net/zhang_qing_yun/article/details/118884946)
@Transactional失效的场景
- 非public的方法。原因就是在为该Bean寻找匹配的Advisor时,如果是非public方法加了该注解会忽视掉。
- 使用final修饰的方法在使用CGLIB动态代理时。原因就是CGLIB并不会代理final方法。
- 类内部的非事务方法调用事务方法。原因就是,事务方法的调用是通过this来完成的,跳过了代理对象。
- 自己捕获了异常。原因就是没有抛出新的异常,导致事务操作不会进行回滚。
具体可以参考:https://blog.csdn.net/qq_20597727/article/details/84900994