ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

「聊一聊Spring」如何正确打开 Spring 事务?

2022-01-10 08:33:48  阅读:106  来源: 互联网

标签:回滚 return int Spring 事务 聊一聊 public


Spring 事务传播行为是 Spring 中一个常见的面试题,它贯穿于 Spring 的事务管理中,因此想要理解 Spring 事务传播行为,首先要对 Spring 的事务管理有一个整体的认识。

Spring 事务基于 Java,而 Java 已经提出了一套 JDBC 规范用于操作数据库,使用 JDBC 的 API,我们可以轻松的提交回滚事务,那么 Spring 为什么又提供一套事务解决方案呢?

由于 JDBC 操作数据库的 API 使用较为繁琐,直接使用 JDBC 会产生大量的样板式代码,各持久层框架(规范)均对 JDBC 进行封装,提供了不同的事务使用方式,包括 JPA、JTA、Hibernate 等等,并且有的框架可能会对应用运行环境产生一定的依赖,为了解决这些问题,Spring 提供了一个 spring-tx 的模块,提供了统一的事务使用方式,并且易于扩展,如 MyBatis 也可以直接使用 Spring 的事务管理。

声明式事务

Spring 的事务使用方式分为声明式事务和编程式事务。

声明式事务直接在 XML 或注解中进行配置即可,使用此方式 Spring 对业务代码的侵入性较小。Spring 同时支持使用 XML 和注解进行事务相关配置。

假定我们有如下的业务类,类中的方法均会操作数据库。

public interface IService {

   Object getOne(Integer id);

   int insert(Object obj);

   int update(Object obj);

   int delete(Integer id);

}

@Data
public class ServiceImpl implements IService {

   private JdbcTemplate jdbcTemplate;

   @Override
   public Object getOne(Integer id) {
       return null;
   }

   @Override
   public int insert(Object obj) {
       return 0;
   }

   @Override
   public int update(Object obj) {
       return 0;
   }

   @Override
   public int delete(Integer id) {
       return 0;
   }
}

假定我们想通过 spring-jdbc 模块提供的 JdbcTemplate 来操作数据库,我们在 ServiceImpl 类中引入了 JdbcTemplate 类型的变量,然后在方法中使用 JdbcTemplate 操作数据库即可,无需考虑事务何时创建、提交、回滚。

那么这样就可以了吗?显然没有那么简单,如果仅仅只是使用 JdbcTemplate ,那么使用 JdbcTemplate 操作数据库,每条 SQL 都将是一个事务,达不到我们想要的效果。

XML 声明式事务

先考虑使用 XML 配置 Spring 事务。代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       https://www.springframework.org/schema/tx/spring-tx.xsd">


    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/test"/>
        <property name="username" value="root"/>
        <property name="password" value="12345678"/>
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="service" class="com.zzuhkp.blog.transaction.ServiceImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" rollback-for="java.lang.Exception"/>
            <tx:method name="get*" rollback-for="java.lang.Exception" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="servicePointcut" expression="execution(* com.zzuhkp.blog.transaction.IService.*(..))"/>
        <aop:advisor pointcut-ref="servicePointcut" advice-ref="transactionAdvice"/>
    </aop:config>

</beans>

Spring 应用上下文 XML 配置中,我们依次配置了 dataSource、jdbcTemplate、service 三个 bean,事实上,有了这三个配置之后,service 的功能已经可以正常执行,但是 JdbcTemplate 每次执行 SQL 都将开启一个事务,不满足我们的需求,因此还需要后面的配置。

在后面的配置中,我们又分别添加了 transactionManager、transactionAdvice、advisor 作为 bean。

我们配置的 transactionManager 类型为 DataSourceTransactionManager,这是 PlatformTranasactionManager 的子类,PlatformTranasactionManager 是 Spring 事务的核心接口,提供了统一的事务管理抽象,不同的环境使用不同的实现即可。在这里需要注意的是一定要保证 transactionManager 和 jdbcTemplate 引用了同一个 dataSource,这样 Spring 事务管理才会生效。

transactionAdvice bean 依赖 transactionManager,可以通过 tx:method 元素节点配置不同的方法的事务行为,如在上面的配置中,我们希望对 get 开头的方法优化为只读事务。tx:method 中可以配置的属性如下:

