「聊一聊Spring」如何正确打开 Spring 事务?
作者:互联网
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 的实现有很多,这个接口将不同的事务管理方式进行了统一,以便可以用相同的方式在 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