Spring 源码分析:不得不重视的 Transaction 事务(一)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 今天,正式介绍一下Java极客技术知识星球SpringBoot 精髓之 SpringBoot-starterSpring 源码学习(八) AOP 使用和实现原理Java:前程似锦的 NIO 2.0我们谈谈面试技巧(初入职场年轻人该学的)

业务系统的数据,一般最后都会落入到数据库中,例如 MySQLOracle 等主流数据库,不可避免的,在数据更新时,有可能会遇到错误,这时需要将之前的数据更新操作撤回,避免错误数据。

Spring 的声明式事务能帮我们处理回滚操作,让我们不需要去关注数据库底层的事务操作,可以不用在出现异常情况下,在 try / catch / finaly 中手写回滚操作。

Spring 的事务保证程度比行业中其它技术(例如 TCC / 2PC / 3PC 等)稍弱一些,但使用 Spring 事务已经满足大部分场景,所以它的使用和配置规则也是值得学习的。

接下来一起学习 Spring 事务是如何使用以及实现原理吧。

使用例子

1.创建数据库表

create table test.user(
id int auto_increment    
primary key,
name varchar(20) null, age int(3) null)
engine=InnoDB charset=utf8;

2.创建对应数据库表的 PO

public class JdbcUser {
    private Integer id;
    private String name;
    private Integer age;
    ...(使用 ctrl + N 进行代码补全 setter 和 getter)
}

3.创建表与实体间的映射

在使用 JdbcTemplate 时很纠结,在 Java 类中写了很多硬编码 SQL,与 MyBatis 使用方法不一样,为了示例简单,使用了 JdbcTemplate,不过还是建议朋友们用 MyBatis,让代码风格整洁。

public class UserRowMapper implements RowMapper {
    @Override
    public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
        JdbcUser user = new JdbcUser();
        user.setId(rs.getInt("id"));
        user.setName(rs.getString("name"));
        user.setAge(rs.getInt("age"));
        return user;
    }
}

4.创建数据操作接口

public interface UserDao {
    /**
     * 插入
     * @param user  用户信息
     */
    void insertUser(JdbcUser user);
    /**
     * 根据 id 进行删除
     * @param id    主键
     */
    void deleteById(Integer id);
    /**
     * 查询
     * @return  全部
     */
    List<JdbcUser> selectAll();
}

5.创建数据操作接口实现类

业务系统的数据,一般最后都会落入到数据库中,例如 MySQLOracle 等主流数据库,不可避免的,在数据更新时,有可能会遇到错误,这时需要将之前的数据更新操作撤回,避免错误数据。

Spring 的声明式事务能帮我们处理回滚操作,让我们不需要去关注数据库底层的事务操作,可以不用在出现异常情况下,在 try / catch / finaly 中手写回滚操作。

Spring 的事务保证程度比行业中其它技术(例如 TCC / 2PC / 3PC 等)稍弱一些,但使用 Spring 事务已经满足大部分场景,所以它的使用和配置规则也是值得学习的。

接下来一起学习 Spring 事务是如何使用以及实现原理吧。

使用例子

1.创建数据库表

create table test.user(
id int auto_increment    
primary key,
name varchar(20) null, age int(3) null)
engine=InnoDB charset=utf8;

2.创建对应数据库表的 PO

public class JdbcUser {
    private Integer id;
    private String name;
    private Integer age;
    ...(使用 ctrl + N 进行代码补全 setter 和 getter)
}

3.创建表与实体间的映射

在使用 JdbcTemplate 时很纠结,在 Java 类中写了很多硬编码 SQL,与 MyBatis 使用方法不一样,为了示例简单,使用了 JdbcTemplate,不过还是建议朋友们用 MyBatis,让代码风格整洁。

public class UserRowMapper implements RowMapper {
    @Override
    public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
        JdbcUser user = new JdbcUser();
        user.setId(rs.getInt("id"));
        user.setName(rs.getString("name"));
        user.setAge(rs.getInt("age"));
        return user;
    }
}

