Spring 事务管理
# 一、事务基础知识
# 1、什么是事务
事务是指数据库中一组操作被视为一个单独的执行单元,这组操作要么全部执行成功,要么全部回滚,保证数据的一致性和完整性。
事务的存在有以下几个原因:
- 数据的一致性:事务可以确保在数据库中的操作是一致的,即满足预定义的约束条件。事务将一组操作作为一个整体,要么全部执行成功,要么全部回滚,避免了数据的不一致性。
- 数据的完整性:事务可以保护数据的完整性,即在事务执行过程中,数据库的状态始终保持一致。如果事务执行过程中出现错误或中断,可以通过回滚操作将数据库状态还原到事务开始之前的状态。
- 并发控制:在并发环境下,多个用户可能同时对数据库进行读写操作,事务提供了并发控制的机制,确保并发操作不会产生不一致的结果。通过隔离级别和锁机制,事务可以提供一定程度的并发控制,避免并发操作引发的并发问题,如脏读、不可重复读和幻读。
- 效率和性能:事务可以减少对数据库的访问次数,提高数据库的效率。将多个操作合并为一个事务,可以减少与数据库的通信开销,提高数据操作的效率。
事务具有以下特性(ACID):
- 原子性(Atomicity):事务中的操作要么全部成功,要么全部失败回滚。
- 一致性(Consistency):事务执行前后,数据库的状态保持一致。
- 隔离性(Isolation):事务的执行不受其他事务的干扰。
- 持久性(Durability):事务一旦提交,对数据库的修改将永久保存。
# 2、并发请求导致的各种问题
为了符合ACID原则,常见的数据库都会提供事务控制机制。
但是,即使在有事务控制机制的情况下。事务并发执行过程中依然可能存在以下问题:
- 脏读(Dirty Read):一个事务读取了另一个未提交事务的数据。
- 不可重复读(Non-repeatable Read):在一个事务内,多次读取同一数据,但结果不一致。
- 幻读(Phantom Read):在一个事务内,多次查询同一范围的数据,结果却不一致。
- 第一类丢失更新(First Lost Update):当两个或多个并发事务同时对同一数据进行更新时,只有一个事务的更新操作能够成功,其他事务的更新操作将被覆盖,导致部分数据的更新丢失。
- 第二类丢失更新(Second Lost Update):当两个或多个并发事务读取同一数据并进行计算后再写回数据库时,在没有事务控制的情况下,可能出现其中一个事务的计算结果被覆盖的情况,导致部分计算结果的丢失。
# 3、事务隔离机制
事务隔离机制是指在多个事务并发执行时,保证各个事务之间的操作互不干扰,每个事务都感觉到自己是独立执行的。事务隔离级别定义了事务之间的隔离程度。
常见的事务隔离级别包括:
- READ_UNCOMMITTED(读未提交):最低级别,允许读取未提交的数据,存在脏读、不可重复读和幻读的问题。
- READ_COMMITTED(读已提交):保证读取到的数据是已提交的,解决了脏读问题,但不可重复读和幻读仍可能出现。
- REPEATABLE_READ(可重复读):保证事务执行期间多次读取同一数据的结果是一致的,解决了脏读和不可重复读问题,但幻读仍可能出现。
- SERIALIZABLE(串行化):最高级别,事务串行执行,避免了所有并发问题,但性能较差。
下面是四种常见的事务隔离级别(隔离机制)对于并发问题的解决情况的表格:
问题 | 脏读 | 不可重复读 | 幻读 | 第一类丢失更新 | 第二类丢失更新 |
---|---|---|---|---|---|
读未提交(Read Uncommitted) | 可能发生 | 可能发生 | 可能发生 | 不会发生 | 可能发生 |
读已提交(Read Committed) | 不会发生 | 可能发生 | 可能发生 | 不会发生 | 可能发生 |
可重复读(Repeatable Read) | 不会发生 | 不会发生 | 可能发生 | 不会发生 | 不会发生 |
串行化(Serializable) | 不会发生 | 不会发生 | 不会发生 | 不会发生 | 不会发生 |
在MySQL中,可以通过如下语句查询默认的事务隔离级别:
# 全局默认
SHOW VARIABLES LIKE 'transaction_isolation';
# session默认
SELECT @@session.transaction_isolation;
# 4、JDBC事务管理
参考之前的文章:JDBC 事务管理
# 二、Spring事务管理
# 1、Spring事务管理的概念
Spring框架提供了强大的事务管理功能,可以帮助开发者管理和控制应用程序中的事务。下面是Spring事务管理的一些关键概念:
事务:事务是一组操作作为一个不可分割的工作单元,要么全部成功提交,要么全部回滚。在Spring中,事务可以在方法级别或类级别进行定义,被注解或配置进行标识。
事务管理器(Transaction Manager):事务管理器是Spring框架提供的用于管理和控制事务的接口。它负责管理事务的开始、提交、回滚和回滚点等操作,与具体的数据访问技术(如JDBC、Hibernate)进行交互。
事务定义(Transaction Definition):事务定义用于定义事务的属性,包括隔离级别、传播行为、超时时间等。可以通过编程方式或声明式方式进行定义。
事务传播行为(Transaction Propagation):事务传播行为定义了在方法调用链中的事务如何传播和交互。例如,如果一个方法A调用了另一个方法B,事务传播行为规定了B方法是否在A方法的事务中执行,或者创建一个新的独立事务。
事务隔离级别(Transaction Isolation Level):事务隔离级别定义了在并发环境中事务之间的隔离程度,以避免并发问题(如脏读、不可重复读、幻读)。常见的隔离级别包括读未提交、读已提交、可重复读和串行化。
事务切面(Transaction Aspect):Spring通过AOP(面向切面编程)实现事务管理。事务切面是将事务管理逻辑与业务逻辑分离的关键组件,它通过在方法调用前后进行拦截,并在合适的时机开启、提交或回滚事务。
声明式事务管理:Spring提供了声明式事务管理的功能,通过注解或XML配置的方式声明事务的属性,而无需显式编写事务管理的代码。这种方式可以将事务管理与业务逻辑解耦,使代码更加简洁和可维护。
通过Spring的事务管理,开发者可以方便地控制事务的边界和行为,保证数据的一致性和完整性,并提供并发控制的支持,提高应用程序的可靠性和性能。
# 2、事务管理器
事务管理器负责管理和控制事务的开始、提交、回滚等操作。Spring支持多种事务管理器,如DataSourceTransactionManager(用于JDBC事务)和HibernateTransactionManager(用于Hibernate事务)等。
在Spring中配置事务管理器的代码取决于你使用的具体的数据访问技术。下面是两个常见的配置示例:
- 配置JDBC事务管理器(使用DataSourceTransactionManager):
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
public class AppConfig {
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// 其他配置代码...
}
在上述示例中,我们使用了DataSourceTransactionManager
作为JDBC事务管理器。我们将DataSource
对象注入到transactionManager
方法中,并将其作为参数传递给DataSourceTransactionManager
的构造函数。最后,我们将DataSourceTransactionManager
对象声明为一个@Bean
,以便Spring可以将其托管。
- 配置Hibernate事务管理器(使用HibernateTransactionManager):
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import javax.sql.DataSource;
@Configuration
public class AppConfig {
@Bean
public HibernateTransactionManager transactionManager(LocalSessionFactoryBean sessionFactory) {
return new HibernateTransactionManager(sessionFactory.getObject());
}
// 其他配置代码...
}
在上述示例中,我们使用了HibernateTransactionManager
作为Hibernate事务管理器。我们将LocalSessionFactoryBean
对象注入到transactionManager
方法中,并通过调用sessionFactory.getObject()
方法获取SessionFactory
对象,然后将其传递给HibernateTransactionManager
的构造函数。最后,我们将HibernateTransactionManager
对象声明为一个@Bean
。
需要注意的是,你还需要在配置中提供适当的数据源(DataSource
)和Hibernate的SessionFactory
。具体的配置方式取决于你的项目和数据访问技术的选择。
# 3、@Transactional
注解
Spring提供了@Transactional注解来声明事务通知。通过在方法上添加@Transactional注解中定义事务通知,可以将方法标记为需要进行事务管理的。
下面是使用@Transactional
注解配置事务通知的示例:
import org.springframework.transaction.annotation.Transactional;
@Service
public class MyService {
@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, timeout = 10)
public void performTransactionalOperation() {
// 执行事务性操作
}
// 其他业务方法...
}
在上述示例中,我们将@Transactional
注解应用于performTransactionalOperation()
方法上。这将告诉Spring该方法需要在事务中执行,如果事务不存在,则会自动创建一个新的事务。当该方法执行完毕时,Spring会根据事务管理器的配置来决定是否提交事务或回滚事务。
我们使用rollbackFor
属性设置回滚的触发异常(默认是RuntimeException),使用isolation
属性设置事务隔离级别为READ_COMMITTED
,使用propagation
属性设置事务传播行为为REQUIRED
,使用timeout
属性设置事务超时时间为10秒。
# 4、自定义事务注解
@Transactional
注解的value
属性可以配置不同的事务管理器,比如:
@Transactional(value = "forumTransactionManager")
public void performTransactionalOperation() {
// 执行事务性操作
}
如果我们需要在多个方法中使用同一个事务管理器,那么每个方法都需要添加@Transactional(value = "forumTransactionManager")
注解,这样会导致代码重复。
为了避免代码重复,我们可以自定义一个事务注解,然后在需要的地方使用该注解。下面是自定义事务注解的示例:
假设已经存在forumTransactionManager
事务管理器。
接下来,我们可以定义一个自定义的事务注解@CustomTransactional
,代码如下:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(value = "forumTransactionManager")
public @interface CustomTransactional {
}
在上面的代码中,我们使用了@Transactional
注解,并指定了value
属性为forumTransactionManager
,表示使用自定义事务管理器CustomTransactionManager
。
接下来,我们可以在需要的方法上使用@CustomTransactional
注解,而不需要再指定具体的事务管理器了,示例如下:
@CustomTransactional
public void performTransactionalOperation() {
// 执行事务性操作
}
这样就可以避免代码重复,统一使用自定义的事务管理器。
# 5、事务的隔离与传播
在第一节的事务隔离机制中,我们已经介绍了事务隔离的四种级别。在Spring中,我们可以通过@Transactional
注解的isolation
属性来设置事务的隔离级别。
@Transactional
注解还有一个propagation
属性,用于设置事务的传播行为。事务的传播行为定义了在方法调用链中的事务如何传播和交互。例如,如果一个方法A调用了另一个方法B,事务传播行为规定了B方法是否在A方法的事务中执行,或者创建一个新的独立事务。
@Transactional
注解的propagation
属性支持以下7种事务传播行为:
Propagation.REQUIRED
:默认值。如果当前没有事务,就创建一个新事务;如果已经存在一个事务中,就加入到这个事务中。Propagation.SUPPORTS
:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式执行。Propagation.MANDATORY
:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。Propagation.REQUIRES_NEW
:创建一个新的事务,如果当前存在事务,则挂起当前事务。Propagation.NOT_SUPPORTED
:以非事务的方式执行操作,如果当前存在事务,则挂起当前事务。Propagation.NEVER
:以非事务的方式执行操作,如果当前存在事务,则抛出异常。Propagation.NESTED
:如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则创建一个新事务。
这些事务传播行为可以根据具体的业务需求进行选择和配置。默认情况下,@Transactional
注解的propagation
属性值为Propagation.REQUIRED
,即默认使用的是REQUIRED
传播行为。
# 6、事务的超时与只读
@Transactional
注解还有两个属性:timeout
和readOnly
,他们分别用于设置事务的超时时间和只读属性。
timeout
属性用于设置事务的超时时间,单位是秒。如果事务执行时间超过了指定的超时时间,则事务会被自动回滚。示例代码如下:
@Transactional(timeout = 10)
public void performTransactionalOperation() {
// 执行事务性操作
}
在上面的示例中,performTransactionalOperation
方法的事务超时时间被设置为10秒。
readOnly
属性用于设置事务的只读属性。如果将readOnly
属性设置为true
,表示当前事务只读,不会对数据库进行修改操作,这样可以提高事务的性能。示例代码如下:
@Transactional(readOnly = true)
public void performTransactionalOperation() {
// 执行只读操作
}
在上面的示例中,performTransactionalOperation
方法的事务被设置为只读。如果在只读事务中进行了修改操作,会抛出异常。
# 7、事务的回滚
在默认情况下,当被@Transactional
注解标记的方法抛出任何未被捕获的异常时,事务会自动回滚。
事务的回滚是通过抛出异常来触发的。当方法抛出一个未被捕获的异常时,事务管理器会将当前事务标记为回滚状态,并抛出一个RuntimeException
或其子类的异常。这样,事务管理器会回滚所有已执行的数据库操作,包括之前成功执行的操作。
以下是一个示例代码,演示了在方法中使用@Transactional
注解,并触发事务回滚的情况:
@Transactional
public void performTransactionalOperation() {
// 执行事务性操作
// 抛出一个未被捕获的异常
throw new RuntimeException("Something went wrong");
}
在上述示例中,当performTransactionalOperation
方法抛出RuntimeException
时,事务管理器会将当前事务回滚。所有在该方法中执行的数据库操作都会被撤销。
需要注意的是,只有抛出RuntimeException
及其子类的异常才会触发事务回滚。如果抛出的异常是受检异常(checked exception),例如IOException
,事务管理器不会自动回滚事务。如果希望在遇到受检异常时也触发事务回滚,可以在@Transactional
注解上添加rollbackFor
属性来指定需要回滚的异常类型。例如:
@Transactional(rollbackFor = IOException.class)
public void performTransactionalOperation() throws IOException {
// 执行事务性操作
// 抛出一个受检异常
throw new IOException("Something went wrong");
}
在上述示例中,当performTransactionalOperation
方法抛出IOException
时,事务管理器会将当前事务回滚。
如果希望在遇到任何异常时都触发事务回滚,可以在@Transactional
注解上添加rollbackFor
属性,并将其设置为Exception.class
。例如:
@Transactional(rollbackFor = Exception.class)
public void performTransactionalOperation() throws IOException {
// 执行事务性操作
// 抛出一个受检异常
throw new IOException("Something went wrong");
}
当然,你也可以排除不需要回滚的异常,需要用到noRollbackFor
属性。例如:
@Transactional(rollbackFor = Exception.class, noRollbackFor = IOException.class)
public void performTransactionalOperation() throws IOException {
// 执行事务性操作
// 抛出一个受检异常
throw new IOException("Something went wrong");
}
# 8、AOP方式控制事务
在Spring中,你可以使用切面配置的方式对某些类中的某些方法进行事务控制,而不是使用@Transactional注解。
首先,创建一个配置类,用于定义事务管理器和事务通知。示例代码如下:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Autowired
private DataSource dataSource;
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public TransactionInterceptor transactionAdvice() {
Properties attributes = new Properties();
attributes.setProperty("save*", "PROPAGATION_REQUIRED");
attributes.setProperty("update*", "PROPAGATION_REQUIRED");
attributes.setProperty("delete*", "PROPAGATION_REQUIRED");
attributes.setProperty("get*", "PROPAGATION_REQUIRED,readOnly");
NameMatchTransactionAttributeSource attributeSource = new NameMatchTransactionAttributeSource();
attributeSource.setProperties(attributes);
return new TransactionInterceptor(transactionManager(), attributeSource);
}
@Bean
public Advisor transactionAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.example.MyService.*(..))");
return new DefaultPointcutAdvisor(pointcut, transactionAdvice());
}
}
在上述示例中,transactionManager
方法定义了事务管理器。transactionAdvice
方法定义了事务通知,通过Properties
对象设置了事务的属性。transactionAdvisor
方法定义了事务通知的切入点和通知对象的关联。
在transactionAdvice
方法中,你可以根据自己的需求来定义事务规则,并使用不同的传播行为和只读属性来控制事务的行为。
# 9、事务中的事件通知
在Spring事件探秘-事务绑定事件中,我们能看到事务方法中发送事件存在的问题。
除了使用@TransactionalEventListener
注解可以解决上面的问题,你也可以使用编码式的方式手动控制在事务执行成功后的逻辑:
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
SpringContextHolder.getApplicationContext().publishEvent(new SomeEvent(t));
}
});
} else {
SpringContextHolder.getApplicationContext().publishEvent(new SomeEvent(t));
}
上述代码中,首先判断当前是否在事务方法中。如果是的话,则注册此事务提交成功后的执行逻辑;否则,则正常发送事件。
# 10、编程式事务管理之TransactionTemplate
在实际应用中,很少通过编程进行事务管理。但Spring还是提供了TransactionTemplate模板类来满足一些特殊场合的需求。
TransactionTemplate
是Spring框架提供的编程式事务管理的工具类。通过使用TransactionTemplate
,我们可以在需要的地方对事务进行手动控制。
以下是使用TransactionTemplate
进行编程式事务管理的示例:
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
public class TransactionalService {
private TransactionTemplate transactionTemplate;
// 依赖注入TransactionTemplate
public TransactionalService(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
public void performTransactionalOperation() {
transactionTemplate.execute(new TransactionCallback<Void>() {
public Void doInTransaction(TransactionStatus status) {
try {
// 在事务中执行数据库操作
// ...
// 如果需要,可以手动控制事务的回滚
// if (someErrorCondition) {
// status.setRollbackOnly();
// }
// 返回结果
return null;
} catch (Exception ex) {
// 如果发生异常,手动设置事务回滚
status.setRollbackOnly();
throw ex;
}
}
});
}
}
在上述示例中,我们首先通过构造函数注入了TransactionTemplate
实例。然后,在performTransactionalOperation()
方法中,我们使用transactionTemplate.execute()
方法来执行一个事务操作。
transactionTemplate.execute()
方法接受一个TransactionCallback
对象作为参数,其中的doInTransaction()
方法定义了在事务中执行的逻辑。在doInTransaction()
方法中,我们可以执行数据库操作、处理异常、手动控制事务的回滚等。
如果在事务过程中发生异常,我们可以手动调用status.setRollbackOnly()
方法将事务标记为回滚。这样,在事务结束时,Spring会检查事务状态,并根据标记决定是否回滚事务。
需要注意的是,TransactionTemplate
会自动管理事务的开始、提交和回滚等操作,我们不需要手动调用这些方法。
通过使用TransactionTemplate
,我们可以更灵活地控制事务的边界和行为,以及处理事务中的异常。它适用于那些需要在代码中动态决定事务的范围和行为的场景。
# 三、总结
本文中,我们主要介绍了事务基础知识和Spring事务管理相关的内容。
在事务基础知识部分,文章首先介绍了事务的定义,即一组数据库操作要么全部成功,要么全部失败。然后讨论了并发请求可能导致的各种问题,如脏读、不可重复读和幻读。接着介绍了事务隔离机制,包括读未提交、读已提交、可重复读和串行化四个隔离级别。最后介绍了JDBC事务管理的相关知识。
在Spring事务管理部分,文章首先解释了Spring事务管理的概念,即通过对业务方法的切面来控制事务的提交和回滚。然后介绍了事务管理器的作用,接着介绍了@Transactional
注解的使用,以及如何自定义事务注解。然后讨论了事务的隔离与传播,包括设置隔离级别和传播行为。接下来介绍了事务的超时与只读的设置,以及如何进行事务的回滚。最后介绍了使用AOP方式控制事务的方法,以及编程式事务管理中的TransactionTemplate
的使用。
本文详细介绍了事务基础知识和Spring事务管理的相关内容,希望对于你理解事务的概念和使用Spring进行事务管理的方法有所帮助。
祝你变得更强!