前言
本文为 【Spring】事务管理 相关知识,下边将对Spring框架事务支持模型的优点
,Spring框架的事务抽象的理解
(包含TransactionManager
、TransactionDefinition
、TransactionStatus
,编程式事务管理
(包含使用 TransactionManager
、使用TransactionTemplate
),声明式事务管理
(包含理解Spring框架的声明式事务
,声明式事务实现的示例
,事务回滚
,``tx:advice/ 设置
,使用 @Transactional
,@Transactional的设置
,带 @Transactional的多个事务管理器
,自定义注解组成
),事务传播
,及编程式和声明式事务管理之间进行选择
等进行详尽介绍~
????博主主页:小新要变强 的主页
????Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
????算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
????Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)
↩️本文上接:XXXX
目录
【Spring】事务管理
一、Spring框架事务支持模型的优点
- 全面的事务支持是使用Spring框架最令人信服的原因之一。 Spring Framework为事务管理提供了一个一致的抽象,给我们的开发带来了极大的便利。
- Spring允许应用程序开发人员在任何环境中使用【一致的编程模型】。 只需编写一次代码,它就可以从不同环境中的不同事务管理策略中获益。
- Spring框架同时提供【声明式】和【编程式】事务管理。 大多数用户更喜欢【声明式事务管理】,这也是我们在大多数情况下所推荐的。
- 使用声明式模型,开发人员通常【很少或不编写】与事务管理相关的代码,因此,不依赖于Spring
Framework事务API或任何其他事务API,也就是啥也不用写。
二、理解Spring框架的事务抽象
spring事务对事务抽象提现在一下三个类中:PlatformTransactionManager
,TransactionDefinition
,TransactionStatus
。
1️⃣TransactionManager
TransactionManage主要有一下两个子接口:
-
org.springframework.transaction.PlatformTransactionManager
接口用于为不同平台提供统一抽象的事务管理器,重要。 -
org.springframework.transaction.ReactiveTransactionManager
接口用于响应式事务管理,这个不重要。
下面显示了’ PlatformTransactionManager ’ API的定义:
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
任何【PlatformTransactionManager】接口实现类的方法抛出的【TransactionException】是未检查的 (也就是说,它继承了【java.lang.RuntimeException】的类)。 这里边隐藏了一个知识点,我们后续再说。
public abstract class TransactionException extends NestedRuntimeException {
public TransactionException(String msg) {
super(msg);
}
public TransactionException(String msg, Throwable cause) {
super(msg, cause);
}
}
任何一个【TransactionManager】的实现通常需要了解它们工作的环境:JDBC、mybatis、Hibernate等等。 下面的示例展示了如何定义一个本地的【PlatformTransactionManager】实现(在本例中,使用纯JDBC)。
你可以通过创建一个类似于下面这样的bean来定义JDBC ’ DataSource ':
username=root
password=root
url=jdbc:mysql://127.0.0.1:3306/ydlclass?characterEncoding=utf8&serverTimezone=Asia/Shanghai
driverName=com.mysql.cj.jdbc.Driver
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driverName}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<property name="url" value="${url}"/>
</bean>
</beans>
DataSourceTransactionManager是PlatformTransactionManager的一个子类,他需要一个数据源进行注入:
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
注意点,在DataSourceTransactionManager源码中有这么一句话,将线程的持有者绑定到线程当中:
// Bind the connection holder to the thread.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
从这里我们也能大致明白,PlatformTransactionManager的事务是和线程绑定的,事务的获取是从当前线程中获取的。
2️⃣TransactionDefinition
TransactionDefinition
接口指定了当前事务的相关配置,主要配置如下:
- Propagation: 通常情况下,事务范围内的所有代码都在该事务中运行。 但是,如果事务方法在 【已经存在事务的上下文中运行】,则可以指定事务的【传播行为】。
- Isolation: 该事务与其他事务的工作隔离的程度。 例如,这个事务可以看到其他事务未提交的写入吗? 【隔离级别】
- Timeout: 该事务在超时并被底层事务基础设施自动回滚之前运行多长时间。
- 只读状态: 当代码读取但不修改数据时,可以使用只读事务。 在某些情况下,如使用Hibernate时,只读事务可能是一种有用的优化。
public interface TransactionDefinition {
/**
* Support a current transaction; create a new one if none exists.
*/
int PROPAGATION_REQUIRED = 0;
/**
* Support a current transaction; execute non-transactionally if none exists.
*/
int PROPAGATION_SUPPORTS = 1;
/**
* Support a current transaction; throw an exception if no current transaction
*/
int PROPAGATION_MANDATORY = 2;
/**
* Create a new transaction, suspending the current transaction if one exists.
*/
int PROPAGATION_REQUIRES_NEW = 3;
/**
* Do not support a current transaction; rather always execute non-transactionally.
*/
int PROPAGATION_NOT_SUPPORTED = 4;
/**
* Do not support a current transaction; throw an exception if a current transaction
*/
int PROPAGATION_NEVER = 5;
/**
* Execute within a nested transaction if a current transaction exists,
*/
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
/**
* Use the default timeout of the underlying transaction system,
* or none if timeouts are not supported.
*/
int TIMEOUT_DEFAULT = -1;
}
这个接口有一个默认实现:
public class DefaultTransactionDefinition implements TransactionDefinition, Serializable {
private int propagationBehavior = PROPAGATION_REQUIRED;
private int isolationLevel = ISOLATION_DEFAULT;
private int timeout = TIMEOUT_DEFAULT;
private boolean readOnly = false;
//....
}
3️⃣TransactionStatus
TransactionStatus
接口为事务代码提供了一种简单的方法来控制事务执行和查询事务状态。下面的例子显示了TransactionStatus
接口:
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
@Override
//返回当前事务是否是新的; 否则将参与现有事务,或者可能从一开始就不在实际事务中运行。
boolean isNewTransaction();
boolean hasSavepoint();
@Override
// 只设置事务回滚。 这指示事务管理器,事务的唯一可能结果可能是回滚,而不是抛出异常,从而触发回滚。
void setRollbackOnly();
@Override
// 返回事务是否被标记为仅回滚(由应用程序或事务基础设施)。
boolean isRollbackOnly();
void flush();
@Override
// 返回该事务是否已完成,即是否已提交或回滚。
boolean isCompleted();
}
三、编程式事务管理
Spring Framework提供了两种编程式事务管理的方法:
- 使用
TransactionTemplate
。 - 使用
TransactionManager
。
1️⃣使用 TransactionManager
????使用 PlatformTransactionManager
我们可以直接使用【org.springframework.transaction.PlatformTransactionManager】直接管理事务。 为此,通过bean引用将您使用的PlatformTransactionManager
的实现传递给您的bean。 然后,通过使用TransactionDefinition
和 TransactionStatus
对象,您可以发起事务、回滚和提交。 下面的示例显示了如何这样做:
给容器注入对应的事务管理器:
<context:property-placeholder location="jdbc.properties"/>
<context:component-scan base-package="com.ydlclass"/>
<!-- 注入事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 注入事务管理器 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${url}"/>
<property name="driverClassName" value="${driverName}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
</bean>
注入对应的service:
@Override
public void transfer(String from, String to, Integer money) {
// 默认的事务配置
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
// 使用事务管理器进行事务管理
TransactionStatus transaction = transactionManager.getTransaction(definition);
try{
// 转账其实是两个语句
String moneyFrom = "update account set money = money - ? where username = ? ";
String moneyTo = "update account set money = money + ? where username = ? ";
// 从转账的人处扣钱
jdbcTemplate.update(moneyFrom,money,from);
int i = 1/0;
jdbcTemplate.update(moneyTo,money,to);
}catch (RuntimeException exception){
exception.printStackTrace();
// 回滚
transactionManager.rollback(transaction);
}
// 提交
transactionManager.commit(transaction);
}
2️⃣使用TransactionTemplate
【TransactionTemplate】采用了与其他Spring模板相同的方法,比如【JdbcTemplate】。 它使用回调方法将应用程序代码从获取和释放事务性资源的样板程序中解放出来,因为您的代码只关注您想要做的事情,而不是希望将大量的时间浪费在这里。
正如下面的示例所示,使用【TransactionTemplate】绝对会将您与Spring的事务基础设施和api耦合在一起。 编程事务管理是否适合您的开发需求,这是您必须自己做出的决定。
必须在事务上下文中运行并显式使用TransactionTemplate
的应用程序代码类似于下一个示例。 您作为一个应用程序开发人员,可以编写一个TransactionCallback
实现(通常表示为一个匿名内部类),其中包含您需要在事务上下文中运行的代码。 然后你可以将你的自定义 TransactionCallback
的一个实例传递给TransactionTemplate
中暴露的 execute(..)
方法。 下面的示例显示了如何这样做:
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
如果没有返回值,你可以在匿名类中使用方便的TransactionCallbackWithoutResult
类,如下所示:
@Override
public void transfer3(String from, String to, Integer money) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// 转账其实是两个语句
String moneyFrom = "update account set money = money - ? where username = ? ";
String moneyTo = "update account set money = money + ? where username = ? ";
// 从转账的人处扣钱
jdbcTemplate.update(moneyFrom, money, from);
// int i = 1 / 0;
jdbcTemplate.update(moneyTo, money, to);
}
});
}
四、声明式事务管理
大多数Spring框架用户选择声明式事务管理。 该选项对应用程序代码的影响最小,因此最符合非侵入式轻量级容器的理想。
Spring框架的声明性事务管理是通过Spring面向切面编程(AOP)实现的。 然而,由于事务切面代码随Spring Framework发行版一起提供,并且可以模板的方式使用,所以通常不需要理解AOP概念就可以有效地使用这些代码。
1️⃣理解Spring框架的声明式事务
Spring框架的声明式事务通过AOP代理进行实现,事务的通知是由AOP元数据与事务性元数据的结合产生了一个AOP代理,该代理使用【TransactionInterceptor】结合适当的【TransactionManager】实现来驱动方法调用的事务。
Spring Framework的【TransactionInterceptor】为命令式和响应式编程模型提供了事务管理。 拦截器通过检查方法返回类型来检测所需的事务管理风格。 事务管理风格会影响需要哪个事务管理器。 命令式事务需要【PlatformTransactionManager】,而响应式事务使用【ReactiveTransactionManager 】实现。
【@Transactional 】通常用于【PlatformTransactionManager 】管理的【线程绑定事务】,将事务暴露给当前执行线程中的所有数据访问操作。(注意:这不会传播到方法中新启动的线程)。
2️⃣声明式事务实现的示例
<!-- from the file 'context.xml' -->
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- ensure that the above transactional advice runs for any execution
of an operation defined by the FooService interface -->
<aop:config>
<aop:pointcut id="point" expression="within(com.ydlclass.service..*)"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="point"/>
</aop:config>
</beans>
3️⃣事务回滚
上一节概述了如何在应用程序中以声明的方式为类(通常是服务层类)指定事务设置的基础知识。 本节描述如何以简单的声明式方式控制事务的回滚。
重点:
在其默认配置中,Spring框架的事务基础结构代码只在运行时、未检查的异常情况下标记事务进行回滚。 也就是说,当抛出的异常是’ RuntimeException ‘的实例或子类时。 (默认情况下,’ Error '实例也会导致回滚)。 事务方法抛出的已检查异常不会导致默认配置的回滚。
您还可以准确地配置哪些“Exception”类型将事务标记为回滚。 下面的XML代码片段演示了如何为一个已检查的、特定于应用程序的“Exception”类型配置回滚:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
如果您不想在抛出异常时回滚事务,您还可以指定“无回滚规则”。 下面的例子告诉Spring框架的事务基础架构,即使面对InstrumentNotFoundException`,也要提交相应的事务:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
当Spring Framework的事务,捕获异常并参考配置的回滚规则来决定是否将事务标记为回滚时,最强匹配规则胜出。 因此,在以下配置的情况下,除了InstrumentNotFoundException之外的任何异常都会导致事务的回滚:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
</tx:attributes>
</tx:advice>
4️⃣<tx:advice/>
设置
本节总结了通过使用<tx:advice/>
标记可以指定的各种事务设置。 默认的<tx:advice/>
设置是:
- 传播行为是
REQUIRED
。 - 隔离级别为
DEFAULT
。 - 事务处于可读写状态。
- 事务超时默认为底层事务系统的默认超时,如果不支持超时,则为none。
- 任何
RuntimeException
触发回滚,而任何选中的Exception
不会。
您可以更改这些默认设置。 下表总结了嵌套在<tx:advice/>
和<tx:attributes/>
标签中的<tx:method/>
标签的各种属性:
属性 | Required? | 默认值 | 描述 |
---|---|---|---|
name |
Yes | 要与事务属性相关联的方法名。 通配符()字符可用于将相同的事务属性设置与许多方法相关联(例如,’ get ‘、’ handle* ‘、’ on*Event '等等)。 | |
propagation |
No | REQUIRED |
事务传播行为。 |
isolation |
No | DEFAULT |
事务隔离级别。 仅适用于’ REQUIRED ‘或’ REQUIRES_NEW '的传播设置。 |
timeout |
No | -1 | 事务超时(秒)。 仅适用于传播’ REQUIRED ‘或’ REQUIRES_NEW '。 |
read-only |
No | false | 读写事务与只读事务。 只适用于’ REQUIRED ‘或’ REQUIRES_NEW '。 |
rollback-for |
No | 触发回滚的“Exception”实例的逗号分隔列表。 例如,“com.foo.MyBusinessException, ServletException”。 | |
no-rollback-for |
No | 不触发回滚的“Exception”实例的逗号分隔列表。 例如,“com.foo.MyBusinessException, ServletException”。 |
5️⃣使用 @Transactional
除了事务配置的基于xml的声明性方法外,还可以使用基于注解的方法。 直接在Java源代码中声明事务语义使声明更接近受影响的代码。 不存在过多耦合的危险,因为要以事务方式使用的代码几乎总是以这种方式部署的。
使用’ @Transactional '注解所提供的易用性可以用一个示例进行最好的说明,下面的文本将对此进行解释。 考虑以下类定义:
// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {
@Override
public Foo getFoo(String fooName) {
// ...
}
@Override
public Foo getFoo(String fooName, String barName) {
// ...
}
@Override
public void insertFoo(Foo foo) {
// ...
}
@Override
public void updateFoo(Foo foo) {
// ...
}
}
在如上所述的类级别上使用,注解指示声明类(及其子类)的所有方法的默认值。 或者,每个方法都可以单独注解。 请注意,类级注解并不适用于类层次结构中的祖先类; 在这种情况下,继承的方法需要在本地重新声明,以便参与子类级别的注解。
当上述POJO类被定义为Spring上下文中的bean时,您可以通过“@Configuration”类中的“@EnableTransactionManagement”注解使bean实例具有事务性。
在XML配置中,<tx:annotation-driven transaction-manager="txManager"/>
标签提供了类似的便利:
<!-- from the file 'context.xml' -->
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- this is the service object that we want to make transactional -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- enable the configuration of transactional behavior based on annotations -->
<!-- a TransactionManager is still required -->
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- (this dependency is defined somewhere else) -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- other <bean/> definitions here -->
</beans>
如果要连接的’ TransactionManager ‘的bean名称为’ TransactionManager ‘,则可以省略’ tx:annotation-driven/ ‘标记中的’ transaction-manager ‘属性。 如果您想要依赖注入的’ TransactionManager ’ bean有任何其他名称,您必须使用’ transaction-manager '属性,如前面的示例所示。
注意:
- (1)当你在Spring的标准配置中使用事务性代理时,你应该【只把@Transactional注解应用到public 的方法上】。如果使用’ @Transactional ‘注解’ protected ‘、’ private’或包可见的方法,则不会引发错误,但已注解的方法中事务不会生效。
- (2)Spring团队建议只使用【 @Transactional】注解来注解具体的类(以及具体类的方法),而不是注解接口。当然,您可以将’ @Transactional '注解放在接口(或接口方法)上,但只有当您使用基于接口的代理时,它才会发挥作用。Java注解的事实并不意味着继承接口,如果使用基于类的代理(proxy-target-class = " true ")或weaving-based方面('模式=“aspectj”),事务设置不认可的代理和编织的基础设施,和对象不是包在一个事务代理。
- (3)在代理模式(这是默认的)中,只有通过代理进入的外部方法调用会被拦截。这意味着,即使被调用的方法被标记为【@Transactional】,自调用(实际上是目标对象中的一个方法调用目标对象的另一个方法)在运行时也不会产生事务。
6️⃣@Transactional的设置
【@Transactional】注解是元数据,它指定接口、类或方法必须具有事务性语义(例如,“在调用此方法时启动一个全新的只读事务,暂停任何现有事务”)。 默认的【@Transactiona】设置如下:
- 传播设置为
PROPAGATION_REQUIRED
。 - 隔离级别为
ISOLATION_DEFAULT
。 - 事务处于可读写状态。
- 事务超时默认为底层事务系统的默认超时,如果不支持超时,则为none。
- 任何
RuntimeException
触发回滚,而任何选中的Exception
不会。
您可以更改这些默认设置。 下表总结了@Transactional
注解的各种属性:
特质 | 类型 | 描述 |
---|---|---|
value | String | 指定要使用的事务管理器的可选限定符。 |
propagation | enum: Propagation | 可选的传播环境。 |
isolation | enum: Isolation | 可选的隔离级别。 仅适用于REQUIRED 或 REQUIRES_NEW的传播值。 |
timeout | int(以秒为粒度) | 可选的事务超时。 仅适用于 REQUIRED 或 REQUIRES_NEW的传播值。 |
readOnly | boolean | 读写事务与只读事务。 只适用于 REQUIRED 或 REQUIRES_NEW的值。 |
rollbackFor | Class 对象的数组,它必须派生自Throwable. | 必须导致回滚的异常类的可选数组。 |
rollbackForClassName | 类名数组。 类必须派生自Throwable. | 必须导致回滚的异常类名称的可选数组。 |
noRollbackFor | Class 对象的数组,它必须派生自Throwable. | 不能导致回滚的异常类的可选数组。 |
noRollbackForClassName | String类名数组,它必须派生自 Throwable. | 异常类名称的可选数组,该数组必须不会导致回滚。 |
label | 数组String标签,用于向事务添加富有表现力的描述。 | 事务管理器可以评估标签,以将特定于实现的行为与实际事务关联起来。 |
目前,您不能显式地控制事务的名称,其中“name”指出现在事务监视器(例如,WebLogic的事务监视器)和日志输出中的【事务名称】。 对于声明性事务,事务名总是完全限定类名+ ‘.’ +事务通知类的方法名。 例如,如果’ BusinessService ‘类的’ handlePayment(…) '方法启动了一个事务,事务的名称将是: com.example.BusinessService.handlePayment
。
7️⃣带 @Transactional的多个事务管理器
大多数Spring应用程序只需要一个事务管理器,但是在某些情况下,您可能希望在一个应用程序中有多个独立的事务管理器。 您可以使用’ @Transactional '注解的【value】或【transactionManager】属性来指定要使用的【transactionManage】的标识。 这可以是bean名,也可以是事务管理器bean的限定符值。 例如,使用限定符表示法,您可以在应用程序上下文中将下列Java代码与下列事务管理器bean声明组合起来:
下面的例子定义了三个事务管理器:
<tx:annotation-driven/>
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="order"/>
</bean>
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="account"/>
</bean>
<bean id="transactionManager3" class="org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager">
...
<qualifier value="reactive-account"/>
</bean>
下面的例子中,不同的事务使用了不同的事务管理器:
public class TransactionalService {
@Transactional("order")
public void setSomething(String name) { ... }
@Transactional("account")
public void doSomething() { ... }
@Transactional("reactive-account")
public Mono<Void> doSomethingReactive() { ... }
}
在这种情况下,【TransactionalService】上的各个方法在单独的事务管理器下运行,通过’ order ‘、’ account ‘和’ reactive-account ‘限定符进行区分。 如果没有找到特别限定的’ transactionManager ’ bean,则仍然使用默认的<tx:annotation-driven>
中定义的bean。
8️⃣自定义注解组成
如果您发现在许多不同的方法上重复使用带有【@Transactional】的相同属性,【Spring的元注解支持】允许您为特定的用例定义自定义的组合注解。 例如,考虑以下注解定义:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "order", label = "causal-consistency")
public @interface OrderTx {
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "account", label = "retryable")
public @interface AccountTx {
}
前面的注解让我们将上一节中的示例编写为如下所示:
public class TransactionalService {
@OrderTx
public void setSomething(String name) {
// ...
}
@AccountTx
public void doSomething() {
// ...
}
}
在前面的示例中,我们使用语法来定义事务管理器限定符和事务标签,但是我们还可以包括传播行为、回滚规则、超时和其他特性。
五、事务传播
事务的传播通常发生在【一个service层中的方法】调用【其他service层中的方法】,虽然我们并不推荐这么做,但是的确会存在一个【大的业务】包含多个【小业务】,大业务和小业务都可独立运行的场景。
比如【销售】中可以包含【增加积分】的操作,而【加积分也可以独立运行】(比如某天搞活动,给老用户直接冲积分),其中销售的方法会有事务,加积分的方法也会有事务,当销售的方法调用加积分的方法时,加积分的【小事务】就被传播到了销售这个【大事务】当中。
当事务发生传播时,一般有以下几种解决方案:
传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED | 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务 |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行 |
PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 |
PROPAGATION_REQUIRED_NEW | 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NOT_SUPPORTED | 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NEVER | 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常 |
PROPAGATION_NESTED | 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务 |
隔离级别 | 含义 |
---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 |
ISOLATION_READ_COMMITTED | 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 |
ISOLATION_REPEATABLE_READ | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生 |
ISOLATION_SERIALIZABLE | 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的 |
六、在编程式和声明式事务管理之间进行选择
只有在有少量事务操作的情况下,编程式事务管理通常是一个好主意。 例如,如果您有一个web应用程序,它只需要为某些更新操作使用事务,那么您可能不希望使用Spring或任何其他技术来设置事务代理。 在这种情况下,使用TransactionTemplate
可能是一种很好的方法。 只有使用事务管理的编程方法才能显式地设置事务名称。
另一方面,如果应用程序有许多事务操作,则声明式事务管理通常是值得的。 它使事务管理远离业务逻辑,并且不难配置。 当使用Spring框架而不是EJB CMT时,声明性事务管理的配置成本大大降低了。
后记
↪️本文下接:XXXX
????Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
????算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~