一、定义
事务(Transaction)是指作为单个逻辑工作单元执行的一系列操作。操作要么全部成功执行,要么全部失败回滚,以确保数据的一致性和完整性。
二、特性
-
原子性(Atomicity):事务被视为不可分割的工作单元,要么全部执行成功,要么全部失败回滚。即使在执行过程中发生了错误,事务也会被回滚到最初的状态,不会留下部分执行的结果。
-
一致性(Consistency):事务在执行前后,数据库中的数据应该保持一致性。这意味着事务的执行不会破坏数据库的完整性约束,如唯一性约束、外键约束等。
-
隔离性(Isolation):多个事务并发执行时,每个事务的操作应该相互隔离,互不干扰。即使多个事务同时对同一数据进行操作,也不应该相互影响,以避免数据的不一致性。
-
持久性(Durability):一旦事务执行成功提交,其结果应该对其他事务可见,并且应该持久保存在系统中,即使系统发生故障或重启,事务的提交结果也不会丢失。
在编程中,特别是在数据库操作中,事务通常用于确保对数据库的一系列操作是原子性的,要么全部成功提交,要么全部回滚。这有助于确保数据的完整性和一致性。
三、事物处理方式
Spring 框架提供了多种方式来处理事务,包括声明式事务管理和编程式事务管理。
-
声明式事务管理:Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。
其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。 -
编程式事务管理:除了声明式事务管理外,Spring 框架还提供了编程式事务管理的方式。通过使用
TransactionTemplate
或PlatformTransactionManager
接口,开发人员可以在代码中显式地控制事务的开始、提交和回滚。
四、声明式事务管理注解方式
1. 启用 Spring 的注释驱动事务管理功能
@EnableTransactionManagement
是 Spring 框架提供的一个注解,用于启用注解驱动的事务管理。一旦 @EnableTransactionManagement
注解被添加,Spring 容器将会自动扫描带有 @Transactional 注解的方法,并为其创建代理以支持事务管理。
在 Spring 应用程序中,@EnableTransactionManagement
注解可以被用于配置类(通常是一个带有 @Configuration
注解的类)和启动类(通常是带有 @SpringBootApplication
注解的类)。虽然在这两种情况下都能够启用注解驱动的事务管理,但它们之间存在一些细微的区别。
配置类上使用 @EnableTransactionManagement
:
-
当
@EnableTransactionManagement
注解被添加到一个专门的配置类上时,通常用于集中配置和管理事务相关的设置,包括数据源、事务管理器等。 -
配置类可以包含其他与应用程序配置相关的 bean 定义,例如数据源、JPA 实体管理工厂、持久化单元等。
-
这种方式使得整个应用程序的事务管理配置更加集中和清晰,同时也使得代码更易于维护和理解。
启动类上使用 @EnableTransactionManagement
:
-
当
@EnableTransactionManagement
注解被添加到 Spring Boot 的启动类上时,通常用于启用整个应用程序范围内的注解驱动的事务管理。 -
启动类是 Spring Boot 应用程序的入口点,通过在启动类上添加
@EnableTransactionManagement
注解,可以方便地启用注解驱动的事务管理,并为整个应用程序启用事务支持。 -
这种方式使得整个应用程序范围内的事务管理配置更加简单,适用于小型应用程序或需要全局事务支持的情况。
总的来说,无论是在配置类上使用 @EnableTransactionManagement 还是在启动类上使用 @EnableTransactionManagement,都可以有效地启用注解驱动的事务管理。选择使用哪种方式取决于应用程序的规模、结构和需求。如果需要更加灵活的事务管理配置,通常会选择在配置类上使用 @EnableTransactionManagement;而如果只需要简单地启用全局的事务支持,可以选择在启动类上使用 @EnableTransactionManagement。
2. 定义事务管理器
事务交给 spring 管理,那么你肯定要创建一个或者多个事务管理者,有这些管理者来管理具体的事务
在 Spring 应用程序中,要定义事务管理器,通常可以通过创建一个 bean 来配置事务管理器。事务管理器负责协调和管理数据库事务的提交和回滚。在 Spring 中,可以使用 PlatformTransactionManager 接口的实现来定义事务管理器,具体取决于应用程序使用的数据访问技术,比如 JDBC、JPA 或 Hibernate 等。
PlatformTransactionManager 多个实现类,用来应对不同的环境
-
JpaTransactionManager: 如果你用 jpa 来操作 db,那么需要用这个管理器来帮你控制事务。
-
DataSourceTransactionManager: 如果你用是指定数据源的方式,比如操作数据库用的是:JdbcTemplate、mybatis、ibatis,那么需要用这个管理器来帮你控制事务。
-
HibernateTransactionManager: 如果你用 hibernate 来操作 db,那么需要用这个管理器来帮你控制事务。
-
JtaTransactionManager: 如果你用的是 java 中的 jta 来操作 db,这种通常是分布式事务,此时需要用这种管理器来控制事务。
以下是一个简单的示例:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
3. 使用 @Transaction
在 Spring 框架中,@Transactional 注解用于声明一个方法是事务性的。通过在方法上添加 @Transactional 注解,开发人员可以指定该方法应该在一个事务中执行。这意味着,在方法执行期间,Spring 框架会自动管理事务的开始、提交、回滚等操作,以确保数据的一致性和完整性。
- 方法级别使用:最常见的用法,放在 public 方法上,那么该方法将被 spring 自动加上事务。
- 放在类上,那么当前类以及其下无限级子类中所有 pubilc 方法将被 spring 自动加上事务。
- 放在接口上,那么接口的实现类中所有 public 都被 spring 自动加上事务。
@Transaction 参数说明
-
value:指定事务管理器的 bean 名称,如果容器中有多事务管理器 PlatformTransactionManager,那么你得告诉 spring,当前配置需要使用哪个事务管理器
-
transactionManager:同 value,value 和 transactionManager 选配一个就行,也可以为空,如果为空,默认会从容器中按照类型查找一个事务管理器 bean
-
propagation:指定事务的传播行为。事务的传播行为定义了在不同事务边界内执行方法时,方法如何参与现有事务或者创建新的事务。可以设置为下列之一:
-
Propagation.REQUIRED
:如果当前存在事务,则加入该事务;如果不存在,则创建一个新的事务。
这是默认的传播行为,适用于大多数情况。它确保方法总是在一个事务中执行。 -
Propagation.REQUIRES_NEW
:始终创建一个新的事务。
使用这种传播行为,方法总是在一个新的事务中执行。即使调用方法的代码已经处于一个事务中,该方法也会挂起当前事务,并创建一个新的事务进行执行。 -
Propagation.SUPPORTS
:如果当前存在事务,则加入该事务;如果不存在事务,则以非事务的方式执行。
这种传播行为表示方法可以在事务中执行,也可以在非事务的环境中执行。如果当前存在事务,则方法将加入该事务;如果没有事务,则方法以非事务的方式执行。 -
Propagation.NOT_SUPPORTED
:以非事务的方式执行。如果当前存在事务,则将其挂起。
使用这种传播行为,方法总是以非事务的方式执行。即使调用方法的代码已经处于一个事务中,该方法也会挂起当前事务,并以非事务的方式执行。 -
Propagation.MANDATORY
:当前方法必须在一个事务中执行,否则会抛出异常。
这种传播行为要求调用方法的代码必须在一个事务中执行,如果没有事务存在,则会抛出异常。 -
Propagation.NEVER
:以非事务的方式执行。如果当前存在事务,则抛出异常。
使用这种传播行为,方法总是以非事务的方式执行。如果调用方法的代码已经处于一个事务中,则会抛出异常。 -
Propagation.NESTED
:如果当前存在事务,则在嵌套事务中执行;如果不存在事务,则创建一个新的事务。
这种传播行为类似于REQUIRED
,但是可以在一个已存在的事务中创建一个嵌套事务。如果当前没有事务,则会创建一个新的事务。
-
-
isolation:指定事务的隔离级别。事务隔离级别定义了在并发环境中,一个事务对数据的读取能力以及对其他事务修改的可见性。可以设置为下列之一:
-
Isolation.DEFAULT
:使用数据库默认的隔离级别。通常情况下,数据库会使用一种默认的隔禅级别,这取决于具体的数据库系统。 -
Isolation.READ_UNCOMMITTED
:一个事务可以读取另一个事务未提交的数据修改。这种隔离级别最低,不保证事务的一致性和隔离性。允许脏读、不可重复读和幻影读。 -
Isolation.READ_COMMITTED
:一个事务只能读取另一个事务已经提交的数据修改。这种隔离级别可以避免脏读,但是仍然可能出现不可重复读和幻影读。 -
Isolation.REPEATABLE_READ
:一个事务在多次读取同一数据时,能够保证读取到的数据是一致的。这种隔离级别可以避免脏读和不可重复读,但是仍然可能出现幻影读。 -
Isolation.SERIALIZABLE
:最高的隔离级别,确保事务可以完全地隔离其他事务的并发操作,避免脏读、不可重复读和幻影读。这种隔离级别对数据库性能有较大的影响,因为可能需要对数据进行严格的锁定。
-
说明
-
脏读(Dirty Read):脏读指的是一个事务读取了另一个事务未提交的数据。当一个事务对数据进行了修改但还未提交时,另一个事务读取到了这些未提交的数据,这就是脏读。如果第一个事务最终回滚了,那么另一个事务读取到的数据就是无效的。
脏读会导致事务读取到不一致甚至无效的数据,破坏了事务的隔离性和一致性。 -
不可重复读(Non-Repeatable Read):不可重复读指的是在一个事务内,多次读取同一数据时,得到了不同的结果。这是因为在事务执行期间,另一个事务对数据进行了修改并提交,导致第一个事务多次读取同一数据时得到了不同的结果。
不可重复读会导致事务在同一数据上得到不一致的结果,也破坏了事务的隔离性和一致性。 -
幻影读(Phantom Read):幻影读指的是在一个事务内,多次执行同一个查询,但得到了不同数量的数据。这是因为在事务执行期间,另一个事务对数据进行了插入或删除,导致第一个事务多次执行相同的查询时得到了不同数量的数据。
-
幻影读会导致事务在相同的查询条件下得到不同数量的数据,同样破坏了事务的隔离性和一致性。
-
timeout:指定事务的超时时间(以秒为单位)。如果事务在指定的时间内未能完成,则会被回滚。
-
readOnly:指定事务是否为只读事务。如果设置为
true
,则表示该事务只读取数据,不进行写操作,可以优化数据库性能。
如果在一个标记为只读事务的方法中进行了数据更新、插入或删除操作,可能会导致事务提交失败或抛出异常。因此,在使用readOnly
参数时,需要确保方法的实际操作与事务的只读属性相符。 -
rollbackFor:指定哪些异常会触发事务回滚。可以设置为一个异常类型的数组,当发生这些异常之一时,事务会回滚。
-
noRollbackFor:指定哪些异常不会触发事务回滚。同样可以设置为一个异常类型的数组。
-
rollbackForClassName:与
rollbackFor
类似,但是接受异常类名的字符串数组,而不是实际的异常类。 -
noRollbackForClassName:与
noRollbackFor
类似,但是接受异常类名的字符串数组,而不是实际的异常类。
这些参数可以在 @Transactional
注解中根据需要进行组合和配置,以便满足特定的事务管理需求。通过灵活地使用这些参数,可以精细地控制事务的行为和特性,以确保数据操作的一致性和完整性。
五、编程式事务管理方式
PlatformTransactionManager
在 Spring 框架中,PlatformTransactionManager
是用于控制事务的关键接口之一。它定义了一系列方法,用于管理事务的生命周期、提交事务以及回滚事务等操作。通过 PlatformTransactionManager
接口,我们可以在 Spring 应用中对事务进行精细化的控制。
以下是使用 PlatformTransactionManager
控制事务的一般步骤:
-
获取
PlatformTransactionManager
的实例。 -
利用获取的实例创建
TransactionDefinition
对象,该对象定义了事务的传播行为、隔禂级别、超时时间等属性。 -
利用
PlatformTransactionManager
开启事务,并获取TransactionStatus
对象,该对象表示了当前事务的状态。 -
在事务内部执行需要进行事务管理的操作。
-
根据执行结果决定提交事务或者回滚事务。
下面是一个简单的示例:
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.PlatformTransactionManager;
public class ProgrammaticTransactionExample {
private PlatformTransactionManager transactionManager;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void performProgrammaticTransaction() {
// 定义事务的传播行为、隔禂级别等属性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 开启事务
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 在事务内部执行数据库操作
// 如果发生异常,可以通过 status.setRollbackOnly() 进行事务回滚
// 操作成功完成后,提交事务
transactionManager.commit(status);
} catch (Exception ex) {
// 操作出现异常,回滚事务
transactionManager.rollback(status);
}
}
}
TransactionTemplate
在 Spring 框架中,可以使用 TransactionTemplate
来实现编程式事务管理。TransactionTemplate
简化了编程式事务的操作,它封装了事务的开始、提交和回滚过程,使得在代码中控制事务变得更加方便。
以下是使用 TransactionTemplate
控制事务的一般步骤:
-
获取
PlatformTransactionManager
的实例。 -
创建
TransactionTemplate
实例,并将PlatformTransactionManager
传入其构造函数。 -
通过
TransactionTemplate
提供的方法执行业务操作。
<T> T execute(TransactionCallback<T> action)
:有返回值,需要传递一个TransactionCallback
对象,该对象定义了要在事务中执行的操作。
TransactionCallback<T>
是一个函数式接口,执行时需要一个TransactionStatus
类型的参数,以及返回个T
类型的对象。void executeWithoutResult(Consumer<TransactionStatus> action)
:没有返回值的,需传递一个Consumer
对象,该对象定义了要在事务中执行的操作。
Consumer<TransactionStatus>
是一个函数式接口,执行时需要一个TransactionStatus
类型的参数,无返回结果。
源码中看的话 executeWithoutResult
方法是 TransactionOperations
接口中的一个默认方法 TransactionTemplate
实现了 TransactionOperations
接口,而在 TransactionOperations
接口中 executeWithoutResult
方法内其实是调用了 execute
方法执行的,只不过没有把 execute
方法的执行结果返回。
在使用这两种方法时,Spring 会自动管理事务的开始、提交和回滚过程,使得事务管理变得更加方便和简单。这样,您可以专注于业务逻辑的实现,而无需手动管理事务的细节。
下面是一个简单的示例:
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
public class ProgrammaticTransactionWithTemplateExample {
private PlatformTransactionManager transactionManager;
private TransactionTemplate transactionTemplate;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
this.transactionTemplate = new TransactionTemplate(transactionManager);
}
public String execute() {
return transactionTemplate.execute(status -> {
// 在事务内部执行数据库操作
// 如果发生异常,可以通过 status.setRollbackOnly() 进行事务回滚
return "操作成功"; // 操作成功完成后返回
});
}
public void executeWithoutResult() {
transactionTemplate.executeWithoutResult(status -> {
// 在事务内部执行数据库操作
// 如果发生异常,可以通过 status.setRollbackOnly() 进行事务回滚
});
}
}