SSM篇面试题
# Spring的事务隔离级别
Spring事务隔离级别比数据库事务隔离级别多一个default
- DEFAULT (默认)
这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。
mysql默认是REPEATABLE_READ Sql Server , Oracle默认是READ_COMMITTED
- READ_UNCOMMITTED (读未提交)
这是事务最低的隔离级别,它允许一个事务可以看到另一个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
- READ_COMMITTED (读已提交)
不允许一个事务读取另一个事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。大致原理是在一个事务读取一条记录时加上S锁,语句执行后释放S锁,使得在读取的时候其他事务不能进行修改,从而保证读取时的记录是最新,最真实的数据。
- REPEATABLE_READ (可重复读)
这种事务隔离级别可以防止脏读、不可重复读,但是可能出现幻像读。大致原理是在一个事务读取一条记录时加上S锁,直到事务提交后才释放S锁,从而达到一个事务重复读取一条记录时,其他事务不能对这条记录进行修改。
- SERIALIZABLE(可序列化)
这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行。除了防止脏读、不可重复读外,还避免了幻像读。当一个事务在操作一条记录时,会锁住所有操作。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| Read uncommitted | √ | √ | √ |
| Read committed | × | √ | √ |
| Repeatable read | × | × | √ |
| Serializable | × | × | × |
存在问题:
- 脏读:一个事务,读取到另一个事务中未提交的数据。
- 不可重复读(虚读):在同一个事务中,两次读取到的数据不一样。不可重复读的重点是修改,同样的条件,你读取过的数据,再次读取出来发现值不一样了
公司发工资了,领导把5000元打到singo的账号上,但是该事务并未提交,而singo正好去查看账户,发现工资已经到账,是5000元整(脏读),非常高兴。 可是不幸的是,领导发现发给singo的工资金额不对,是2000元,于是迅速回滚了事务,修改金额后,将事务提交,最后singo实际的工资只有2000元(不可重复读),singo空欢喜一场。
- 幻读:一个事务操作(DML)数据表中所有记录,另一个事务添加了一条数据,则第一个事务查询不到自己的修改。幻读的重点在于新增或者删除:同样的条件,第 1 次和第 2 次读出来的记录数不一样
目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。
此时,事务B插入一条工资也为5000的记录。
这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。
隔离级别与锁的关系
- 在Read Uncommitted级别下,读操作不加S锁;
- 在Read Committed级别下,读操作需要加S锁,但是在语句执行完以后释放S锁;
- 在Repeatable Read级别下,读操作需要加S锁,但是在事务提交之前并不释放S锁,也就是必须等待事务执行完毕以后才释放S锁。
- 在Serialize级别下,会在Repeatable Read级别的基础上,添加一个范围锁。保证一个事务内的两次查询结果完全一样,而不会出现第一次查询结果是第二次查询结果的子集。
S锁:又称读锁,保证了其他事务可以读A记录,但在当前事务释放A上的S锁之前不能对A做任何修改 X锁:又称写锁,保证了其他事务在当前事务释放A记录的X锁之前不能读取和修改A
项目开发推荐使用的是**READ_COMMITTED **
原因是:效率和安全性上适应大部分的业务场景
在REPEATABLE_READ隔离级别下,存在间隙锁,导致出现死锁的几率比READ_COMMITTED大的多
在REPEATABLE_READ隔离级别下,条件列未命中索引会锁表!而在READ_COMMITTED隔离级别下,只锁行
在READ_COMMITTED隔离级别下,半一致性读(semi-consistent)特性增加了update操作的并发性
在5.1.15的时候,innodb引入了一个概念叫做“semi-consistent”,减少了更新同一行记录时的冲突,减少锁等待。
所谓半一致性读就是,一个update语句,如果读到一行已经加锁的记录,此时InnoDB返回记录最近提交的版本,由MySQL上层判断此版本是否满足update的where条件。若满足(需要更新),则MySQL会重新发起一次读操作,此时会读取行的最新版本(并加锁)!
Spring设置事物隔离级别示例:@Transactional(isolation = Isolation.READ_UNCOMMITTED)
# Spring事务的传播行为
**事务传播行为:**事务协调员对事务管理员所携带事务的处理态度或者方法间对事务的处理行为
如:方法1(事务管理员)开启了事务后,方法2(事务协调员)会进行的行为
传播属性行为类型
| 事务传播行为类型 | 说明 |
|---|---|
| PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
| PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
| PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
| PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
| PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
| PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
| PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行,嵌套的事务可以独立于当前事务进行单独提交或回滚。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。不过这个并不是所有厂商都支持这种事务提交方式,mysql是支持的。 |
行为
使用@Transactional注解控制
@Transactional(propagation = Propagation.REQUIRES_NEW,
rollbackFor = {NullPointerException.class})
2
# Spring事务的实现原理
面试话术
Spring好像提供两种事务的实现方式,一种是编程式事务,一种是声明式事务,编程式事务太少用没什么印象了,声明式事务就是用那个@Transcational注解来实现,其实spring的事务就是AOP的一个很好的体现,本来一般事务是由数据库,也就是MySQL来进行管理的,但是spring为了方便我们在业务逻辑层面操作事务,对这个事务的功能进行了扩展,当一个方法添加了@Transcational注解后,spring会基于这个类生成一个代理对象,作为bean,来方便当调用了这个方法时来使用这个代理对象对方法进行加强,加强其实就是如果这个方法有涉及到数据库操作,那么就会关闭事务的自动提交,然后执行完业务逻辑后,如果没有出现异常,则直接提交事务,如果有则进行回滚,然后好像得是RuntimeException才能被捕获到进行回滚。
# 请说说哪些情况会导致Spring事务失效?
面试话术
- @Transactional配置的方法非public权限修饰
- @Transactional所在类非Spring容器管理的bean
- @Transactional所在类中,注解修饰的方法被本类的其他内部方法调用
- 业务代码抛出异常类型非RuntimeException,事务失效
- 业务代码中存在异常时用try catch捕获但是没有抛出RuntimeException
- @Transactional中Propagation属性设置为not_support不支持事务
- @Transactional(propagation = Propagation.REQUIRED) 默认值,当前方法必须处在事务中。前面的业务方法有事务,同共享前面的事务;如果前面没有事务,则开启新事务
- @Transactional(propagation = Propagation.SUPPORTS) 前面业务方法有事务,同共享前面的事务,如果前面没有事务,则不开事务,通常在查询业务中
- @Transactional(propagation = Propagation.REQUIRES_NEW) 不管前面业务是否有事务,都要独立创建新事务。
- @Transactional(propagation = Propagation.NOT_SUPPORTED) 不支持事务
- mysql数据库引擎选用了MyISAM,这个引擎不支持事务
# Spring中用到了哪些设计模式
Spring 框架中用到了哪些设计模式?
- 工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
- 代理设计模式 : Spring AOP 功能的实现。
- 单例设计模式 : Spring 中的 Bean 默认都是单例的。
- **建造者模式:**那些带Builder的,比如BeanDefinitionBuilder
- 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
- 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
面试官:“谈谈Spring中都用到了那些设计模式?”。 (opens new window)
# 请说说Spring是如何解决循环依赖问题的?
属性注入的循环依赖:
@Component
public class TestService1{
@Autowired
private TestSerivice2 service2;
}
@Component
public class TestService2{
@Autowired
private TestSerivice1 service1;
}
2
3
4
5
6
7
8
9
10
11
spring内部有三级缓存(三个Map结构):
1)singletonObjects 一级缓存,用于存放完全初始化好的 bean(最终要被用户使用)
2)earlySingletonObjects 二级缓存,存放原始的 bean 对象(尚未填充属性),避免Bean实例化完成但未初始。
3)singletonFactories 三级缓存,存放 bean 工厂对象(ObjectFactory),用于生成AOP代理对象(@Transactional或@Async)。
用户从SpringIOC容器获取一个Bean的底层,会先从一级缓存获取Bean;一级缓存拿不到,从二级缓存获取;二级缓存获取不到从三级缓存获取。
# Spring Bean的生命周期
面试话术
spring Bean的生命周期主要分为四个阶段,和多个扩展点
四个阶段
- 实例化(构造器) Instantiation
- 属性赋值(依赖注入) Populate
- 初始化 Initialization
- 销毁 Destruction
多个扩展点如:
**BeanPostProcessor **
- postProcessBeforeInitialization
- postProcessAfterInitialization
多个Aware接口
大致生命周期流程:
- spring启动,查找并加载需要被spring管理的bean,进行bean的实例化
- bean实例化后对bean进行依赖注入
- 然后调用多个Aware接口,如果bean有实现这些接口的话。比如 BeanNameAware 会调用setBeanName()方法,也就是给Bean起名字;还有BeanFactoryAware,会调用setBeanFactory方法设置bean工厂,以及 ApplicationContextAware,会调用setApplicationContext方法。如果是web应用的话会调用servletContext方法,然后调用BeanPostProcess的预初始化方法。
- 如果Bean实现了InitializingBean接口的话,spring会调用其afterPropertiesSet()方法,以及如果有使用@PostConstruct注释声明init方法的话,在执行自定义init前该方法也会被调用。比如mybatisplus就实现了这个接口,由于mp不是springboot的,所以没有走自动装配的流程,而是走的bean的生命周期。
- 接着就走自定义的init方法。
- 这个时候Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
- 同样如果bean实现了DisposableBean接口的话,spring会调用它的destory()方法,这个方法也是会在我们用@PreDestroy声明的销毁方法执行前被调用。
- 最后就是执行自定义的销毁方法。
- 然后需要注意的就是只有单例的时候销毁方法才会伴随着容器的销毁而调用,如果是多例bean的话则不然,当这个bean没有再被引用的时候就会被销毁。
再谈Spring中Bean的生命周期 (opens new window)
面试话术
Spring Bean的生命周期大致上是可以分为四个阶段以及多个扩展点,四个阶段是实例化、依赖注入、初始化、销毁。
- 首先实例化就是通过反射创建实例嘛,通过调用createBeanInstance来创建对象
- 然后就是依赖注入,这里的话会有个循环依赖问题,Spring 是通过三级缓存来解决的
- 然后在初始化之前开始有很多的扩展点了,比如查看bean是否有实现各种Aware接口,比如BeanNameAware、BeanFactoryAware、ApplicationContextAware、实现BeanNameAware就会调用setBeanName,实现BeanFactoryAware就会调用setBeanFactory、实现ApplicationContextAware就会调用setApplicationContext。
- 然后就到bean的初始化,通过调用BeanPostProcessor 中的前置方法来完成对bean前置工作,然后如果Bean实现了InitializingBean接口的话,spring会调用其afterPropertiesSet()方法来设置bean对象,好像mybatisplus走的就是这种初始化,然后才会去调用我们用@PostConstruct声明的自定义init方法。
- 然后会调用BeanPostProcessor 的后置方法,来完成对bean的后置工作,好像aop就是在这实现的
- 前置后置都结束后bean就可以正常使用了
- 然后就是bean的销毁,在销毁的时候会判断bean是否实现了DisposableBean接口,有的话则会在调用销毁方法前调用destoryMethod方法,然后再会去调用我们用@PreDestroy声明的销毁方法,不过销毁方法的调用时机跟bean的类型也有关,一般默认是创建单例bean,所以是伴随着容器的销毁而销毁,而多例bean的话则是发现后文没有再引用的时候就会进行销毁了。
# 说说SpringMVC的组件和执行流程
前端控制器:DispatcherServlet
用户请求到达前端控制器,它就相当于 MVC 模式中的 C,DispatcherServlet 是整个流程控制的中心,由 它调用其它组件处理用户的请求,DispatcherServlet 的存在降低了组件之间的耦合性。处理器映射器:HandlerMapping
负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的 映射方式,例如:配置文件方式,实现接口方式,注解方式等。处理器适配器:HandlerAdapter
通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理 器进行执行。处理器:Handler
它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由 Handler 对具体的用户请求进行处理。视图解析器:View Resolver
负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名,即 具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。视图:View SpringMVC
框架提供了很多的 View 视图类型的支持,包括:jstlView、freemarkerView、pdfView等。最 常用的视图就是 jsp。一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程 序员根据业务需求开发具体的页面

