精尽Spring面试题
Spring整体
什么是Spring Framework?
Spring是一个开源应用框架,旨在降低应用程序开发的难度。
-
轻量级,低耦合;
相较于EJB轻量级,随着Spring功能逐渐强大,需要进行各种配置,又称配置地狱,因此后面推出了Spring Boot
-
分层体系结构,允许用户选择组件,为J2EE应用程序开发提供了便利;
-
可以集成其他框架,如Spring MVC、MyBatis等;
Spring Framework中有多少个模块?它们分别是什么?
Spring 核心容器,是Spring Framework的核心,对应图中Core Container
-
spring-core;
-
spring-beans;
spring-core和spring-beans提供Spring框架的基本功能——IoC和DI。主要组件是BeanFactory,是工厂模式的实现,消除了编程单例的需要,允许将应用程序配置和规范与程序实际业务逻辑分离。
-
spring-context;
上下文(spring-context)模块建立在Core和Beans模块提供的坚实基础上:这是一种以类似于JNDI注册中心的框架样式方式访问对象的方法。 Context模块从Beans模块继承其功能,并增加了对国际化(例如,使用资源束),事件传播,资源加载以及通过Servlet容器透明创建上下文的支持。上下文模块还支持Java EE功能,例如EJB,JMX和基本远程处理。 ApplicationContext接口是Context模块的焦点。 表现形式为
-
spring-context-support;
spring-context-support提供了将通用第三方库集成到Spring应用程序上下文中以支持缓存(EhCache,Guava,JCache),邮件(JavaMail),调度(CommonJ,Quartz)和模板引擎(FreeMarker,JasperReports,Velocity)的支持。
-
spring-expression;
spring-expression模块提供了一种功能强大的表达式语言,用于在运行时查询和操作对象图。它是对JSP 2.1规范中指定的统一表达语言(统一EL)的扩展。该语言支持设置和获取属性值,属性分配,方法调用,访问数组,集合和索引器,逻辑和算术运算符,命名变量以及按名称从Spring的IoC容器中检索对象的内容。它还支持列表投影和选择以及常见的列表聚合。
AOP
-
spring-aop,支持面向切面编程,使用声明式事务管理集成到应用程序中;
-
spring-aspects,为AspectJ的集成提供支持;
-
spring-instrumentation,为类检测和类加载器实现提供支持;
数据访问,提供与数据库交互的支持,对应图中Data Access
- JDBC,Java DataBase Connectivity,提供了对关系数据库的访问;
- ORM,Object Relational Mapping,提供了对Hibernate5和JPA的集成;
- OXM,Object XML Mappers,类似于ORM映射机制,将Java对象和XML文件进行映射;
- Transaction,提供声明式事务和编程式事务等简单强大的事务管理功能;
- JMS,提供一个Java Messaging Service集成框架;
Web
- WebMVC,全功能构建Web应用的MVC实现,容纳了大量视图技术(模板引擎),jsp,Velocity等;
- WebFlux,基于Reactive库的响应式Web应用;
- WebSocket,提供了一种在Web应用中实现高效、双向通讯,客户端与服务端之间高频和低延时的消息交换机制;
Potlet
其他
- Test,为JUint和TestNG进行测试提供支持;
- Messaging,支持STOMP,从WebSocket客户端路由和处理STOMP消息
使用Spring框架能带来哪些好处?(优缺点)
优点
- 轻量级,与EJB容器相比,Ioc容器趋向于轻量级,更便于在有限的内存和CPU资源上的机器上,开发和部署;
- DI方法使得构造器、JavaBean和properties文件的依赖关系清晰;
- Web框架,Spring Web框架为开发者提供了一个精心设计的Web MVC框架;
- 集成主流框架,集成了已有的技术栈,例如J2EE,ORM等;
- 面向切面编程(AOP),Spring支持面向切面编程,将应用的业务逻辑和系统的服务分离;
- 事务管理,Spring提供了一个便捷的事务管理接口,适用于小型的本地事务处理(单DB)和复杂的共同事务处理(JTA);
- 异常处理,将特定技术的异常统一转换为Uncheked异常;
- 便捷的测试,测试相关代码囊括在框架中,利用POJO类,利用注入依赖写入测试数据。
缺点
- 调整阶段不直观,后期的bug对应阶段,不容易判断问题所在,需要花费一定时间去理解,每个框架都有的问题;(使用框架)
- Spring封装了很多JavaEE的内容,在满足快速开发高质量程序的同时,隐藏了实现细节,直接导致从Java工程师变成了Spring工程师,使得很多工程师离开了Spring变得无所适从,我们应当知其所以然。(了解底层)
Spring框架中都用到了哪些设计模式?
Spring框架中使用了大量的设计模式,列举几个具有代表性的。
-
DI:贯穿与BeanFactory和ApplicationContext接口的核心理念;
-
工厂模式:用BeanFactory创建Bean;
-
单例模式:默认创建的Bean为Singleton;
-
代理模式:在AOP和Remoting使用较多;
-
模板方法:用来解决代码重复的问题,例如JdbcTemplate、RestTemplate等;
-
前端控制器:DispatcherServlet对请求进行分发。
Spring IoC
IoC侧重于容器;
Bean侧重于被容器管理的Bean
什么是Spring IoC容器?
Spring框架的核心是Spring IoC容器。Spring IoC容器创建Bean对象,完成配置,装配工作,并管理它们完整的生命周期。
- Spring容器使用依赖注入管理组成应用程序的Bean对象;
- 容器通过读取配置元数据Bean Definition,接收对象实例化,配置和组装的指令;
- 可以通过XML,Java注解或者Java Config提供配置元数据Bean Definition;
什么是依赖注入?
依赖注入的英文缩写是Dependency Injection,DI。
在代码中将组件和服务分离,通过配置文件描述不同的组件所需的服务,交由IoC容器创建,配置和组装。
IoC和DI有什么区别?
IoC,inverse of control,指Bean的创建由程序员交由应用程序
DI是IoC实现的一种方式,在EJB使用DL实现
可以通过多少种方式完成依赖注入?
依赖注入可以通过三种方式完成
- 构造方法注入;
- setter注入;
- 接口注入;
Spring框架仅实现构造方法注入和setter注入,实际场景下,setter注入方式使用得更多。
构造方法注入 | setter注入 |
---|---|
没有部分注入 | 有部分注入 |
不会覆盖setter属性 | 会覆盖setter属性 |
任意修改都会创建一个新实例 | 任意修改不会创建一个新实例 |
适用于设置很多属性 | 适用于设置少量属性 |
Spring中有多少种IoC容器?
Spring提供了两种IoC容器,BeanFactory和ApplicationContext
两者采用了抽象工厂设计模式,ApplicationContext接口扩展了BeanFactory接口
BeanFactory | ApplicationContext |
---|---|
懒加载 | 即时加载 |
使用语法显式提供资源对象 | 自行创建、配置和管理对象 |
不支持国际化 | 支持国际化 |
不支持基于依赖的注解 | 支持基于依赖的注解 |
因此,BeanFactory被称为低级容器,ApplicationContext被称为高级容器。
请介绍下常用的BeanFactory容器?
BeanFactory最常用的是XmlBeanFactory,它根据XML文件中定义的内容,创建相应的Bean。
请介绍下常用的ApplicationContext容器?
- ClassPathXmlApplicationContext,从ClassPath下的XML配置文件读取上下文
- FileSystemXmlApplicationContext,从文件系统中的XML配置文件读取上下文
- XmlWebApplicationContext,从Web应用的XML文件读取上下文,Spring MVC
- ConfigServletWebServerApplicationContext,从Web应用的XML文件读取上下文,Spring Boot
列举IoC的一些好处?
- 最小化应用程序中的代码量;
- 最小影响,最少侵入机制,促进松耦合;
- 支持即时实例化和延迟加载Bean对象;
- 不需要单元测试用例中的任何单例或者JDNI查找机制,使应用程序易于测试。
简述Spring IoC的实现机制?
工厂模式 + 反射机制
interface Fruit {
public abstract void eat();
}
class Apple implments Fruit {
public void eat() {
System.out.println("Apple");
}
}
class Orange implments Fruit {
public void eat() {
System.out.println("Orange");
}
}
class Factory {
public static Fruit getInstance(String className) {
Fruit f = null;
try {
f = (Fruit) Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
class Client {
public static void main(String[] args) {
Fruit f = Factory.getInstance("com.spring.jerry.Apple");
if(f != null){
f.eat();
}
}
}
Fruit接口,有Apple和Orange两个实现类;
Factory工厂类通过反射机制,创建className对应的Fruit对象;
Client通过Factory工厂,获取对应的Fruit对象。
实际情况下,Spring IoC比这个复杂很多,还要考虑单例Bean,Bean属性的注入,Bean的依赖注入、依赖检查、支持集合等
Spring框架中有哪些不同类型的事件?
- 上下文更新事件,ContextRefreshedEvent。该事件在ApplicationContext被初始化或者更新时发布,也可以在调用ConfigurableApplicationContext接口中得refresh()方式时被触发;
- 上下文开始事件,ContextStartedEvent。当容器调用ConfigurableApplicationContext的start(),或者重新开始容器时触发该事件;
- 上下文停止事件,ContextStoppedEvent。当容器调用ConfigurableApplicationContext的stop()触发该事件;
- 上下文关闭事件,ContextClosedEvent。当ApplicationContext被关闭时,容器被关闭时,所有的单例Bean都会销毁;
- 请求处理事件,RequestHandlerEvent。在Web应用中,当一个HTTP请求(request)结束时触发该事件;
自定义事件类继承了ApplicationEvent
自定义事件监听器实现了ApplicationListener
ApplicationContext实现了ApplicationEventPublisher接口,在ApplicationContext发布事件
Spring Bean
什么是Spring Bean?
- Bean由Spring IoC容器实例化,配置,装配和管理;
- Bean基于用户提供给IoC容器的配置元数据Bean Definition创建;
Spring有哪些配置方式?
-
XML配置文件
在XML格式的配置文件指定Bean所需的依赖项和服务,以标签开头
<bean id="studentBean" class="org.edureka.firstSpring.StudentBean"> <property name="name" value="Edureka"></property> </bean>
-
注解配置
在相关类,方法,字段声明上使用注解,将Bean配置为组件类本身,需要在XML上开启注解装配
<beans>
<context:annotation-config/>
<!-- bean definitions go here -->
</beans>
- Java Config配置
Spring的Java配置是通过用@Configuration和@Bean来实现。
@Configuration等价于XML文件,@Bean等价于
现在Spring Boot项目较多,因此Java Config使用得较多。
三种方式可以混合使用
- MyBatis,我喜欢使用xml和mapper;
- Spring MCV请求的配置,我喜欢用@RequestMapping和@PostMapping;
- Spring MCV拦截器的配置,我喜欢用Java Config配置;
Spring支持几种Bean Scope?
Spring支持5种Bean Scope
-
Singleton,Spring IoC容器中仅有一个单Bean实例,默认;
-
Prototype,与Singleton相对,每个请求都会产生一个新的实例;
-
Request,每一次HTTP请求都会产生一个新的Bean实例,该bean仅在当前HTTP请求内有效;
-
Session,每一个Session都会产生一个新的Bean实例,该bean仅在当前HTTP Session内有效;
-
Application,每一个Web Application都会产生一个新的Bean实例,该bean在整个Web Application都有效
Request,Session和Application都是在Web应用才有,此外之前还有一个Global-Session级别已经废弃。Singleton使用得最多,其他四种使用得比较少。
Spring Bean在容器的生命周期是什么样的?
Spring Bean的初始化流程如下:
-
实例化对象
- Spring IoC容器根据Bean Definition实例化Bean对象
- 使用依赖注入填充所有属性
-
Aware相关的属性,注入到Bean对象
- 如果Bean实现BeanNameAware接口,则工厂通过传递Bean的beanName调用setBeanName(String name);
- 如果Bean实现BeanFactoryAware接口,则工厂通过传递自身的实例来调用setBeanFactory(BeanFactory beanFactory);
-
调用相应的方法,进一步初始化Bean对象
- 如果存在与Bean关联的任何BeanPostProcessor,则调用preProcessBeforeInitialization(Objcet bean, String beanName);
- 如果Bean实现了InitializingBean接口,就会调用afterPropertiesSet();
- 如果为Bean指定了init方法(中的init-method属性),则调用该方法;
- 如果存在与Bean关联的任何BeanPostProcessor,则调用postProcessAfterInitialization(Object bean, String beanName);
Spring Bean的销毁流程如下:
- 如果Bean实现了DispoableBean接口,Spring容器关闭时,会调用destroy();
- 如果为bean指定了destroty方法(中的destroy-method属性),那么将调用该方法。
什么是Spring的内部bean?
只有将Bean仅用作另一个Bean的属性时,才能将Bean声明为内部Bean。
- Spring提供基于XML的配置元数据在或者提供了元素的使用;
- 内部Bean总是匿名的,并且它们总是作为Prototype;
什么是Spring装配?
当Bean在Spring容器中组合在一起,被称为装配,其实和DI实际上一样。
自动装配有哪些方式?
Spring通过检查BeanFactory的内容,自动解析,配合bean的协作者。
自动装配的不同模式:
- no,默认设置不启动自动装配,显示地使用Bean引用进行装配;
- byName,根据Bean名称注入对象依赖项;
- byType,常用,根据Bean类型注入对象依赖项;
- 构造函数,调用类的构造函数注入依赖项;
- autodetect,容器首先尝试通过构造函数装配,如果不能,再尝试通过byType自动装配;
自动装配有什么局限?
- xml配置可以覆盖自动装配;
- 原数据类型、字符串无法自动装配;
- 因为自动装配不太精确,在复杂场景下使用明确的装配;
解释下什么是延迟加载?
BeanFactory默认延迟加载,ApplicationContext默认即时加载,我们可以在Bean设置lazy-init="true"指定延迟加载。
延迟加载指容器启动后不立即加载Bean,直至真实使用(getbBean()时),才真正创建加载。
Spring框架中的单例Bean是线程安全的吗?
Spring框架并没有对单例Bean进行任何多线程的封装处理。Spring关心的是根据配置文件,创建单例或者多例Bean的功能,关于单例Bean的线程安全和并发文体,需要开发者自行搞定。
但实际上,大部分Bean没有可变状态(例如Servlet类,Service类,Dao类),所以在某种程度上说单例Bean是线程安全的。
如果你的Bean有多种状态,又需要自行保证线程安全,最简单的做法是将Bean的作用域由Singleton改成Prototype。
Spring Bean如何解决循环依赖的问题?
循环依赖其实就是循环引用
Spring只能field属性,单例模式的循环依赖,通过ObjectFactory提前曝光自己的引用,从而在singletonFactories解决循环依赖问题。
Spring注解
什么是基于注解的容器配置?
开发人员通过在相关的类,方法或者字段声明上使用注解将配置移动到组件类本身,作为XML配置的替代方案。
@Configuration实现XML整体文件的代替;
@Bean实现的代替;
如何在Spring中启动注解配置?
默认情况下,Spring容器中未打开注解配置,因此要启动注解配置,需要在配置文件中配置context:annotation-config/,开启注解配置。
在Spring Boot项目中,默认情况已经开启注解配置。
@Component,@Controller,@Service,@Repository有什么区别?
对于程序来说,四者起的作用等价,对于程序员,声明的语义不同。
@Component是Spring管理组件的通用型;
@Controller表示控制器;
@Service表示服务层类;
@Repository表示Dao层;
@Required注解有什么用?
@Required应用于Bean属性的setter(),很少使用。
@Autowired注解有什么用?
@Autowired更准确地控制自动配置的位置和方式。
用在setter(),构造方法,具有任意名称或者多个参数的属性或方法,实现自动装配Bean;
默认采用byType。
@Qualifier注解有什么用?
当你创建多个相同类型的Bean,可以使用@Autowired或@Qualifier指定ID装配确切的Bean消除歧义。
Spring AOP
概念题,对切面的理解。
- AOP;
- Aspect;
- JoinPoint;
- PointCut;
- Advice;
- Target;
- AOP Proxy;
- Weaving;
什么是AOP?
AOP(Aspect-Oriented Programming),面向切面编程,它与OOP(Object-Oriented Programming)相相辅相成,提供了与OOP不同的抽象软件结构的视角。
- 在OOP中,以类(Class)作为基本单元;
- 在AOP中,以切面(Aspect)作为基本单元;
什么是Aspect?
Aspect由PointCut和Advice组成。
- 它既包括了连接点的定义,也包括了横切逻辑的定义;
- Spring AOP就是负责实施切面的框架,在切面指定的连接点中编织切面定义的横切逻辑。
AOP的工作重心在于如何增强编织目标对象的连接点上。
- 如何通过PointCut和Advice定位到特定的JointPoint上;
- 如何在Advice中编写切面代码;
可以简单地认为使用@Aspect注解的类就是切面。
什么是JoinPoint?
JoinPoint,连接点,程序运行中的一些时间点。
- 一个方法的执行;
- 一个异常的处理;
在Spring AOP中,只有方法连接点。
什么是PointCut?
PointCut,切点,匹配JoinPoint的谓词/条件
- Advice是和特定的PointCut关联的,并且在PointCut相匹配的JoinPoint中执行。Advice=》PointCut=》JoinPoint;
- 在Spring中,所有的方法都可以被认为是JoinPoint,但是我们并不希望给所有的方法添加Advice。因此PointCut的作用,就是提供一组规则(AspectJ PointCut expression language)匹配JoinPoint,给满足规则的JoinPoint添加Advice;
关于JoinPoint和PointCut的区别
JoinPoint和PointCut本质上是两个不同维度上的东西。
-
在Spring AOP中,所有方法都是JoinPoint,而PointCut是一个描述信息,修饰的是JoinPoint,这样我们就可以确定在哪些JoinPoint上织入Advice;
-
Advice在JoinPoint上执行,而PointCut规定了在哪些JoinPoint织入哪些Advice。
什么是Advice?
Advice,增强,特定JoinPoint处的Aspect所采取的动作称为Advice。
有哪些类型的Advice?
- Before,在JoinPoint方法之前执行,@Before注解标记;
- After Returning,在JoinPoint方法正常执行后执行,@AfterReturning注解标记;
- After Throwing,仅在JoinPoint方法抛出异常后使用,@AfterThrowing注解标记;
- After Finally,在JoinPoint方法执行后执行(包括正常执行和抛出异常两种情形),@After注解标记;
- Around,在JoinPoint方法执行前和执行后执行,@Around注解标记,最常使用。
和拦截器的执行时间十分相似。
什么是Target?
Target,织入Advice后的目标对象,也被称为Advised Object。
因为Spring AOP使用运行时代理的方式实现Aspect,因此Target总是一个代理对象,而不是指原来的对象。
AOP有哪些实现方式?
实现AOP的技术,主要分两大类:
- 静态代理,指使用AOP框架提供的命令进行编译,从而在编译阶段就可生成AOP代理类,因此也被称为编译时增强。
- 编译时编织(特殊编译器实现);
- 类加载时编织(特殊的类加载器实现);
- 动态代理,在运行时在内存“临时”生成AOP动态代理类,因此也被称为运行时增强。
- JDK动态代理,代理接口,核心是InvocationHandler类和Proxy类;
- CGLIB,代理类,通过继承实现,动态生成代理子类;
Spring AOP和AspectJ AOP有什么区别?
-
代理方式不同
- Spring AOP基于动态代理方式实现;
- AspectJ AOP基于静态代理方式实现;
-
PointCut支持的粒度不同
- Spring AOP仅支持方法级别;
- AspectJ AOP提供了完全的AOP支持,还支持属性级别的PointCut。
什么是编织(Weaving)?
Weaving,编织。
- 为了创建一个Advice对象而链接一个Aspect和其他应用类型或对象,称为编织(Weaving);
- 在Spring AOP中,编织在运行时执行,即动态代理。
Spring如何使用AOP切面?
在Spring AOP中,有两种方式配置AOP切面:
- 基于XML方式的切面实现;
- 基于注解方式的切面实现,目前主流喜欢使用注解方式;
小结
Spring AOP源码待读
Spring Transaction
什么是事务?
事务就是对一系列的数据库操作(例如插入多条数据)进行统一的提交或者回滚操作,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作。
这样可以防止出现脏数据,防止数据库数据出现问题。
事务的特性指的是?
ACID
- 原子性,Atomicity,一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会停留在中间的某个状态。事务在执行过程中发生错误,会被恢复(rollback)到事务开始前的状态,就像这个事务从未执行过。即事务不可分割、不可约简;
- 一致性,Consistency,在事务开始之前和事务结束之后,数据库的完整性没有被破坏,这表示所有写的数据必须符合所有预设约束、触发器和级联回滚等;
- 隔离性,Isolation,数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable);
- 持久性,durability,事务处理结束后,对数据的修改是永久性的,即时系统故障也不会丢失;
列举Spring支持的事务管理类型?
目前Spring提供两种类型的事务管理:
- 声明式事务,通过使用注解或者基于XML配置事务,从而使事务管理与业务代码分离;
- 编程式事务,通过编码的方式实现事务管理,需要在代码中显示地调用事务的获得、提高、回滚,维护起来非常困难;
实际场景下,我们一般使用SpringBoot+声明式注解事务。
- @EnableTransactionMangement开启注解事务管理;
- @Transactional指定回滚,事务传播,隔离级别,超时等属性
- SpringBoot根据导入的包注入默认的事务管理器;
- JDBC默认注入DataSourceTransactionManagement;
- JPA默认注入JpaTransactionManagement;
- 事务管理器都实现Spring中的PlatformTransactionManagement接口;
传播 功能
PROPAGATION_REQUIRED 支持当前事务,如果当前没有事务,则新建一个事务
PROPAGATION_SUPPORT 支持当前事务,如果当前没有事务,则以非事务执行
PROPAGATION_MANDATORY 支持当前事务,如果当前没有事务,则抛出异常
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,则把当前事务挂起
PROPAGATION_NOT_SUPPORT 以非事务方式操作,如果当前有事务,则把当前事务挂起
PROPAGATION_NEVER 以非事务方式操作,如果当前有事务,则抛出异常
Spring事务如何和不同的数据持久层框架做集成?
-
数据持久层框架是指Spring JDBC、Hibernate、Spring JPA、MyBatis等;
-
Spring事务管理通过PlatformTransactionManager进行管理。org.springframework.transaction
// PlatformTransactionManager
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface PlatformTransactionManager {
// 根据事务定义 TransactionDefinition获取TransactionStatus
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
// 根据情况,提交事务
void commit(TransactionStatus status) throws TransactionException;
// 根据情况,回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
getTransaction()获取TransactionStatus而不是创建事务,
- 因为如果当前存在事务,则不会进行创建,一般来说会与当前线程绑定,如果不存在事务,则进行创建;
- TransactionStatus不仅包含事务,还包含事务的其他信息——是否读写,是否为新创建的事务;
commit()和rollback()根据情况,跟事务的传播有关,假设A()和B()都使用了@Transactional。
-
AbstractPlatformTransactionManager实现了PlatformTransactionManager,基于模板方法模式,实现事务整体逻辑的骨架,而抽象方法(doCommit(),doRollback())交由子类实现;
-
所以,不同的数据持久层框架,会有相对应的PlatfromTransactionManager实现类。
- Hibernate5:HibernateTransactionManager;
- JDBC(MyBatis、Spring JDBC):DataSourceTransactionManager;
- JPA:JpaTransactionManager
为什么在Spring事务中不能切换数据源?
Spring多数据源场景下,开启事务的Service层中的方法,无法切换数据源。
因为在Spring的事务管理中,所使用的数据库连接会和当前线程所绑定,所以即使我们设置了另外一个数据源,使用的还是当前的数据源连接。
另外在多数据源且需要事务的场景,本身会带来多事务一致性的问题,暂时没有特别好的解决方案。
所以一般一个应用,除读写分离带来的多数据源,推荐只有一个数据源。
而且随着微服务的发展和流行,一个服务对应一个DB是比较常见的架构选择。
@Transactional注解有哪些属性?如何使用?
属性 | 类型 | 描述 |
---|---|---|
value | String | 指定事务管理器,可选 |
propagation | enum:Propagation | 设置事务传播行为,可选 |
isolation | enum:Isolation | 设置事务隔离级别,可选 |
readOnly | boolean | 读写/可读事务,默认读写事务 |
timeout | int,seconds | 设置事务超时时间 |
rollbackFor | Class[],必须继承自Throwable | 导致事务回滚的异常类数组 |
rollbackForClassName | String[],必须继承自Throwable | 导致事务回滚的异常类名字数组 |
noRollbackFor | Class[],必须继承自Throwable | 不会导致事务回滚的异常类数组 |
noRollbackForClassName | String[],必须继承自Throwable | 不会导致事务回滚的异常类名字数组 |
一般情况下,我们直接使用@Transactional的所有默认属性。
具体用法:
- @Transactional可以作用于接口、接口方法、类以及类方法上。当作用于类上,该类的所有public都将具有该类型的事务属性,作用在方法上会覆盖作用在类上的;
- Spring建议不要在接口或者接口方法上使用该注解,因为只有使用基于接口的代理类才会生效;
- @Transactional注解只应该应用到public方法上,这是由Spring AOP的本质决定的。在protected,默认,private使用@Transactional将会忽略,也不会抛出异常;
源码
@Transactional注解的属性,会被解析为TransactionDefinition对象
// Transactional.java
package org.springframework.transaction;
import java.sql.Connection;
import org.springframework.lang.Nullable;
public interface TransactionDefinition {
// 事务的传播级别由Spring自身所定义
// 定义了三类七种传播级别
// 支持当前事务的情况
// 如果当前存在事务,则使用该事务
// 如果当前不存在事务,则创建一个新事务
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;
// Spring使用后端数据库默认的隔离级别,四种隔离级别
// MySQL默认采用REPEATABLE_READ隔离级别
// Oracle默认采用READ_COMMITTED隔离级别
int ISOLATION_DEFAULT = -1;
// 最低隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、不可重复读和幻读
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
// 允许读取并发事务已经提交的数据,可以阻止脏读,但是不可重复读和幻读仍可能发生
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
// 对同一字段的多次读取结果都是一致的,除非本身事务修改数据,可以阻止脏读和不可重复读,幻读仍可能发生
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
// 最高的隔离级别,完全服从ACID隔离级别,所有事务依次执行,可以阻止脏读,不可重复读和幻读,但十分影响性能
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
int TIMEOUT_DEFAULT = -1; // 事务的超时
int getPropagationBehavior(); // 事务的传播行为
int getIsolationLevel(); // 事务的隔离级别
int getTimeout(); // 事务的超时时间
boolean isReadOnly(); // 事务是否只读
@Nullable
String getName(); // 事务的名字
}
RuleBasedTransactionAttribute定义回滚的异常类
// RuleBasedTransactionAttribute
package org.springframework.transaction.interceptor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.lang.Nullable;
@SuppressWarnings("serial")
public class RuleBasedTransactionAttribute extends DefaultTransactionAttribute implements Serializable {
/** Prefix for rollback-on-exception rules in description strings. */
public static final String PREFIX_ROLLBACK_RULE = "-";
/** Prefix for commit-on-exception rules in description strings. */
public static final String PREFIX_COMMIT_RULE = "+";
/** Static for optimal serializability. */
private static final Log logger = LogFactory.getLog(RuleBasedTransactionAttribute.class);
@Nullable
private List<RollbackRuleAttribute> rollbackRules;
public RuleBasedTransactionAttribute() {
super();
}
public RuleBasedTransactionAttribute(RuleBasedTransactionAttribute other) {
super(other);
this.rollbackRules = (other.rollbackRules != null ? new ArrayList<>(other.rollbackRules) : null);
}
public RuleBasedTransactionAttribute(int propagationBehavior, List<RollbackRuleAttribute> rollbackRules) {
super(propagationBehavior);
this.rollbackRules = rollbackRules;
}
public void setRollbackRules(List<RollbackRuleAttribute> rollbackRules) {
this.rollbackRules = rollbackRules;
}
public List<RollbackRuleAttribute> getRollbackRules() {
if (this.rollbackRules == null) {
this.rollbackRules = new LinkedList<>();
}
return this.rollbackRules;
}
@Override
public boolean rollbackOn(Throwable ex) {
if (logger.isTraceEnabled()) {
logger.trace("Applying rules to determine whether transaction should rollback on " + ex);
}
RollbackRuleAttribute winner = null;
int deepest = Integer.MAX_VALUE;
if (this.rollbackRules != null) {
for (RollbackRuleAttribute rule : this.rollbackRules) {
int depth = rule.getDepth(ex);
if (depth >= 0 && depth < deepest) {
deepest = depth;
winner = rule;
}
}
}
if (logger.isTraceEnabled()) {
logger.trace("Winning rollback rule is: " + winner);
}
// User superclass behavior (rollback on unchecked) if no rule matches.
if (winner == null) {
logger.trace("No relevant rollback rule found: applying default rules");
return super.rollbackOn(ex);
}
return !(winner instanceof NoRollbackRuleAttribute);
}
@Override
public String toString() {
StringBuilder result = getAttributeDescription();
if (this.rollbackRules != null) {
for (RollbackRuleAttribute rule : this.rollbackRules) {
String sign = (rule instanceof NoRollbackRuleAttribute ? PREFIX_COMMIT_RULE : PREFIX_ROLLBACK_RULE);
result.append(',').append(sign).append(rule.getExceptionName());
}
}
return result.toString();
}
}
什么是事务的隔离级别?分成哪些隔离级别?
什么是事务的传播级别?分成哪些传播级别?
什么是事务的超时属性?
事务超时指的是一个事务所允许执行的最长时间
// Transactional.java
package org.springframework.transaction;
public interface TransactionDefinition {
int TIMEOUT_DEFAULT = -1; // 事务的超时
}
什么是事务的只读属性?
事务的只读属性是对事务性资源进行只读操作或者读写操作。
事务性资源指被事务管理的资源,例如数据源,JMS资源以及自定义的事务性资源等
确定对事务性资源进行只读操作,将事务设置只读,数据库就可以采取一些优化,提高事务的处理性能
一次执行单条查询语句,没有必要启动事务支持;
而一次执行多条查询语句,则需要启动事务支持,保持数据的一致性,防止在两个SQL查询语句之间,数据被其他用户修改。
// Transactional.java
package org.springframework.transaction;
public interface TransactionDefinition {
boolean isReadOnly(); // 事务是否只读
}
什么是事务的回滚规则?
事务的回滚规则是由Spring自身定义的概念,定义了哪些异常会导致事务回滚,而哪些异常不会导致事务回滚。
- 默认事务遇到运行期异常回滚,遇到检查性异常不回滚;
- 自定义声明,rollbackFor,rollbackForClassName,noRollbackFor,noRollbackForClassName
简单介绍下TransactionStatus?
// TransactionStatus.java
package org.springframework.transaction;
import java.io.Flushable;
public interface TransactionStatus extends SavepointManager, Flushable {
// 是否是新创建的事务
boolean isNewTransaction();
// 是否有 SavePoint
boolean hasSavepoint();
// 设置为只回滚
void setRollbackOnly();
// 是否为只回滚
boolean isRollbackOnly();
// flush操作
@Override
void flush();
// 事务是否完成
boolean isCompleted();
}
事务对象在TransactionStatus的实现类DefaultTransactionStatus中
// 表示事务对象
@Nullable
private final Object transaction;
使用Spring事务有什么优点?
- 通过PlatformTransactionManager为不同的数据层持久化框架(原生JDBC、Spring JDBC、JPA、Hibernate或者Mybatis)提供统一的API,可以使用不同的数据源;
- 通过声明式事务,使业务代码和事务管理的逻辑分离;
我喜欢注解+声明式事务
小结
PlatformTransactionManagement和PlatformTransactionManger的关系
Spring Data Access
Spring支持哪些ORM框架?
- Hibernate;
- JPA;
- MyBatis;
- JDO;
- OJB;
在Spring框架中如何更有效地使用JDBC?
Spring提供了Spring JDBC框架,方便我们使用JDBC,主要使用JdbcTemplate类。
- 将数据库数据转换为数据类型或者对象;
- 执行数据库操作语句;
- 提供自定义的数据错误处理;
Spring数据访问层有哪些异常?
通过使用Spring数据访问层,统一了各个数据持久化层框架的不同异常
org.springframework.dao,DataAccessException异常及其子类
JPA规范与ORM框架之间的关系是怎么样的?
JPA规范本质上就是一种ORM规范,具体的ORM框架由各个厂家自行实现。
在不同ORM框架切换所编写的代码有一定的差异,因此使用Spring Data JPA封装Respository层。
引用
本文是从芋道源码学习总结的文章,以后再补学习链接