属性名 默认值 含义
name   方法名,可以使用通配符,遵循 ant 风格;必填
propagation REQUIRED 事务传播行为;可取值包括 REQUIRED | SUPPORTS | MANDATORY | REQUIRES_NEW | NOT_SUPPORTED | NEVER | NESTED
isolation DEFAULT 事务隔离级别;可取值包括 DEFAULT | READ_UNCOMMITTED | READ_COMMITTED | REPEATABLE_READ | SERIALIZABLE
timeout -1 事务超时时间,单位:秒
read-only false 是否为只读事务
rollback-for   异常类;遇到该异常回滚事务;默认 RuntimeException 或 Error 异常会回滚事务
no-rollback-for   异常类;遇到该异常时不要回滚事务

transactionAdvice 用于在目标方法前后添加事务相关的行为,而 advisor 将 pointcut、advice 整合到一起,因此后面我们还配置了一个 advisor。

至此事务相关配置结束,此时业务代码对 Spring 的事务管理是无感的,但是在 XML 配置文件中产生了大量有关 AOP、事务相关的配置,这要求用户对 Spring AOP 和 事务管理有一定了解,因此提高了使用 Spring 事务管理的门槛,为了解决这个问题,我们可以通过注解的方式配置事务。

注解声明式事务

通过注解的方式使用 Spring 事务管理,首先需要开启这个功能,有两种方式:

  • Spring XML 配置文件中配置 <tx:annotation-driven/>。
  • Spring 配置类上添加 @EnableTransactionManagement 注解。

开启注解后还需要在 Spring 中配置 TransactionManager 作为 bean,通常使用的实现为 DataSourceTransactionManager,如果引入了 spring-boot-starter-jdbc,则无需显式配置 TransactionManager,而只需要配置一个 Datasource 即可。

开启注解支持后需要在 Spring Bean 的类或方法上使用 @Transactional 注解。

将我们 XML 中的配置转换为注解,代码如下:

@Data
@Transactional(rollbackFor = Exception.class)
public class ServiceImpl implements IService {

    private JdbcTemplate jdbcTemplate;

    @Transactional(readOnly = true, rollbackFor = Exception.class)
    @Override
    public Object getOne(Integer id) {
        return null;
    }

    @Override
    public int insert(Object obj) {
        return 0;
    }

    @Override
    public int update(Object obj) {
        return 0;
    }

    @Override
    public int delete(Integer id) {
        return 0;
    }
}

这里只对 ServiceImpl 做了两处改造:

  • 第一处在类上添加了 @Transactional 注解,默认方法执行时遇到 Exception 异常时回滚事务。
  • 第二处在 getOne 方法上添加了 @Transactional 注解,并且设置了 rollbackFor 为 true,表示这是一个只读事务。

可以看到 @Transactional 和 XML 配置中的 tx:method 元素节点的功能类似,都是提供创建事务的元信息。@Transactional 注解可以同时添加到类和方法上,优先级从高到底如下:

  • 子类方法上的 @Transactional
  • 父类方法上的 @Transactionoal
  • 子类上的 @Transactional
  • 父类上的 @Transactional

@Transactional 注解可配置的属性如下:

属性名 默认值 含义
value   事务管理器的 bean 名称;transactionManager 的别名
transactionManager   事务管理器的 bean 名称;value 的别名
propagation Propagation.REQUIRED 事务传播行为;可取值与 xml 配置相似,参见 Propagation 类
isolation Isolation.DEFAULT 事务隔离级别;可取值与 xml 配置相似,参见 Isolation
timeout -1 事务超时时间,单位:秒
readOnly false 是否为只读事务
rollbackFor   导致回滚的异常 Class 数组
rollbackForClassName   导致回滚的异常类名称数组
noRollbackFor   不会导致回滚的异常 Class 数组
noRollbackForClassName   不会导致回滚的异常类名称数组

编程式事务

编程式事务是指直接在代码中使用 Spring 提供的事务 API 开启事务支持,因此这种方式对代码的侵入性较强,但灵活性相对较高。

PlatformTransactionManager

