Spring 面试题——事务
目录
1.谈谈对 Spring 事务的理解。
(1)概念:Spring 事务是 Spring 框架提供的一种用于处理数据库事务的机制。在传统的 JDBC 中,我们需要手动管理事务的开启、提交和回滚,这样的代码实现非常繁琐且容易出错,而 Spring 事务通过对事务进行封装,提供了一种更方便、更简单的方式来管理事务。
(2)底层实现原理:Spring 的事务管理是通过 Spring AOP
功能实现的,它在方法调用前后进行事务的管理和控制。以声明式事务管理为例,通过在方法或类上添加 @Transactional
注解,Spring 会在执行方法时自动为其创建事务,并在方法执行完成后根据执行结果决定是否提交或回滚事务。通过使用 Spring AOP 和事务管理,可以实现业务逻辑和横切关注点的松耦合,并实现更可维护和可扩展的代码。
(3)特性:Spring 事务的关键特性以下几点:
- 多种管理事务的方式:Spring 提供了多种管理事务的方式,例如声明式事务、编程式事务,我们根据实际情况选取对应的方式。
- 多种传播行为:Spring 事务提供了 7 种传播行为,如
REQUIRED
(如果没有事务,则创建一个新事务;如果有事务,则加入当前事务)、REQUIRES_NEW
(无论是否存在事务,都创建一个新事务)、NESTED
(嵌套事务,创建一个保存点,允许内部事务独立提交或回滚)等。这些传播行为可以根据业务需求进行配置,实现对事务的精确控制。 - 隔离级别:Spring 事务支持多种隔离级别,如
READ_UNCOMMITTED
(读未提交)、READ_COMMITTED
(读已提交)、REPEATABLE_READ
(可重复读)、SERIALIZABLE
(串行化)等。通过配置隔离级别,我们可以控制事务对于其他事务的可见性和并发行为,确保数据的隔离性。 - 异常处理:Spring 事务提供了异常处理机制,可以配置需要回滚的异常类型和不需要回滚的异常类型。当方法中抛出指定类型的异常时,事务将回滚或不回滚,从而可以对事务执行过程中发生的异常进行精细的管理。
2.Spring 管理事务的方式有哪几种?
Spring 管理事务的方式有以下三种:
- 编程式事务管理:编程式事务管理是通过在代码中编写事务管理相关的代码实现的。这种方式需要手动控制事务的开启、提交和回滚,代码比较繁琐。在 Spring 中,可以使用
TransactionTemplate
或者直接使用PlatformTransactionManager
接口的实现类来实现编程式事务管理。 - 声明式事务管理:声明式事务管理是通过在配置文件或者注解@Transactional 中声明事务的相关信息来实现的。这种方式可以将事务的控制从代码中分离出来,使得代码更加简洁。在 Spring 中,可以使用 AOP 技术来实现声明式事务管理,通过在配置文件中配置切面和通知,使得 Spring 在执行方法时自动应用事务管理。
3.?Spring 事务底层源码是如何实现的?
Spring 事务的源码部分大体上可以分为 2 步:创建目标 Bean 的后置处理和事务执行。
3.1.后置处理
(1)第一步就是后置处理,在创建目标 Bean 的后置处理器中,主要是做下面两件事情。
(2)首先会获取到所有的切面信息,然后和目标 Bean 的所有方法进行匹配,然后找到目标 Bean 所有需要进行事务处理的方法,匹配成功的方法。此外,还需要将事务属性保存到缓存 attributeCache
中。attributeCache
是一个 Map 结构,其中的 key
保存的是方法对象,value
保存的是该方法对应的属性信息。
public abstract class AbstractFallbackTransactionAttributeSource implements TransactionAttributeSource {
//...
final Map<Object, TransactionAttribute> attributeCache = new ConcurrentHashMap<>();
}
(3)然后就是创建 AOP 代理对象。在该过程中涉及到一个名为 AbstractAutoProxyCreator
的类,该类实现了 BeanPostProcessor
接口,并且重写了后置处理方法 postProcessAfterInitialization
,该方法在 Bean 的初始化之后被调用,它允许开发者在初始化之前对 Bean 进行一些自定义的操作。postProcessAfterInitialization 方法中调用了 wrapIfNecessary
方法,该方法主要执行逻辑是:
- 根据已知的相关信息调用
createProxy
方法来获取目标 Bean 代理对象,Spring 源码中的AopProxy
接口定义了创建代理对象的方法,并且它有两个实现类,分别对应 2 种创建 AOP 代理对象的方式:- 如果是基于接口的代理,则选择使用
JDK
动态代理,对应JdkDynamicAopProxy
类; - 如果是基于类的代理,则选择使用
CGLIB
动态代理,对应CglibAopProxy
类;
- 如果是基于接口的代理,则选择使用
3.2.事务执行
(1)回到业务逻辑,通过目标 Bean 的 AOP 代理对象,开始执行主方法。在执行事务时会调用 TransactionInterceptor
中的 invoke
方法,里面会调用事务执行的核心方法,即 TransactionAspectSupport
类中的 invokeWithinTransaction
方法,其源码如下:
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
//...
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
//获取我们的事务属源对象
TransactionAttributeSource tas = getTransactionAttributeSource();
//通过事务属性源对象获取到我们的事务属性信息
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
//获取我们配置的事务管理器对象
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
//从tx属性对象中获取出标注了@Transactionl的方法描述符
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
//处理声明式事务
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
//有没有必要创建事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal;
try {
//调用钩子函数进行回调目标方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
//抛出异常进行回滚处理
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
//清空我们的线程变量中transactionInfo的值
cleanupTransactionInfo(txInfo);
}
//提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
//编程式事务
else {
//这里不是我们的重点,省略...
}
}
}
(2)invokeWithinTransaction
方法的主要执行逻辑如下:
- 获取事务属性:从
attributeCache
缓存中获取事务的缓存数据,即TransactionAttribute
对象; - 创建事务:通过
createTransactionIfNecessary
方法创建并开启事务; - 执行逻辑:调用
proceedWithInvocation
方法,开始执行业务逻辑。在执行过程中可能出现以下两种情况:- 如果在执行过程中没有出现异常或者错误,则最后提交事务,整个流程结束;
- 如果在执行过程中抛出异常,则会进入
completeTransactionAfterThrowing
方法,走后续的事务回滚操作。至于具体是否执行回滚操作,或者针对哪些指定的异常执行回滚操作,则与事务属性rollbackFor
或者noRollbackFor
的具体配置有关;
3.3.总结
总的来说,Spring 事务底层实现主要分为 2 步:
- 先匹配出目标 Bean 对象所有关于事务的切面列表,并将匹配成功的事务属性保存到
advisorsCache
缓存中; - 从缓存取出事务属性,然后创建事务、启动事务,执行业务逻辑,最后提交或者回滚事务。
4.@Transactional 注解有什么作用?它的常用属性有哪些?
(1)@Transactional
注解是一个用于声明事务的注解,用于在方法或类级别上标记为事务性。它的作用是告诉 Spring 框架要对带有该注解的方法或类进行事务处理。
(2)@Transactional
注解有以下几个常用属性:
propagation
:指的是一个事务方法被另一个事务方法调用时,事务如何传播,默认值为 REQUIRED。isolation
:表示事务的隔离级别,默认值为 DEFAULT。隔离级别指的是多个事务同时访问数据库时的数据隔离程度,常用的隔离级别包括:DEFAULT
:使用数据库默认的隔离级别。READ_UNCOMMITTED
:允许读取未提交的数据变更。READ_COMMITTED
:只能读取已经提交的数据变更。REPEATABLE_READ
:可重复读,确保多次读取的结果一致。SERIALIZABLE
:串行化,最高的隔离级别,确保事务串行执行。
readOnly
:表示事务是否为只读事务,默认值为 false。如果将其设置为 true,表示该事务只读取数据而不更新数据,可以用于优化事务性能。timeout
:表示事务的超时时间,默认值为 -1,表示不超时。超时时间是指事务执行的最长时间,超过该时间,则事务会被自动回滚。rollbackFor
:指定异常类型数组,当方法抛出指定类型的异常时,事务会回滚。示例:@Transactional(rollbackFor = {Exception.class})rollbackForClassName
:指定异常类型名称数组,当方法抛出指定类型的异常时,事务会回滚。示例:@Transactional(rollbackForClassName = {“Exception”})noRollbackFor
:指定异常类型数组,当方法抛出指定类型的异常时,事务不会回滚。示例:@Transactional(noRollbackFor = {CustomException.class})noRollbackForClassName
:指定异常类型名称数组,当方法抛出指定类型的异常时,事务不会回滚。示例:@Transactional(noRollbackForClassName = {“CustomException”})
(3)注意:回滚属性可以单独使用,也可以组合使用,以定义回滚策略。如果不指定回滚属性,默认情况下,事务会在遇到 RuntimeException 及其子类抛出时回滚。
相关知识点:
MySQL 面试题——事务
(3)Transactional 注解的源码如下所示:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
5.?Spring 事务中的传播行为是指什么?具体有哪几种传播行为?
(1) 事务中的传播行为解决的核心问题是多个声明了事务的方法相互调用的时候存在的事务嵌套问题,那么这个事务的行为应该如何进行传递呢?比如说 methodA()
调用 methodB()
,两个方法都显示地开启了事务,那么 methodB() 是开启一个新事务,还是继续在 methodA() 的事务中执行呢?这就取决于事务的传播行为。
(2)所以 Spring 为了解决这个问题,定义了以下 7 种事务传播行为:
传播属性 | 描述 |
---|---|
REQUIRED | 如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行(默认的) |
REQUIRED_NEW | 无论当前是否存在事务,都必须创建一个新事务。如果当前存在事务,将会被挂起,并在新事务结束后恢复。即使外部事务回滚,新事务也不受影响 |
NESTED | 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行.否则,就启动一个新的事务,并在它自己的事务内运行 |
MANDATORY | 强制事务执行,当前的方法必须运行在事务内部;如果没有正在运行的事务,就抛出异常 |
SUPPORTS | 支持事务,如果有事务在运行,当前的方法就在这个事务内运行;否则以非事务的方式执行 |
NOT_SUPPORTED | 不支持事务,以非事务的方式执行,如果有运行的事务,将它挂起 |
NEVER | 以非事务的方式执行,如果有运行的事务,就抛出异常 |
Spring 事务中的事务传播行为是指在一个方法内部调用另一个带有事务的方法时,事务如何传播到被调用的方法中的行为。
6.Spring 中如何设置传播行为?
(1)在 Spring 中设置事务传播行为,可以通过在 @Transactional 注解中设置 propagation 属性来实现。propagation 属性的默认值是 REQUIRED,也就是使用当前事务,如果不存在则新建事务。
(2)例如,使用 REQUIRED 传播行为的示例:
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class UserServiceImpl implements UserService {
// ...
}
在这个示例中,@Transactional 注解的 propagation 属性被设置为 REQUIRED,也就是在该方法内使用当前事务,如果不存在则新建一个事务。可以根据需要将 propagation 属性设置为其他的传播行为,如 SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER 或 NESTED。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!