4.创建数据操作接口

public interface UserDao {
    /**
     * 插入
     * @param user  用户信息
     */
    void insertUser(JdbcUser user);
    /**
     * 根据 id 进行删除
     * @param id    主键
     */
    void deleteById(Integer id);
    /**
     * 查询
     * @return  全部
     */
    List<JdbcUser> selectAll();
}

5.创建数据操作接口实现类

具体位置在:org.springframework.transaction.annotation.Transactional

属性 类型 作用
value String 可选的限定描述符,指定使用的事务管理器
propagation 枚举:Propagation 可选的事务传播行为
isolation 枚举:Isolation 可选的事务隔离级别设置
readOnly boolean 设置读写或只读事务,默认是只读
rollbackFor Class 数组,必须继承自 Throwable 导致事务回滚的异常类数组
rollbackForClassName 类名称数组,必须继承自 Throwable
noRollbackFor Class 数组,必须继承自 Throwable 不会导致事务回滚的异常类数组
noRollbackForClassName 类名称数组,必须继承自 Throwable

事务的传播性 Propagation

  • REQUIRED

这是默认的传播属性,如果外部调用方有事务,将会加入到事务,没有的话新建一个。

  • PROPAGATION_SUPPORTS

如果当前存在事务,则加入到该事务;如果当前没有事务,则以非事务的方式继续运行。

  • PROPAGATION_NOT_SUPPORTED

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

  • PROPAGATION_NEVER

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


事务的隔离性 Isolation

  • READ_UNCOMMITTED

最低级别,只能保证不读取

物理上损害的数据,允许脏读

  • READ_COMMITTED

只能读到已经提交的数据

  • REPEATABLE_READ

可重复读

  • SERIALIZABLE

串行化读,读写相互阻塞

这里只是简单描述了一下这两个主要属性,因为底层与数据库相关,可以看下我之前整理过的 MySQL锁机制


Spring 中实现逻辑

介绍完如何使用还有关键属性设定,本着知其然,知其所以然的学习精神,来了解代码是如何实现的吧。


解析

之前在解析自定义标签时提到,AOPTX 都使用了自定义标签,按照我们上一篇 AOP 的学习,再来一遍解析自定义标签的套路:事务自定义标签。

定位到 TxNamespaceHandler 类的初始化方法:

@Override
public void init() {
    registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
    // 使用 AnnotationDrivenBeanDefinitionParser 解析器,解析 annotation-driven 标签
    registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
    registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
}

根据上面的方法,Spring 在初始化时候,如果遇到诸如 <tx:annotation-driven> 开头的配置后,将会使用 AnnotationDrivenBeanDefinitionParser 解析器的 parse 方法进行解析。

public BeanDefinition parse(Element element, ParserContext parserContext) {
    registerTransactionalEventListenerFactory(parserContext);
    String mode = element.getAttribute("mode");
    // AspectJ 另外处理
    if ("aspectj".equals(mode)) {
        // mode="aspectj"
        registerTransactionAspect(element, parserContext);
        if (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader())) {
            registerJtaTransactionAspect(element, parserContext);
        }
    }
    else {
        // mode="proxy"
        AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
    }
    return null;
}

Spring 中的事务默认是以 AOP 为基础,如果需要使用 AspectJ 的方式进行事务切入,需要在 mode 属性中配置:

<tx:annotation-driven mode="aspectj"/>

本篇笔记主要围绕着默认实现方式,动态 AOP 来学习,如果对于 AspectJ 实现感兴趣请查阅更多资料~


注册 InfrastructureAdvisorAutoProxyCreator

AOP 一样,在解析时,会创建一个自动创建代理器,在事务 TX 模块中,使用的是 InfrastructureAdvisorAutoProxyCreator

首先来看,在默认配置情况下,AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext) 做了什么操作:

