2-SSM框架篇
01-什么是Spring IOC 和DI ?
IOC(控制反转):把传统上由程序代码直接操控的对象调用权交给容器,通过容器实现对象组件的装配和管理。“控制反转”的核心是组件对象控制权的转移,从程序代码本身转移到外部容器,不再由开发者手动创建和管理对象,而是由Spring容器统一管控。
DI(依赖注入):是IOC的具体实现方式,在创建对象的过程中,将对象所依赖的属性(比如其他类的实例)自动注入到该类中,无需开发者手动设置依赖,以此实现对象间依赖关系的解耦。
02-有哪些不同类型的依赖注入实现方式?
依赖注入主要有三种实现方式:
- 接口注入:通过特定接口暴露依赖注入的入口,容器借助该接口将依赖注入到目标对象中(实际开发中极少使用);
- 构造器依赖注入:容器通过触发类的构造器完成注入,该类的构造器包含一系列参数,每个参数对应对其他类的依赖,容器会在创建对象时传入这些依赖对象;
- Setter方法注入:容器先通过无参构造器或无参static工厂方法实例化Bean,再调用该Bean的Setter方法,将依赖对象注入,是实际开发中最常用的注入方式。
03- Spring支持的几种bean的作用域 Scope
Spring框架支持五种Bean作用域:
- singleton(单例):每个Spring IOC容器中,该Bean仅存在一个实例,是Spring Bean的默认作用域;
- prototype(原型):一个Bean定义可以创建多个实例,每次从容器中获取该Bean时都会生成新的实例;
- request(请求):每次HTTP请求都会创建一个新的Bean实例,该作用域仅在基于Web的Spring ApplicationContext环境下有效;
- session(会话):在一个HTTP Session生命周期内,一个Bean定义对应一个实例,仅在基于Web的Spring ApplicationContext环境下有效;
- global-session(全局会话):在全局HTTP Session中,一个Bean定义对应一个实例,主要用于Portlet环境,仅在基于Web的Spring ApplicationContext环境下有效。
04- Spring框架中的单例bean是线程安全的吗?
Spring框架中的单例Bean本身不是线程安全的。因为Spring默认的Bean是单例模式,Spring框架并未对单例Bean做多线程相关的封装或同步处理。
但实际开发中,若单例Bean中不包含共享的成员变量(比如全局的状态数据),仅作为无状态的工具类/服务类使用,就不会出现线程安全问题;从这个角度来说,日常使用的单例Bean通常是“线程安全”的。
05- spring 自动装配 bean 有哪些方式?
在Spring框架的XML配置中,主要有3种核心自动装配方式:
- byName(按名称装配):根据Bean的名称进行自动装配,如果一个Bean的属性名与另一个Bean的name属性值相同,Spring容器会自动将后者注入到前者的该属性中;
- byType(按类型装配):根据参数的数据类型进行自动装配,如果容器中存在唯一匹配该属性类型的Bean,就将其注入;若存在多个同类型Bean,会抛出异常;
- constructor(构造器装配):利用构造函数完成装配,构造函数的参数会通过byType方式匹配容器中的Bean,再注入到构造器中创建对象。
06- Spring中的事务是如何实现的
Spring事务底层基于数据库事务和AOP(面向切面编程)机制实现,核心流程如下:
- 对于标注了@Transactional注解的Bean,Spring会为其创建代理对象;
- 调用代理对象的方法时,先判断方法上是否有@Transactional注解;
- 若有注解,通过事务管理器创建数据库连接,并将该连接的autocommit(自动提交)属性设为false(这是Spring事务实现的关键步骤);
- 执行当前方法中的SQL逻辑;
- 方法执行完成后,若无异常则提交事务;若出现异常,且该异常属于需要回滚的类型(默认是RuntimeException及其子类),则回滚事务,否则仍提交事务;
- Spring事务的隔离级别直接对应数据库的隔离级别;事务的传播机制是Spring自身实现的核心特性,基于数据库连接实现:一个数据库连接对应一个事务,若传播机制要求新开事务,本质是新建一个数据库连接并在该连接上执行SQL。
07- Spring中事务失效的场景
Spring事务基于动态代理实现,以下场景会导致@Transactional注解失效:
- 非代理对象调用:加了@Transactional的方法被Bean自身(而非代理对象)调用(比如内部方法调用),注解无法被Spring的AOP拦截,导致失效;
- 方法为private修饰:底层CGLIB动态代理基于父子类实现,子类无法重载父类的private方法,代理无法生效,@Transactional失效;
- 异常被手动捕获:业务代码中捕获了异常且未重新抛出,Spring框架无法感知异常,无法触发事务回滚,导致事务失效;
- 注解标注在非public方法上:Spring AOP仅对public方法的@Transactional做代理处理,非public方法注解无效。
08- 说一下Spring的事务传播行为
Spring事务传播行为定义了多个带有事务的方法相互调用时,事务的创建、加入或挂起规则,核心传播行为如下:
- PROPAGATION_REQUIRED(默认):如果当前无事务,创建新事务;如果当前已有事务,加入该事务,是最常用的设置;
- PROPAGATION_SUPPORTS:支持当前事务,若当前有事务则加入,若无则以非事务方式执行;
- PROPAGATION_MANDATORY:强制要求当前存在事务,若有则加入,若无则抛出异常;
- PROPAGATION_REQUIRES_NEW:无论当前是否有事务,都创建新事务(原有事务会被挂起,新事务执行完后恢复原有事务);
- PROPAGATION_NOT_SUPPORTED:以非事务方式执行,若当前有事务则将其挂起;
- PROPAGATION_NEVER:以非事务方式执行,若当前存在事务则抛出异常;
- PROPAGATION_NESTED:若当前有事务,在嵌套事务内执行(嵌套事务依赖外层事务,外层回滚则嵌套也回滚,嵌套回滚不影响外层);若当前无事务,按PROPAGATION_REQUIRED规则执行。
09- JDK动态代理和CGLIB动态代理的区别
Spring AOP的动态代理主要有JDK动态代理和CGLIB动态代理两种方式,核心区别如下:
- 实现原理:JDK动态代理基于接口实现,通过Proxy.newProxyInstance(类加载器, 代理对象实现的所有接口, 代理执行器)创建代理对象,仅支持对接口的代理;CGLIB动态代理基于继承实现,通过Enhancer.create(父类的字节码对象, 代理执行器)创建代理对象,代理类是目标类的子类;
- 适用范围:JDK动态代理要求目标类必须实现接口,否则无法代理;CGLIB无需目标类实现接口,但目标类若被标记为final(无法被继承),则无法使用CGLIB代理;
- 性能:JDK1.8后JDK动态代理性能优于CGLIB,低版本JDK中CGLIB性能更优。
10- 什么是AOP , 你们项目中有没有使用到AOP
AOP(面向切面编程):作为面向对象编程(OOP)的补充,将与业务无关但对多个对象产生影响的公共行为和逻辑(比如日志、权限、事务)抽取封装为可重用的“切面(Aspect)”,减少系统重复代码,降低模块耦合度,提升可维护性。
项目中直接手写AOP的场景较少,但大量框架功能底层基于AOP实现:比如Spring的事务管理(@Transactional)、权限认证(比如拦截请求校验权限)、系统日志记录(统一记录接口入参出参)、异常统一处理等,都是AOP的典型应用。
11- SpringMVC的执行流程知道嘛
SpringMVC的核心执行流程如下:
- 用户发送HTTP请求至前端控制器DispatcherServlet;
- DispatcherServlet调用HandlerMapping(处理器映射器),根据请求URL查找对应的Handler(处理器,即Controller中的方法);
- HandlerMapping返回处理器对象及对应的处理器拦截器(若有)给DispatcherServlet;
- DispatcherServlet调用HandlerAdapter(处理器适配器),适配并执行对应的Handler;
- HandlerAdapter适配后调用具体的Handler(后端控制器),Handler执行完成后返回ModelAndView(模型数据+视图名称);
- HandlerAdapter将ModelAndView返回给DispatcherServlet;
- DispatcherServlet将ModelAndView传给ViewResolver(视图解析器),视图解析器解析出具体的View(视图);
- DispatcherServlet对View进行渲染(将Model中的数据填充到视图中);
- DispatcherServlet将渲染后的视图响应给用户。
12- Spring MVC常用的注解有哪些?
Spring MVC常用注解及作用:
- @RequestMapping:处理请求URL映射,可标注在类或方法上;标注在类上时,类中所有响应请求的方法都以该地址为父路径;
- @RequestBody:接收HTTP请求中的JSON数据,自动将JSON转换为Java对象;
- @ResponseBody:将Controller方法的返回对象转换为JSON对象,响应给客户端;
- @Controller:标注类为Spring MVC的控制器(表现层),是核心注解,无法被其他注解替代;
- @RestController:组合注解,等价于@Controller + @ResponseBody,标注后方法返回值直接以JSON响应,无需额外加@ResponseBody;
- @GetMapping/@PostMapping/@PutMapping/@DeleteMapping:分别对应GET/POST/PUT/DELETE请求的映射注解,是@RequestMapping的简化版;
- @PathVariable:接收请求路径中的路径变量(比如/{id}中的id);
- @RequestParam:接收请求参数(比如URL中的?id=123或表单参数),可指定参数名、是否必传、默认值等。
13- Mybatis #{}和${}的区别
Mybatis中#{}和${}是两种参数绑定方式,核心区别如下:
- 处理方式:#{}是占位符,做预编译处理;Mybatis会将SQL中的#{}替换为?,调用PreparedStatement的set方法赋值,参数以字符串形式传入;${}是字符串拼接符,做字符串替换,无预编译处理,直接将参数拼接到SQL中;
- 安全性:#{}能有效防止SQL注入(因为预编译处理,参数被当作值而非SQL语句),提升系统安全性;${}无法防止SQL注入,参数直接拼接可能被恶意注入;
- 替换位置:#{}的变量替换在数据库系统内部完成;${}的变量替换在数据库系统外(Mybatis框架层面)完成。
14- Mybatis 如何获取生成的主键
Mybatis获取插入操作生成的主键主要有两种方式:
- 基于insert标签属性:在insert标签上添加useGeneratedKeys="true"(开启获取自增主键),同时设置keyProperty="主键字段名"(比如userId),插入后主键值会自动填充到实体类的对应属性中;
- 基于selectKey标签:在insert标签内部添加selectKey标签,通过执行SQL(比如MySQL的select last_insert_id())查询生成的主键,并将结果赋值给实体类的主键属性。
15- 当实体类中的属性名和表中的字段名不一样 ,怎么办
解决实体类属性名与数据库表字段名不一致的问题,主要有两种方式:
- SQL别名方式:在查询SQL中为字段名定义别名,使别名与实体类的属性名一致(比如SELECT user_name AS userName FROM user);
- ResultMap映射方式:在Mybatis的映射文件中定义ResultMap,明确指定数据库字段名与实体类属性名的映射关系,是更通用、可复用的方式。
16- Mybatis如何实现多表查询
Mybatis实现多表查询主要有两种方式:
- 关联查询方式:编写多表关联的SQL语句(比如JOIN),通过ResultMap建立结果集映射,使用association标签(一对一关联)、collection标签(一对多关联)映射关联对象/集合;
- 分步查询方式:将多表查询拆分为多个单表查询,在ResultMap的association/collection标签中通过select属性指定另一个SQL的ID,Mybatis会自动执行该SQL并将结果封装到关联对象/集合中。
17-Mybatis都有哪些动态sql?能简述一下动态sql的执行原理吗?
Mybatis动态SQL允许在XML映射文件中通过标签编写动态逻辑,完成SQL的条件拼接,核心动态SQL标签包括:trim、where、set、foreach、if、choose、when、otherwise、bind共9种。
动态SQL的执行原理:Mybatis通过OGNL(对象图导航语言)从SQL参数对象中计算表达式的值,根据表达式的结果动态拼接SQL语句,最终生成可执行的SQL并执行,以此实现SQL的动态调整。
18- Mybatis是否支持延迟加载?
Mybatis支持延迟加载(懒加载),但仅支持association(一对一关联)和collection(一对多关联)对象的延迟加载:
- 延迟加载的核心逻辑:查询主表数据时,不立即查询关联表数据,仅在首次访问关联对象/集合时,才执行关联查询;
- 开启方式:在Mybatis的核心配置文件中设置lazyLoadingEnabled=true(默认false),即可启用全局延迟加载;也可在ResultMap的association/collection标签中单独配置延迟加载规则。
19- 如何使用Mybatis实现批量插入 ?
Mybatis通过foreach标签实现批量插入,foreach标签可迭代集合并拼接SQL,核心步骤如下:
- 在insert标签中使用foreach标签遍历需要插入的集合;
- foreach标签的核心属性:
- collection:指定要遍历的集合参数名(比如list、array);
- item:迭代集合时,每个元素的别名(自定义变量名);
- index:迭代过程中当前元素的索引(不常用);
- open:指定拼接SQL的起始字符(比如"(");
- separator:指定迭代元素之间的分隔符(比如",");
- close:指定拼接SQL的结束字符(比如")");
- 拼接INSERT INTO ... VALUES (...)的批量插入SQL,完成批量插入。
20- Mybatis 批量插入是否能够返回主键
Mybatis批量插入可以返回生成的主键,主键值会自动封装到传入的集合中每个对象的主键属性里:例如批量插入List,插入后每个User对象的id(主键)属性会被赋值为数据库生成的自增主键。
21- Mybatis的一级、二级缓存 ?
Mybatis的缓存分为一级缓存和二级缓存,核心内容如下:
- 一级缓存:基于SqlSession级别的缓存,默认开启。同一个SqlSession中,多次执行相同的SQL查询,会将结果缓存到一级缓存中,后续查询直接从缓存获取,无需访问数据库;关闭SqlSession后,一级缓存失效;
- 二级缓存:基于SqlSessionFactory的NameSpace(命名空间)级别缓存,默认未开启,需手动配置:
- 第一步:在Mybatis核心配置文件中开启全局二级缓存:;
- 第二步:在Mapper映射文件中配置cache标签:(eviction为缓存淘汰策略,flushInterval为刷新间隔,size为缓存最大条目数);
二级缓存跨SqlSession生效,同一个NameSpace下的多个SqlSession可共享缓存,提升查询效率。
总结
- SSM核心:Spring的IOC/DI实现对象解耦,事务基于AOP+数据库连接实现,需注意代理、异常捕获等导致的事务失效场景;
- SpringMVC核心是DispatcherServlet为中心的请求分发流程,常用注解简化请求映射和参数/响应处理;
- Mybatis核心是参数绑定(#{}防注入)、动态SQL、多表查询(association/collection)、缓存(一级默认开启,二级手动配置),批量操作依赖foreach标签实现。