面试-Spring事务
Spring事务
前言
本文默认读者已经对事务的四大特性已经MySQL有所了解,不再对其赘述。
Spring事务不仅仅是面试的重点,也是每一个spring使用者必须掌握的知识点,本文全面,详细的对spring事务进行全面的讲解,希望各位读者能够从中得到体会。
摘要
Spring事务是指在Spring框架中对于一组数据库操作,当其中一个或多个操作失败时,回滚所有操作,以保证数据的一致性和完整性。在Spring中,事务是通过AOP(面向切面编程)实现的,即Spring使用代理模式对方法进行增强,以实现事务的控制。
Spring中使用事务的方式有两种,声明式事务和编程式事务。
编程式事务
编程式事务是指通过编写代码来控制事务的开启、提交和回滚等操作。在编程式事务中,开发人员需要显式地编写代码来管理事务,而不是使用声明式事务那样通过注解或XML配置的方式进行管理。
编程式事务的实现通常依赖于底层的事务管理器(如JDBC事务管理器、Hibernate事务管理器、JTA事务管理器等),开发人员需要显式地获取事务管理器对象,然后在代码中手动开启、提交或回滚事务。
下面是一个使用编程式事务的例子
@Service
public class UserService {
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private UserDao userDao;
@Autowired
private UserInfoDao userInfoDao;
public void register(User user, UserInfo userInfo) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
userDao.insert(user);
userInfoDao.insert(userInfo);
} catch (Exception e) {
transactionStatus.setRollbackOnly();
throw e;
}
}
});
}
}
优点:
- 灵活
缺点:
- 代码侵入性高
声明式事务
声明式事务也就是通过注解的方式管理事务。将事务与方法调用解耦,声明式事务管理使用Spring的AOP技术。
下面是一个例子,将@Transaction注解到想要使用事务的方法或者类,注解在类上时,该类的所有方法都拥有事务的特性。
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Transactional
@Override
public void updateUser(User user) {
userDao.update(user);
}
}
事务有很多属性来决定他在某种情况下的行为,比如说有方法A调用了方法B,A和B都用@Transaction修饰,那么当2个事务直接有调用关系,这种情况下怎么处理呢,这也就是要定义事务的传播行为,当然,还有很多其他的属性,接下来学习一下怎么去运用这些属性。
事务属性
事务传播行为
事务传播行为是指在多个事务方法嵌套调用的情况下如何处理事务的机制。在涉及到多个事务方法相互调用、交叉调用的情况下,就需要事务传播行为来处理这些调用之间的事务交互关系。
Spring 框架提供了七种常用的事务传播行为:
- REQUIRED:如果当前存在一个事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认情况下的传播行为。
- 如果外部开启事务,内部则加入事务,其中一个方法回滚,整个事务回滚
- SUPPORTS:支持当前事务,如果当前存在事务,则在该事务的上下文中执行;否则,在非事务的上下文中执行。
- MANDATORY:支持当前事务,如果没有当前事务,则抛出异常。
- REQUIRES_NEW:创建一个新的事务,并在必要时将当前事务挂起。
- 如果外部开启事务,内部开启新事务,外部事务回滚不影响内部事务。若内部事务发生异常且被外部事务捕获,则一同回滚,否则内部事务回滚。
- NOT_SUPPORTED:以非事务的方式执行操作,如果存在事务,则将其挂起。
- NEVER:以非事务的方式执行,但是如果存在事务,则抛出异常。
- NESTED:如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则按照 REQUIRED 属性执行。
- 若外部开启事务,内层被调用方法回滚与否,不会影响外层调用方法。而外层调用方法出异常回滚,也会回滚内层被调用方法。
其中,前五种传播行为支持嵌套事务的使用,NESTED 则是特别用来实现嵌套事务的传播行为。在 NESTED 的传播行为下,内层事务的提交或回滚不会对外层事务造成影响,但是内层事务抛出异常时,外层事务将会回滚。
事务传播行为的选择需根据业务需求、开发经验等因素进行权衡。多个方法嵌套调用时,合理地设置传播行为可以减少不必要的开销,避免事务处理引起的不必要的问题。
这是一个例子
@Service
public class DataService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional(propagation = Propagation.REQUIRED)
public void doWork() {
jdbcTemplate.update("update foo set name = 'test1' where id = 1");
try {
doSomeMoreWork();
} catch (Exception e) {
// Do something
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomeMoreWork() {
jdbcTemplate.update("update foo set name = 'test2' where id = 2");
throw new RuntimeException("exception thrown from doSomeMoreWork");
}
}
这里我故意让外部事务和内部事务使用了不同的传播行为,其目的是告诉大家,事务传播行为是规定内部事务在外部事务下的行为,因此当外部事务和内部事务使用了不同的传播行为时,应当按照内部事务的事务传播行为处理,也就是说,此时doSomeMoreWork与doWork应该分别在两个事务当中,互不影响。doWork出错,只会回滚doWork,doSomeMoreWork出错,只会回滚doSomeMoreWork。
事务隔离级别
事务隔离级别用于描述并发事务之间的机制,用于解决数据库并发访问可能导致的数据一致性问题。
Spring 支持的事务隔离级别与标准的隔离级别保持一致,包括:
DEFAULT
:由底层数据访问技术决定。READ_UNCOMMITTED
:读取未提交的数据。这是最低的隔离级别,并且可能导致脏读,不可重复读,幻影读和其他并发问题。READ_COMMITTED
:只能读取已经提交的数据,并且可以避免脏读。但是,它不能避免不可重复读和幻影读问题。REPEATABLE_READ
:重复读取,保证对同一数据的多次读取结果是一致的,可避免脏读和不可重复读的问题。在REPEATABLE_READ
级别下,会使用行级锁来保证读取的数据一致性,这可以有效避免脏读和不可重复读。SERIALIZABLE
:串行化,最高的隔离级别,通过完全串行化事务,避免一切并发问题。在SERIALIZABLE
级别下,会使用表级锁来保证事务的串行化执行,从而避免脏读、重复读和幻影读等并发问题,但是会影响并发性能,一般很少使用。
在 Spring 中,默认情况下隔离级别为 DEFAULT
,这意味着 Spring 将使用已配置的原始事务管理器的默认隔离级别。可以使用 @Transactional
注解或编程式事务控制来设置事务隔离级别。以下是一个使用 @Transactional
注解设置事务隔离级别的例子:
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
@Transactional(isolation = Isolation.READ_COMMITTED)
public void doSomething() {
// ...
}
}
在上面的代码中,我们将事务隔离级别设置为 READ_COMMITTED
。这意味着在方法执行期间,会使用 READ_COMMITTED
隔离级别,以保证数据的可靠性和正确性。
事务超时属性
事务超时属性是指在一个事务执行的时间超过事先设定的阈值后,该事务会被强制回滚并释放所有的锁和资源,以保证应用系统的稳定性和可靠性。事务超时属性可以防止事务因阻塞而导致整个系统出现性能问题,从而帮助系统恢复正常运行。
在Spring中,我们可以使用 @Transactional 注解的 timeout属性 来设置事务的超时时间。
以下是一个使用 @Transactional 注解设置事务超时属性的例子:
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
@Transactional(timeout = 60) // 60秒的事务超时时间
public void doSomething() {
// ...
}
}
在上面的代码中,我们设置了60秒的事务超时时间。如果方法执行时间超过60秒,则事务将被回滚并释放所有的锁和资源。请注意,事务超时时间的精度是以秒为单位的,而不是毫秒。
需要注意的是,事务超时属性只对读写事务生效。对于只读事务,事务超时属性将被忽略。此外,如果底层的数据访问技术(如JDBC或Hibernate)不支持事务超时属性,则此设置将无效。
事务只读属性
当事务中没有修改操作时,可以指定事务为只读。
通过@Transactional的readOnly属性指定
@Service
public class MyService {
@Autowired
private MyRepository myRepository;
@Transactional(readOnly = true)
public void doSomething() {
// ...
}
}
那么这样做有什么好处呢
- 指定readOnly属性,数据库会对查询进行优化
- 使用Transactional,可以保证前后读取的数据保持一致,不会因为其他事务在读取过程中进行修改而导致前后不一致
事务回滚规则
用于规定事务在哪些情况下才进行回滚。
默认情况下,当事务抛出RuntimeException或Error,或者它们的子类时,会导致事务回滚。这是因为RuntimeException和Error是无法恢复的异常,它们通常表示程序中的错误状态。
定义语法为
@Transactional(rollbackFor= Exception.class)
@Transactional 的作用范围
位置 | 作用 |
---|---|
方法 | 方法具有事务特性,注意必须为public方法 |
类 | 对类中所有public方法生效 |
接口 | 不推荐 |
Spring事务管理相关接口
TransactionDefinition
:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface TransactionDefinition {
// 事务传播行为
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
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;
// 超时时间
int TIMEOUT_DEFAULT = -1;
// 事务传播行为默认为PROPAGATION_REQUIRED
default int getPropagationBehavior() {
return 0;
}
// 隔离级别默认为ISOLATION_DEFAULT
default int getIsolationLevel() {
return -1;
}
// 超时时间,默认超时
default int getTimeout() {
return -1;
}
// 非只读事务
default boolean isReadOnly() {
return false;
}
@Nullable
default String getName() {
return null;
}
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
}
}
TransactionStatus
:事务运行状态。
PlatformTransactionManager
:(平台)事务管理器,Spring 事务策略的核心,用于定义开启事务,提交事务,回滚事务的API。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface PlatformTransactionManager extends TransactionManager {
// 开启事务
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
// 提交事务
void commit(TransactionStatus status) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
可以看到,其实开始事务,就是根据事务的配置(TransactionDefinition)创建一个事务,事务的状态为TransactionStatus。
提交事务和回滚事务都是修改TransactionStatus的状态。
@Transactional
事务注解原理
其原理是通过AOP实现的,也就是对被@Transactional注解的方法的类进行代理,在方法调用前开启事务,执行完成后提交事务,执行出错则回滚事务。
Spring AOP使用两种代理技术:JDK动态代理和CGLIB代理。如果目标bean实现了至少一个接口,Spring将使用JDK动态代理生成代理对象;否则,Spring将使用CGLIB代理生成代理对象。在生成代理对象之后,Spring事务管理器将根据带有@Transactional注解的方法中的事务属性管理事务。
因为事务是通过代理实现的,所以可以延伸出一个问题
class test {
@Transactional
public void A(){
// dosomething
this.B();
return ;
}
@Transactional
public void B(){
// dosomething
return ;
}
}
在上述代码中,如果执行方法A,方法B是没有事务的特性的。因为在A中使用this调用B,相当于跳过了代理对象,而是直接当做普通方法调用了。
解决方法
class test {
@Transactional
public void A(){
// dosomething
test proxy =(test)AopContext.currentProxy();
proxy.b();
return ;
}
@Transactional
public void B(){
// dosomething
return ;
}
}
总结
spring事务分为编程式事务和声明式事务,编程式事务通过事务管理器(TransactionTemplate、TransactionManager)手动管理事务。
声明式事务通过@Transactional来进行事务管理,在@Transactional中可以定义事务的一些属性,这些属性本质上被封装在TransactionDefinition中,spring提供了一系列的枚举类Propagation、Propagation等。
Spring 框架中,事务管理相关最重要的 3 个接口**PlatformTransactionManager
、TransactionDefinition
、TransactionStatus
,PlatformTransactionManager根据TransactionDefinition
开启事务,通过设置TransactionStatus
**的属性来提交和回滚事务。
@Transactional
的常用配置参数总结
属性名 | 说明 |
---|---|
propagation | 事务的传播行为,默认值为 REQUIRED,可选的值在上面介绍过 |
isolation | 事务的隔离级别,默认值采用 DEFAULT,可选的值在上面介绍过 |
timeout | 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
readOnly | 指定事务是否为只读事务,默认值为 false。 |
rollbackFor | 用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。 |
转载自:https://juejin.cn/post/7239148708700848184