private static class AopAutoProxyConfigurer {
    public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
        // 注册 InfrastructureAdvisorAutoProxyCreator 自动创建代理器
        AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
        // txAdvisorBeanName = org.springframework.transaction.config.internalTransactionAdvisor
        String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;
        if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {
            Object eleSource = parserContext.extractSource(element);
            // Create the TransactionAttributeSource definition.
            // 创建 TransactionAttributeSource 的 bean
            RootBeanDefinition sourceDef = new RootBeanDefinition(
                    "org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
            // 注册 bean,并使用 Spring 中的定义规则生成 beanName
            String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
            // 创建 TransactionInterceptor 的 bean
            RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
            interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
            String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
            // 创建 TransactionAttributeSourceAdvisor 的 bean
            RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
            // 将 sourceName 的 bean 注入 advisor 的 transactionAttributeSource 属性中
            advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
            // 将 interceptorName 的 bean 注入到 advisor 的 adviceBeanName 属性中
            advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
            if (element.hasAttribute("order")) {
                // 如果配置了 order 属性,则加入到 bean 中
                advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
            }
            // 以 txAdvisorBeanName 名字注册 advisorDef
            parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);
            // 创建 CompositeComponentDefinition
            CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
            compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
            compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
            compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName));
            parserContext.registerComponent(compositeDef);
        }
    }
}

在这里注册了代理类和三个 bean,这三个关键 bean 支撑了整个事务功能,为了待会更好的理解这三者的关联关系,我们先来回顾下 AOP 的核心概念:

  1. Pointcut
    定义一个切点,可以在这个被拦截的方法前后进行切面逻辑。
  2. Advice
    用来定义拦截行为,在这里实现增强的逻辑,它是一个祖先接口 org.aopalliance.aop.Advice。还有其它继承接口,例如 MethodBeforeAdvice ,特定指方法执行前的增强。
  3. Advisor
    用来封装切面的所有信息,主要是上面两个,它用来充当 AdvicePointcut 的适配器。

7.jpg

回顾完 AOP 的概念后,继续来看下这三个关键 bean:

  • TransactionInterceptor: 实现了 Advice 接口,在这里定义了拦截行为。
  • AnnotationTransactionAttributeSource:封装了目标方法是否被拦截的逻辑,虽然没有实现 Pointcut 接口,但是在后面目标方法判断的时候,实际上还是委托给了 AnnotationTransactionAttributeSource.getTransactionAttributeSource,通过适配器模式,返回了 Pointcut 切点信息。
  • TransactionAttributeSourceAdvisor: 实现了 Advisor 接口,包装了上面两个信息。

这三个 bean 组成的结构与 AOP 切面环绕实现的结构一致,所以先学习 AOP 的实现,对事务的了解会有所帮助

相关文章
|
3月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
133 2
|
3月前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
85 1
|
3月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
161 5
|
23天前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
42 6
|
2月前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
1月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
|
1月前
|
SQL Java 关系型数据库
【SpringFramework】Spring事务
本文简述Spring中数据库及事务相关衍伸知识点。
50 9
|
2月前
|
Java 开发者 Spring
理解和解决Spring框架中的事务自调用问题
事务自调用问题是由于 Spring AOP 代理机制引起的,当方法在同一个类内部自调用时,事务注解将失效。通过使用代理对象调用、将事务逻辑分离到不同类中或使用 AspectJ 模式,可以有效解决这一问题。理解和解决这一问题,对于保证 Spring 应用中的事务管理正确性至关重要。掌握这些技巧,可以提高开发效率和代码的健壮性。
128 13
|
2月前
|
缓存 安全 Java
Spring高手之路26——全方位掌握事务监听器
本文深入探讨了Spring事务监听器的设计与实现,包括通过TransactionSynchronization接口和@TransactionalEventListener注解实现事务监听器的方法,并通过实例详细展示了如何在事务生命周期的不同阶段执行自定义逻辑,提供了实际应用场景中的最佳实践。
76 2
Spring高手之路26——全方位掌握事务监听器
|
2月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
74 2