①用户发送请求至前端控制器DispatcherServlet。
② DispatcherServlet收到请求调用HandlerMapping处理器映射器。
③处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
④ DispatcherServlet调用HandlerAdapter处理器适配器。
⑤ HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
⑥ Controller执行完成返回ModelAndView。
⑦ HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
⑧ DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
⑨ ViewReslover解析后返回具体View。
⑩ DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。DispatcherServlet响应用户。
面试话术
mvc的核心组件有:DispatcherServlet前端控制器、Handler处理器,也就相当于我们写的controller、HandlerMapping处理器映射器、HandlerAdpater处理器适配器、ViewResolver视图解析器
然后执行流程的话我记得大致是这样的,用户发送请求到前端控制器,前端控制器进行一个转发,向控制器映射器查询请求的Handler,也就是我们写在Controller上的那个访问路径,然后得到位置后前端控制器再去向处理器适配器发起调用请求,由处理器适配器调用对应的处理器,也就是我们写的controller,最后返回一个ModelAndView对象,这里如果使用了@ResponseBody的话会直接返回json格式的响应给前端控制器,就不会走视图解析器了。没用的话就会走视图解析器根据ModelAndView对象渲染视图响应给前端控制器,再由前端控制器响应给用户。
# 说一说你对AOP的理解?
面试话术
AOP就是面向切面编程嘛,也是Spring的一个核心思想之一,最主要的特点就是解耦,在不污染原代码的情况下对目标方法进行加强,如果没有AOP就等于要在所有的方法中都添加这些共性的代码,导致代码非常的冗余,AOP就是为解决这种问题而存在的。比如说日志的记录,权限处理,以及mybatis的分页其实也是利用了AOP的思想。比如之前我参与的一个项目就有用到日志管理,用了一种比较灵活的方式来使用AOP,在需要加入日志管理的方法上加入自定义注解,以这个注解作为切点来对方法进行增强。
# 说一说你对IOC的理解?
面试话术
IOC就是控制反转嘛,对象的创建权以及对象属性的设置都交给了容器处理,就使用spring之前的编码,我们都要自己new对象,然后对对象的属性进行set赋值也好,构造器赋值也好,总之整个对象创建出来就需要我们手动进行管理嘛,使用Spring后,对象的创建由容器管理了,这个就是控制,反转就是指属性的注入,通过DI来进行属性注入,一般用的都是@Autowired比较多。整个Bean的生命周期,从创建到销毁都是由容器进行控制的。容器一般有三级缓存,就是一个三层map结构。