SSM相关面试题
1 Spring
1.1 什么是Spring IOC 和DI ?
难易程度:☆☆☆
出现频率:☆☆☆☆
① 控制反转(IOC):Spring容器使用了工厂模式为我们创建了所需要的对象,我们使用时不需要自己去创建,直接调用Spring为我们提供的对象即可,这就是控制反转的思想。
② 依赖注入(DI):Spring使用Java Bean对象的Set方法或者带参数的构造方法为我们在创建所需对象时将其属性自动设置所需要的值的过程就是依赖注入的基本思想。
1.2 有哪些不同类型的依赖注入实现方式?
难易程度:☆☆
出现频率:☆☆
依赖注入分为接口 注入,Setter方 法注入和构造器注入
构造器依赖注入:构造器依赖注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每 个参数代表一个对其他类的依赖。
Setter方法注入:Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之 后,调用该bean的setter方法,即实现了基于setter的依赖注入。
1.3 spring的bean的生命周期
难易程度:☆☆☆☆☆
出现频率:☆☆☆
Spring Bean的生命周期如下图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-umbdgmmk-1663995673768)(SSM相关面试题.assets/image-20220213131235983.png)]
1、实例化Bean:
反射 BeanDefinition
-
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。
-
对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。(BeanDefinition是Spring 中极其重要的一个概念,它存储了bean对象的所有特征信息,如是否单例,是否懒加载,factoryBeanName 等。BeanDefinition对象的创建时通过各种bean的配置信息进行构建 )
2、设置对象属性(依赖注入):实例化后的对象被封装在BeanWrapper对象中,紧接着Spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成依赖注入。
3、处理Aware接口:接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:
-
如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值。
-
如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
-
如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文。
4、Bean的后置处理器(BeanPostProcessor):如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用
postProcessBeforeInitialization方法。由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;
5、InitializingBean 与 init-method:如果Bean在Spring配置文件中配置了init-method 属性,则会自动调用其配置的初始化方法。
6、Bean的后置处理器(BeanPostProcessor):如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;
以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。
7、DisposableBean:当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;
8、destroy-method:最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
示例代码:
User类:
@Component
@Slf4j
public class User implements BeanNameAware , BeanFactoryAware , ApplicationContextAware {
public User() {
System.out.println("a的构造方法执行了.........");
}
private String name ;
@Value("张三")
public void setName(String name) {
System.out.println("setName方法执行了.........");
}
@Override
public void setBeanName(String name) {
System.out.println("setBeanName方法执行了.........");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("setBeanFactory方法执行了.........");
}
@PostConstruct
public void init() {
System.out.println("init方法执行了.................");
}
@PreDestroy
public void destory() {
System.out.println("destory方法执行了...............");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("setApplicationContext方法执行了........");
}
}
Bean的后置处理器(BeanPostProcessor):
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization方法执行了.........");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization方法执行了.........");
return bean;
}
}
测试类:
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = applicationContext.getBean(User.class);
System.out.println(user);
applicationContext.close();
控制台输出结果:
Generic bean: class [com.itheima.user.domain.User]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [E:\idea-edu-workplace\spring\target\classes\com\itheima\user\domain\User.class]
a的构造方法执行了.........
setName方法执行了.........
setBeanName方法执行了.........
setBeanFactory方法执行了.........
setApplicationContext方法执行了........
postProcessBeforeInitialization方法执行了.........
init方法执行了.................
postProcessAfterInitialization方法执行了.........
com.itheima.user.domain.User@4386f16
destory方法执行了...............
InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。
1.4 Spring支持的几种bean的作用域 Scope
难易程度:☆☆☆
出现频率:☆☆
struts2—类似于springmvc
Spring框架支持以下五种bean的作用域:
singleton : bean在每个Spring ioc 容器中只有一个实例。
prototype:一个bean的定义可以有多个实例。
request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的 Spring ApplicationContext情形下有效。
global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基 于web的Spring ApplicationContext情形下有效。
1.5 Spring框架中的单例bean是线程安全的吗?
难易程度:☆☆☆
出现频率:☆☆☆
不是线程安全的
当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求对应的业务逻辑(成员方法),如果该处理逻辑中有对该单列状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。
Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。
Spring bean并没有可变的状态(比如Service类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。
如果你的bean有多种状态的话(比如 View Model对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用由“singleton”变更为“prototype”。
1.6 Spring自动装配 bean 有哪些方式?
难易程度:☆☆
出现频率:☆☆
spring的自动装配功能的定义:无须在Spring配置文件中描述javaBean之间的依赖关系(如配置、)。
自动装配模式:
1、no:这是 Spring 框架的默认设置,在该设置下自动装配是关闭的,开发者需要自行在 bean 定义中用标签明确的设置依赖关系 。
2、byName:该选项可以根据bean名称设置依赖关系 。 当向一个bean中自动装配一个属性时,容器将根据bean的名称自动在在配置文件中查询一个匹配的bean。 如果找到的话,就装配这个属性,如果没找到的话就报错 。
3、byType:该选项可以根据 bean 类型设置依赖关系 。 当向一个 bean 中自动装配一个属性时,容器将根据 bean 的类型自动在在配置文件中查询一个匹配的 bean。 如果找到的话,就装配这个属性,如果没找到的话就报错 。
4、constructor :构造器的自动装配和byType模式类似,但是仅仅适用于与有构造器相同参数的bean,如果在容器中没有找到与构造器参数类型一致的bean ,那么将会抛出异常 。
5、default:该模式自动探测使用构造器自动装配或者byType自动装配 。 首先会尝试找合适的带参数的构造器,如果找到的话就是用构造器自动装配,如果在bean内部没有找到相应的 构造器或者是无参构造器,容器就会自动选择 byTpe 的自动装配方式 。
比如如下注入:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sZYGBXFM-1663995673769)(SSM相关面试题.assets/image-20220821220013607.png)]
可以改造为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qTi40DPq-1663995673770)(SSM相关面试题.assets/image-20220821220036096.png)]
1.7 你用过哪些重要的Spring注解?
难易程度:☆☆☆
出现频率:☆☆☆☆
1、@Component- 用于服务类。
@Service
@Repository
@Controller
2、@Autowired - 用于在 spring bean 中自动装配依赖项。通过类型来实现自动注入bean。和@Qualifier注解配合使用可以实现根据name注入bean。
3、@Qualifier - 和@Autowired一块使用,在同一类型的bean有多个的情况下可以实现根据name注入的需求。
4、@Scope - 用于配置 spring bean 的范围。
5、@Configuration,@ComponentScan 和 @Bean - 用于基于 java 的配置。
6、@Aspect,@Before,@After,@Around,@Pointcut - 用于切面编程(AOP)
1.8 Spring中的事务是如何实现的
难易程度:☆☆☆
出现频率:☆☆☆
Spring支持编程式事务管理和声明式事务管理两种方式!
编程式事务控制:需要使用TransactionTemplate来进行实现,这种方式实现对业务代码有侵入性,因此在项目中很少被使用到。
声明式事务管理:声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在
目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过**@Transactional**注解的方式,便可以将事务规则应用到业务逻辑中。
1.9 Spring中事务失效的场景?
难易程度:☆☆☆☆
出现频率:☆☆☆
因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时, 那么这个注解才会⽣效 ,
如果使用的是被代理对象调用, 那么@Transactional不会失效
同时如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现 的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效
如果在业务中对异常进行了捕获处理 , 出现异常后Spring框架无法感知到异常, @Transactional也会失效
1.10 说一下Spring的事务传播行为
难易程度:☆☆☆☆
出现频率:☆☆☆
-
*PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就 加入该事务,该设置是最常用的设置。
-
PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不 存在事务,就以非事务执行。
-
PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前 不存在事务,就抛出异常。
-
*PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
-
PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前 事务挂起。
-
PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
-
PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则 按REQUIRED属性执行。
1.11 什么是AOP , 你们项目中有没有使用到AOP
难易程度:☆☆☆
出现频率:☆☆☆
AOP一般称为面向切面编程,作为面向对象的一种补充,用于 将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模 块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时 提高了系统的可维护性。
在我们的项目中我们自己写AOP的场景其实很少 , 但是我们使用的很多框架的功能底层都是AOP , 例如 :
1、统一日志处理
2、spring中内置的事务处理
项目做完了,代码开发完了
新的需求:
是谁,在什么时间,修改了数据(修改之前和修改之后),删除了什么数据,新增了什么数据
方法的命名(必须统一)
新增:
add,insert
1.12 JDK动态代理和CGLIB动态代理的区别
难易程度:☆☆☆
出现频率:☆☆☆
Spring中AOP底层的实现是基于动态代理进行实现的。
常见的动态代理技术有两种:JDK的动态代理和CGLIB。
两者的区别如下所示:
1、JDK动态代理只能对实现了接口的类生成代理,而不能针对类
2、Cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法进行增强,但是因为采用的是继承,所以该类或方法最好不要声明为final,对于final类或方法,是无法继承的。
Spring如何选择是用JDK还是cglib?
1、当bean实现接口时,会用JDK代理模式
2、当bean没有实现接口,会用cglib实现
3、可以强制使用cglib
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KcfyDnIU-1663995673770)(SSM相关面试题.assets/image-20220823002551782.png)]
1.13 spring中的循环引用
难易程度:☆☆☆☆☆
出现频率:☆☆☆
1.13.1 什么是Spring的循环依赖?(高频)
简单的来说就是A依赖B的同时,B依赖A。在创建A对象的同时需要使用的B对象,在创建B对象的同时需要使用到A对象。如下代码所示:
@Component
public class A {
public A(){
System.out.println("A的构造方法执行了...");
}
private B b;
@Autowired
public void setB(B b) {
this.b = b;
System.out.println("给A注入B");
}
}
@Component
public class B {
public B(){
System.out.println("B的构造方法执行了...");
}
private A a;
@Autowired
public void setA(A a) {
this.a = a;
System.out.println("给B注入了A");
}
}
1.13.2 出现循环依赖以后会有什么问题?(高频)
对象的创建过程会产生死循环,如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eZaBzGTG-1663995673771)(SSM相关面试题.assets/image-20220214004925047.png)]
在spring中通过某些机制(三级缓存)帮开发者解决了部分循环依赖的问题。
1.13.3 spring如何解决循环依赖的?(高频)
生命周期回顾:
1、Spring扫描class得到BeanDefinition
2、根据得到的BeanDefinition,根据class推断构造方法, 通过反射得到一个对象(原始对象)
3、为原始对象填充属性(依赖注入)
4、如果原始对象中的某一个方法配置的有AOP,则需要针对于该原始对象生成一个代理对象
5、把最终的生成的代理对象放入单例池(singletonObjects)中, 下次getBean时直接从单例池拿即可
当然bean的整个生命周期很复杂, 还有很多的步骤, 这里就不一一列举了。
Spring解决循环依赖是通过三级缓存,对应的三级缓存如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cikXGTNI-1663995673772)(SSM相关面试题.assets/image-20220214125510368.png)]
缓存 | 源码名称 | 作用 |
---|---|---|
一级缓存 | singletonObjects | 单例池; 缓存已经经历了完整声明周期, 已经初始化完成的bean对象 |
二级缓存 | earlySingletonObjects | 缓存早期的bean对象(生命周期还没有走完) |
三级缓存 | singletonFactories | 缓存的是ObjectFactory, 表示对象工厂, 用来创建某个对象的 |
一级缓存作用:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iU3MF78n-1663995673772)(SSM相关面试题.assets/image-20220822214906139.png)]
一级缓存解决不了循环依赖问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PTvnLr6y-1663995673773)(SSM相关面试题.assets/image-20220822215206712.png)]
二级缓存的作用:如果要想打破上述的循环 , 就需要一个中间人的参与, 这个中间人就是缓存。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AKlMpLBY-1663995673773)(SSM相关面试题.assets/image-20220214131327871.png)]
步骤如下所示:
1、实例化A得到A的原始对象
2、将A的原始对象存储到二级缓存(earlySingletonObjects)中
3、需要注入B,B对象在一级缓存中不存在,此时实例化B,得到原始对象B
4、将B的原始对象存储到二级缓存中
5、需要注入A,从二级缓存中获取A的原始对象
6、B对象创建成功
7、将B对象加入到一级缓存中
8、将B注入给A,A创建成功
9、将A对象添加到一级缓存中
三级缓存的作用:
从上面这个分析过程中可以得出,只需要一个缓存就能解决循环依赖了,那么为什么Spring中还需要singletonFactories ?
基于上面的场景想一个问题:如果A的原始对象注入给B的属性之后,A的原始对象进行了AOP产生了一个代理对象,此时就会出现,对于A而言,它的Bean对象其实应该是AOP之后的代理对象,而B的a属性对应的并不是AOP之后的代理对象,这就产生了冲突。 也就是说, 最终单例池中存放的A对象(代理对象)和B依赖的A对象不是同一个。
所以在该场景下, 上述提到的二级缓存就解决不了了。那这个时候Spring就利用了第三级缓存singletonFactories来解决这个问题。
singletonFactories中存的是某个beanName对应的ObjectFactory,在bean的生命周期中,生成完原始对象之后,就会构造一个ObjectFactory存入singletonFactories中,后期其他的Bean可以通过调用该ObjectFactory对象的getObject方法获取对应的Bean。
整体的解决循环依赖问题的思路如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-etI4P5GE-1663995673774)(SSM相关面试题.assets/image-20220214173409241.png)]
步骤如下所示:
1、实例化A,得到原始对象A,并且同时生成一个原始对象A对应的ObjectFactory对象
2、将ObjectFactory对象存储到三级缓存中
3、需要注入B,发现B对象在一级缓存和二级缓存都不存在,并且三级缓存中也不存在B对象所对应的ObjectFactory对象
4、实例化B,得到原始对象B,并且同时生成一个原始对象B对应的ObjectFactory对象,然后将该ObjectFactory对象也存储到三级缓存中
5、需要注入A,发现A对象在一级缓存和二级缓存都不存在,但是三级缓存中存在A对象所对应的ObjectFactory对象
6、通过A对象所对应的ObjectFactory对象创建A对象的代理对象
7、将A对象的代理对象存储到二级缓存中
8、将A对象的代理对象注入给B,B对象执行后面的生命周期阶段,最终B对象创建成功
9、将B对象存储到一级缓存中
10、将B对象注入给A,A对象执行后面的生命周期阶段,最终A对象创建成功,将二级缓存的A的代理对象存储到一级缓存中
注意:
1、后面的生命周期阶段会按照本身的逻辑进行AOP, 在进行AOP之前会判断是否已经进行了AOP,如果已经进行了AOP就不会进行AOP操作了。
2、singletonFactories : 缓存的是一个ObjectFactory,主要用来去生成原始对象进行了AOP之后得到的代理对象,在每个Bean的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本bean,那么这个工厂无用,本bean按照自己的生命周期执行,执行完后直接把本bean放入singletonObjects中即可,如果出现了循环依赖依赖了本bean,则另外那个bean执行ObjectFactory提交得到一个AOP之后的代理对象(如果没有AOP,则直接得到一个原始对象)。
1.13.4 只有一级缓存和三级缓存是否可行?(高频)
不行,每次从三级缓存中拿到ObjectFactory对象,执行getObject()方法又会产生新的代理对象,因为A是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行了objectFactory.getObject()产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍objectFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象。
总结:所以如果没有AOP的话确实可以两级缓存就可以解决循环依赖的问题,如果加上AOP,两级缓存是无法解决的,不可能每次执行objectFactory.getObject()方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象。
1.13.5 构造方法出现了循环依赖怎么解决?(高频)
Spring中大部分的循环依赖已经帮助我们解决掉了,但是有一些循环依赖还需要我们程序员自己进行解决。如下所示:
@Component
public class A {
// B成员变量
private B b;
public A(B b){
System.out.println("A的构造方法执行了...");
this.b = b ;
}
}
@Component
public class B {
// A成员变量
private A a;
public B(A a){
System.out.println("B的构造方法执行了...");
this.a = a ;
}
}
main方法程序:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private A a ;
@Test
public void testTransfer() throws Exception {
System.out.println(a);
}
}
控制台输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Ti60APz-1663995673774)(SSM相关面试题.assets/image-20220214175957232.png)]
解决方案:使用@Lazy注解
@Component
public class A {
// B成员变量
private B b;
public A(@Lazy B b){
System.out.println("A的构造方法执行了...");
this.b = b ;
}
}
在构造参数前面加了@Lazy注解之后, 就不会真正的注入真实对象, 该注入对象会被延迟加载 , 此时注入的是一个代理对象 。
2 SpringMVC
2.1 Spring MVC中的拦截器和Servlet中的filter有什么区别?
难易程度:☆☆☆
出现频率:☆☆☆
过滤器:依赖于servlet容器,在实现上基于函数回调,可以对几乎所有请求进行过滤
拦截器:依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。只能对controller中的handler(方法)请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。
2.2 SpringMVC的执行流程知道嘛
难易程度:☆☆☆☆☆
出现频率:☆☆☆
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-menZyKQX-1663995673774)(SSM相关面试题.assets/image-20220821220623769.png)]
具体流程如下所示:
1、用户发送出请求到前端控制器DispatcherServlet。
2、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。
3、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
4、DispatcherServlet调用HandlerAdapter(处理器适配器)。
5、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。
6、Controller执行完成返回ModelAndView对象。
7、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。
8、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。
9、ViewReslover解析后返回具体View(视图)。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet响应用户。
2.3 Spring MVC常用的注解有哪些?
难易程度:☆☆☆
出现频率:☆☆☆☆
1、@RequestMapping:用于映射请求路径,可以定义在类上和方法上。用于类上,则表示类中的所有的方法都是以该地址作为父路径。
2、@RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
3、@RequestParam:指定请求参数的名称
4、@PathVariable:从请求路径下中获取请求参数(/user/{id}),传递给方法的形式参数
5、@ResponseBody:注解实现将controller方法返回对象转化为json对象响应给客户端。
6、@RequestHeader:获取指定的请求头数据
2.4 Spring MVC怎么处理异常?
难易程度:☆☆☆
出现频率:☆☆☆
可以直接使用Spring MVC中的全局异常处理器对异常进行统一处理,此时Contoller方法只需要编写业务逻辑代码,不用考虑异常处理代码。
开发一个全局异常处理器需要使用到两个注解:@Controlleradvice 、@ ExceptionHandler
如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P5KBVlKW-1663995673775)(SSM相关面试题.assets/image-20220821220742131.png)]
- @ControllerAdvice 标注在一个类上,表示该类是一个全局异常处理的类。
- @ExceptionHandler(Exception.class) 标注在异常处理类中的方法上,表示该方法可以处理的异常类型。
3 Mybatis
3.1 Mybatis #{}和${}的区别
难易程度:☆☆
出现频率:☆☆☆☆☆
1、#{}是预编译处理,${}是字符串替换。
2、Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
3、Mybatis在处理 时,就是把 {}时,就是把 时,就是把{}替换成变量的值。
4、使用#{}可以有效的防止SQL注入,提高系统安全性。
#{}的日志如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DJ3mxbYX-1663995673775)(SSM相关面试题.assets/image-20220821221001480.png)]
${}的日志如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Y66fQiL-1663995673775)(SSM相关面试题.assets/image-20220821221032765.png)]
3.2 MyBatis执行流程
难易程度:☆☆☆☆
出现频率:☆☆
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iqwEHdG2-1663995673776)(SSM相关面试题.assets/image-20220821221243044.png)]
1、读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
2、加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在MyBatis 配置文件 mybatis-config.xml 中加载。
mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
3、构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
4、创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
5、Executor执行器:MyBatis底层定义了一个Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时
负责查询缓存的维护。
6、MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的
SQL 语句的 id、参数等信息。
7、输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对
preparedStatement 对象设置参数的过程。
8、输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解
析过程。
3.3 Mybatis 如何获取生成的主键
难易程度:☆☆☆
出现频率:☆☆
使用insert标签中的useGeneratedKeys和keyProperty 属性。使用方式如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NqqBJl1Z-1663995673776)(SSM相关面试题.assets/image-20220211235736148.png)]
属性说明:
1、useGeneratedKeys:是够获取自动增长的主键值。true表示获取。
2、keyProperty :指定将获取到的主键值封装到哪个属性里
3.4 当实体类中的属性名和表中的字段名不一样 ,怎么办
难易程度:☆☆☆
出现频率:☆☆
第1种: 通过在查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0mjj58fF-1663995673776)(SSM相关面试题.assets/clip_image003.png)]
第2种: 通过来映射字段名和实体类属性名的一一对应的关系。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gRxY8I1S-1663995673777)(SSM相关面试题.assets/clip_image005.png)]
3.5 Mybatis如何实现多表查询
难易程度:☆☆☆☆
出现频率:☆☆
Mybatis是新多表查询的方式也有二种 :
第一种是 : 编写多表关联查询的SQL语句 , 使用ResultMap建立结果集映射 , 在ResultMap中建立多表结果集映射的标签有association
和collection
<resultMap id="Account_User_Map" type="com.heima.entity.Account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<association property="user">
<id property="id" column="uid"></id>
<result property="username" column="username"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
</association>
</resultMap>
<!--public Account findByIdWithUser(Integer id);-->
<select id="findByIdWithUser" resultMap="Account_User_Map">
select a.*, username, birthday, sex, address from account a , user u where a.UID = u.id and a.ID = #{id} ;
</select>
第二种是 : 将多表查询分解为多个单表查询, 使用ResultMap表的子标签association
和collection
标签的select
属性指定另外一条SQL的定义去执行, 然后执行结果会被自动封装
<resultMap id="Account_User_Map" type="com.heima.entity.Account" autoMapping="true">
<id property="id" column="id"></id>
<association property="user" select="com.heima.dao.UserDao.findById" column="uid" fetchType="lazy"> </association>
</resultMap>
<!--public Account findByIdWithUser(Integer id);-->
<select id="findByIdWithUser" resultMap="Account_User_Map">
select * from account where id = #{id}
</select>
3.6 Mybatis都有哪些动态sql?能简述一下动 态sql的执行原理吗?
难易程度:☆☆☆☆
出现频率:☆☆
Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态 拼接sql的功能,Mybatis提供了9种动态sql标签 trim|where|set|foreach|if|choose|when|otherwise|bind。
其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此 来完成动态sql的功能。
3.7 Mybatis是否支持延迟加载?
难易程度:☆☆☆☆
出现频率:☆☆☆
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配
置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zAkYxj6N-1663995673777)(SSM相关面试题.assets/image-20220211192134991.png)]
默认情况下延迟加载是关闭的。
实现原理:
它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用order.getUser().getUserName(),拦截器invoke()方
法发现order.getUser()是null值,那么就会单独发送事先保存好的查询关联User对象的sql,把User查询上来,然后调用order.setUser(user),于是order的对象user属性就有值了,接着完成order.getUser().getUserName()方法的调用。
3.8 如何使用Mybatis实现批量插入 ?
难易程度:☆☆☆
出现频率:☆☆☆
批量插入数据:
1、mybatis的接口方法参数需要定义为集合类型List
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VNeSM2Jq-1663995673777)(SSM相关面试题.assets/image-20220211224420729.png)]
2、在映射文件中通过forEach标签遍历集合,获取每一个元素作为insert语句的参数值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b9o6jYrC-1663995673777)(SSM相关面试题.assets/image-20220211224442128.png)]
3.9 Mybatis的一级、二级缓存 ?
难易程度:☆☆☆
出现频率:☆☆☆☆
1、一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当Session进行flush或close之后,该Session中的所有Cache
就将清空,默认打开一级缓存。如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zxHLKB44-1663995673778)(SSM相关面试题.assets/image-20220211232245131.png)]
使用同一个sqlSession对象获取两次UserMapper对象,进行了两次用户数据的查询。控制台的输出结果如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0MIIu0mj-1663995673778)(SSM相关面试题.assets/image-20220211232346445.png)]
只执行了一次sql语句。说明第二次查询的时候使用的是缓存数据。
2、二级缓存:二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,默认也是采用 PerpetualCache,HashMap 存储。
如下代码:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yun9smEx-1663995673778)(SSM相关面试题.assets/image-20220211233858648.png)]
当执行完sqlSession1.close()方法时一级缓存就一斤被清空掉了。再次获取了一个新的sqlSession对象,那么此时就需要再次查询数据,因此控制台的输
出如下所
示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lzn4IH1s-1663995673780)(SSM相关面试题.assets/image-20220211234036227.png)]
可以看到进行了两次查询。
默认情况下二级缓存并没有开启,要想使用二级缓存,那么就需要开启二级缓存,如下所示:
① 全局配置文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yUNjeLn8-1663995673781)(SSM相关面试题.assets/image-20220211234211651.png)]
② 映射文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uftFtLs4-1663995673782)(SSM相关面试题.assets/image-20220211234241277.png)]
运行程序进行测试,控制台输出结果如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hmb58eBn-1663995673783)(SSM相关面试题.assets/image-20220211234320643.png)]
只进行了一次查询,那么就说明数据已经进入到了二级缓存中。
3、对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 中的缓存将被 clear。
注意事项:
1、二级缓存需要缓存的数据实现Serializable接口
2、只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
…(img-zxHLKB44-1663995673778)]
使用同一个sqlSession对象获取两次UserMapper对象,进行了两次用户数据的查询。控制台的输出结果如下所示:
[外链图片转存中…(img-0MIIu0mj-1663995673778)]
只执行了一次sql语句。说明第二次查询的时候使用的是缓存数据。
2、二级缓存:二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,默认也是采用 PerpetualCache,HashMap 存储。
如下代码:
[外链图片转存中…(img-Yun9smEx-1663995673778)]
当执行完sqlSession1.close()方法时一级缓存就一斤被清空掉了。再次获取了一个新的sqlSession对象,那么此时就需要再次查询数据,因此控制台的输
出如下所
示:
[外链图片转存中…(img-lzn4IH1s-1663995673780)]
可以看到进行了两次查询。
默认情况下二级缓存并没有开启,要想使用二级缓存,那么就需要开启二级缓存,如下所示:
① 全局配置文件
[外链图片转存中…(img-yUNjeLn8-1663995673781)]
② 映射文件
[外链图片转存中…(img-uftFtLs4-1663995673782)]
运行程序进行测试,控制台输出结果如下所示:
[外链图片转存中…(img-hmb58eBn-1663995673783)]
只进行了一次查询,那么就说明数据已经进入到了二级缓存中。
3、对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 中的缓存将被 clear。
注意事项:
1、二级缓存需要缓存的数据实现Serializable接口
2、只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
3、可自定义存储源,如 Ehcache。