前面在 XML 的配置中,我们配置了一个 DataSourceTransactionManager,这是 PlatformTransactionManager 的子类,在 springframework 5.2 版本之前,PlatformTransactionManager 是事务管理的核心接口,我们也主要使用这个接口以编程的方式使用 Spring 事务,5.2 版本时又提出了一个 ReactiveTransactionManager 接口。PlatformTransactionManager 相关类图如下:

PlatformTransactionManager 类图

可以看到 PlatformTransactionManager 的实现有很多,这个接口将不同的事务管理方式进行了统一,以便可以用相同的方式在 Spring 中使用事务。使用这个类需要先了解它提供的 API,接口定义如下:

public interface PlatformTransactionManager extends TransactionManager {
	// 开启事务
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

	// 提交事务
	void commit(TransactionStatus status) throws TransactionException;

	// 回滚事务
	void rollback(TransactionStatus status) throws TransactionException;
}

可以看到 PlatformTransactionManager 将事务的控制权全部交给用户,由用户决定何时开启、提交、回滚事务。可以猜想到,声明式事务的底层也是使用了 PlatformTransactionManager,然后结合 AOP,方法执行前创建事务,方法结束后提交或回滚事务。

PlatformTransactionManager 接口的方法中,我们遇到了两个新的类,一个是 TransactionDefinition,这是开启事务时使用的元数据,XML 配置中的 tx:method 元素节点和注解配置中的 @Transactional 属性都将转换为这个接口,接口的定义如下:

public interface TransactionDefinition {
	// 获取事务传播行为
	default int getPropagationBehavior() {
		return PROPAGATION_REQUIRED;
	}
	// 获取事务隔离级别
	default int getIsolationLevel() {
		return ISOLATION_DEFAULT;
	}	
	// 获取超时时间,单位:秒
	default int getTimeout() {
		return TIMEOUT_DEFAULT;
	}
	// 事务是否只读
	default boolean isReadOnly() {
		return false;
	}	
	// 获取事务名称
	@Nullable
	default String getName() {
		return null;
	}
}

TransactionStatus 主要包含了创建后的事务的一些元信息,经常调用的一个方法是#setRollbackOnly,将事务设置为仅回滚,这样在声明式事务中就不用以抛出异常的方式回滚事务,Spring 在提交事务时进行检查以决定是提交事务还是回滚事务。

PlatformTransactionManager 使用示例如下:

public class ServiceImpl implements IService {

    private JdbcTemplate jdbcTemplate;

    private PlatformTransactionManager platformTransactionManager;

    public ServiceImpl(DataSource dataSource) {
        jdbcTemplate = new JdbcTemplate(dataSource);
        platformTransactionManager = new DataSourceTransactionManager(dataSource);
    }
    
    @Override
    public int insert(Object obj) {
        DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        
        TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
        try {
          return jdbcTemplate.update("insert into user(id,username,password) values (1,'hkp','123456')");
        } catch (Exception e) {
            platformTransactionManager.rollback(transactionStatus);
            throw e;
        }
        platformTransactionManager.commit(transactionStatus);
        return 0;
    } 
}

TransactionTemplate

在声明式事务中,通过注解的方式,业务代码可以专注于自己的逻辑,而不需要把精力放在事务中,那么在编程式事务中有没有合适的方式简化事务的使用呢?

为了简化编程式事务的使用,Spring 提供了一个 TransactionTemplate 类,这个类对 PlatformTransactionManager 封装,接受一个回调,用户只需要专注于业务代码即可,同时它也继承了 DefaultTransactionDefinition,因此也可以直接设置事务属性。使用示例如下:

public class ServiceImpl implements IService {

    private JdbcTemplate jdbcTemplate;

    private TransactionTemplate transactionTemplate;

    public ServiceImpl(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.transactionTemplate = new TransactionTemplate(new DataSourceTransactionManager(dataSource));
    }

    @Override
    public int insert(Object obj) {
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        int count = transactionTemplate.execute(new TransactionCallback<Integer>() {
            @Override
            public Integer doInTransaction(TransactionStatus status) {
                return jdbcTemplate.update("insert into user(id,username,password) values (1,'hkp','123456')");
            }
        });
        return count;
    }
}

 

吃水不忘挖井人:

 

标签:回滚,return,int,Spring,事务,聊一聊,public
来源: https://www.cnblogs.com/xfeiyun/p/15782998.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有