《Java 后端面试经》专栏文章索引:
《Java 后端面试经》Java 基础篇
《Java 后端面试经》Java EE 篇
《Java 后端面试经》数据库篇
《Java 后端面试经》多线程与并发编程篇
《Java 后端面试经》JVM 篇
《Java 后端面试经》操作系统篇
《Java 后端面试经》Linux 篇
《Java 后端面试经》设计模式篇
《Java 后端面试经》计算机网络篇
《Java 后端面试经》微服务篇
《Java 后端面试经》 Java EE 篇
- 🚀通识基础
- 🚀Spring
-
- 🚁Spring 框架了解吗?说说它的优缺点?
- 🚁Spring 中有两个重要特性是什么?
- 🚁Spring 如何解决循环依赖的?
- 🚁描述一下 Spring 容器中 Bean 的生命周期
- 🚁Spring 装配 bean 的方式有哪几种?
- 🚁解释下 Spring 支持的六种 bean 的作用域
- 🚁Spring 框架中的单例 Bean 是线程安全的么
- 🚁Spring 里用到了哪些设计模式?
- 🚁通过注解和 XML 进行装配各有什么优缺点?
- 🚁Spring 中有哪些常见的注解?
- 🚁@Autowired 和 @Resource 注解有什么区别?
- 🚁Spring 事务的实现方式原理以及隔离级别
- 🚁Spring 事务传播机制
- 🚁Spring 事务什么时候会失效
- 🚁BeanFactory 和 ApplicationContext 有什么区别
- 🚁什么是 bean 的自动装配,有哪些方式?
- 🚁bean 是不是线程安全的?
- 🚁Spring 单例,为什么 Controller、Service 和 Dao 却能保证线程安全?
- 🚁自动装配的优缺点是什么呢?
- 🚁Spring、Spring MVC 和 Spring Boot 有什么区别?
- 🚁SpringMVC 执行流程
- 🚁Spring MVC 的主要组件?
- 🚁谈谈 SpringMVC 拦截器
- 🚀SpringBoot
- 🚀Mybatis
🚀通识基础
🚁Servlet 的生命周期
🚁什么是嵌入式服务器?为什么要使用嵌入式服务器?
SpringBoot 已经内置了 tomcat.jar,运行 main 方法时会去启动 tomcat,并利用 tomcat 的 spi 机制加载 SpringMVC.
节省了下载安装 tomcat,应用也不需要再打 war 包,然后放到 webapp 目录下再运行。只需要一个安装了 Java 的虚拟机,就可以直接在上面部署应用程序了。
🚀Spring
🚁Spring 框架了解吗?说说它的优缺点?
Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。
一般来说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container 中的 Core 组件是 Spring 所有组件的核心,Beans 组件和 Context 组件是实现 IOC 和 DI 的基础,AOP 组件用来实现面向切面编程。
Spring 官网列出的 Spring 的 6 个特征:
- 核心容器 :依赖注入(DI),AOP,事件(events),资源,i18n,验证,数据绑定,类型转换,SpEL.
- 测试 :模拟对象,TestContext 框架,Spring MVC 测试,WebTestClient.
- 数据访问 :事务,DAO支持,JDBC,ORM,编组 XML.
- Web 支持 : Spring MVC 和 Spring WebFlux Web 框架。
- 集成 :远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
- 语言 :Kotlin,Groovy,动态语言。
Spring 框架的优点:
- 轻量级:Spring 框架是轻量级的,最基础的版本大约只有 2MB.
- 控制反转(IOC):通过控制反转技术,实现了解耦合。对象给出它们的依赖,而不是创建或查找依赖的对象,是一种面向对象编程的设计思想。
- 面向切面(AOP):Spring 支持面向切面的编程,并将应用程序业务逻辑与系统服务分离。
- MVC 框架:Spring 的 Web 框架是一个设计良好的 Web MVC 框架,它为 web 框架提供了一个很棒的替代方案。
- 容器:Spring 包含并管理对象的生命周期和配置。
- 事务管理:Spring 提供了一个一致性的事务管理接口,可以收缩到本地事务,也可以扩展到全局事务(JTA)。
- 异常处理:Spring 提供了方便的 API 来将具体技术的异常(由 JDBC、Hibernate 或 JDO 抛出)转换为一致的 unchecked 异常。
Spring 框架的缺点:
使用了大量的反射机制,反射机制非常占用内存。
(一)重量级框架
我们看到 Spring 架构图时会发现 Spring 里面包含有很多其他组件,比如数据访问、MVC、事务管理、面向切点、WebSocket 功能等,因此这么复杂的组件集中到一起就会提高初学者的学习成本。还有一方面随着你的服务越多,那么 Spring 的启动就会变得越慢。
(二)集成复杂
比如我们想要使用 MyBatis 或者 MongoDB的时候,我们要做很多工作不管使用配置方式也好还是使用注解方式。
(三)配置复杂
在使用 Spring 的时候,我们更多可能是选择 XML 进行配置,但目前这种配置方式已不在流行。
(四)构建和部署复杂
启动 Spring 的 IOC 容器,是完全要依赖于第三方的 web 服务器。自身不能启动的。
🚁Spring 中有两个重要特性是什么?
Spring 中有两个非常重要的特性 IOC 和 AOP,其中 AOP 是对 IOC 功能的拓展:
- IOC(Inversion Of Control):控制反转是面向对象编程的一种设计思想,帮助我们维护对象与对象之间的依赖关系,降低对象之间的耦合度。如果不使用这种设计思想,我们手动维护对象之间的依赖关系,容易造成对象之间耦合度过高的问题。IOC 作为一种抽象的设计思想,它的落地实现是 DI(Dependency Injection)。实现 DI 的关键是 IOC 容器,它的本质是一个工厂。
- AOP(Aspect Oriented Programming):AOP 是对 OOP 的一种补充,可以在 OOP 的基础上进一步提高编程效率。它可以统一解决一批组件的共性需求(如权限检查、记录日志、事务管理等)。在 AOP 思想下,我们可以将解决共性需求的代码独立出来,然后通过配置的方式,声明这些代码在什么地方、什么时候调用。当满足调用条件时,AOP 会将该业务代码织入到我们指定的位置,从而统一解决了问题,又不需要修改这一批组件的代码。
🪂追问1:IOC 的好处有哪些?
- IOC 可以最小化应用程序代码量。
- 它使测试应用程序变得容易,因为单元测试中不需要单例或 JNDI 查找机制。
- 以最小的代价和最少的干扰来促进松耦合。
- IOC 容器支持快速实例化和懒加载。
🪂追问2:依赖注入有哪些方式
- 基于构造器的注入
- 基于 setter 方法的注入
🪂追问3:AOP 是怎么实现的?
AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 CGLib 生成一个被代理对象的子类来作为代理。
🪂追问4:JDK 代理和 CGLib 代理的区别?
- CGLib 所创建的动态代理对象在实际运行时候的性能要比 JDK 动态代理高(1.6 和 1.7 的时候,CGLib 更快;JDK 1.8 及以后,JDK 动态代理更快)。
- CGLib 在创建对象的时候所花费的时间却比 JDK 动态代理多。
- 单例的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用 CGLib 动态代理,反之,对于多例的对象因为需要频繁的创建代理对象,则 JDK 动态代理更合适。
- JDK 生成的代理类类型是 Proxy (因为继承的是 Proxy),CGLib 生成的代理类类型 Enhancer 类型。
- JDK 动态代理是面向接口的,CGLib 动态代理是通过字节码底层继承代理类来实现(如果被代理类被 final 关键字所修饰,那么会失败)。
- 如果要被代理的对象是个实现类,那么 Spring 会使用 JDK 动态代理来完成操作(Spring 默认采用 JDK 动态代理实现机制)。如果要被代理的对象不是实现类,那么 Spring 会强制使用 CGLib 来实现动态代理。
🚁Spring 如何解决循环依赖的?
Spring 对循环依赖的处理有三种情况:
- 构造器的循环依赖:这种依赖 Spring 是处理不了的,直接抛出
BeanCurrentlylnCreationException
异常。 - 单例模式下的 setter 循环依赖:通过“三级缓存”处理循环依赖。
- 其他模式的循环依赖:无法处理。
Spring 单例对象的初始化大略分为三步:
- createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
- populateBean:填充属性,这一步主要是对 bean 的依赖属性进行填充
- initializeBean:调用 spring.xml 中的 init 方法
循环依赖主要发生在第一步、第二步,也就是构造器循环依赖和 setter 循环依赖。Spring 为了解决单例的循环依赖问题,使用了三级缓存(三个 Map):
- 一级缓存为单例池 singletonObjects,完成初始化的单例对象的 Cache
/** Cache of singleton objects: bean name –> bean instance */ //一级缓存,单例池
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
- 二级缓存为提前曝光对象 earlySingletonObjects,完成实例化但是尚未初始化的,提前曝光的单例对象的 Cache
/** Cache of early singleton objects: bean name –> bean instance */ //二级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap(16);
- 三级缓存为提前曝光对象工厂 singletonFactories,进入实例化阶段的单例对象工厂的 Cache
/** Cache of singleton factories: bean name –> ObjectFactory */ //三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
我们在创建 bean 的时候,会首先从 Cache 中获取这个 bean,这个缓存就是 sigletonObjects.
主要的调用方法是:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
// isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
synchronized(this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
//allowEarlyReference 是否允许从singletonFactories中通过getObject拿到对象
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//从singletonFactories中移除,并放入earlySingletonObjects中
//从三级缓存移动到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
从上面三级缓存的分析,我们可以知道,Spring 解决循环依赖的诀窍就在于 singletonFactories这个三级 Cache, 这个 Cache 的类型是 ObjectFactory,定义如下:
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
这个接口在 AbstractBeanFactory 里实现,并在核心方法 doCreateBean() 引用下面的方法:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized(this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
这段代码发生在 createBeanInstance 之后,populateBean 之前,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,此时将这个对象提前曝光出来,让大家使用。
假设出现这么一种循环依赖的情况: “A的某个 field 或者 setter 依赖了 B 的实例对象,同时 B 的某个 field 或者 setter 依赖了 A 的实例对象”,解决循环依赖的执行过程为:
- A 首先完成了初始化的第一步,并且将自己提前曝光到 singletonFactories 中,此时进行初始化的第二步,发现自己依赖对象 B,此时就尝试获取 B,但是发现 B 还没有被创建,所以走create B 的流程。
- B 在初始化第一步的时候发现自己依赖了对象 A,于是尝试获取 A,尝试获取一级缓存singletonObjects (肯定没有,因为 A 还没初始化完全),尝试获取二级缓存 earlySingletonObjects(也没有),尝试获取三级缓存 singletonFactories.
- 由于 A 通过 ObjectFactory 将自己提前曝光了,所以 B 能够通过 ObjectFactory.getObject 拿到 A 对象(虽然A还没有初始化完全,但是总比没有好呀),B 拿到 A 对象后顺利完成了初始化阶段 1、2、3,完全初始化之后将自己放入到一级缓存 singletonObjects 中。
- 然后返回 A 中,A 此时能拿到 B 的对象顺利完成自己的初始化阶段 2、3,最终 A 也完成了初始化,进入到一级缓存 singletonObjects 中,而且更加幸运的是,由于 B 拿到了 A 的对象引用,所以 B 现在拿到了 A 对象完成了初始化。
🚁描述一下 Spring 容器中 Bean 的生命周期
Springc Bean 生命周期简单概括为 4 个阶段:
- 实例化 bean,申请内存空间。
- 属性注入:如果 Bean 实现了 BeanNameAware 接口,重写了 setBeanName(String s) 方法,将在此时调用该方法,能获取 bean 容器中的 name.
- 初始化
- 如果容器中有对象实现了 BeanPostProcessor 接口,将在初始化前后执行重写的对应方法,即 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法。
- 如果实现了 InitializingBean 接口,将在初始化方法执行后执行该接口重写的 afterPropertiesSet 方法。
- 如果 Bean 实现了 BeanFactoryAware 接口,将在此时执行重写的 setBeanFactory 方法。
- 销毁
- 容器关闭后,如果 Bean 实现了 DisposableBean 接口,将在销毁时执行 destory() 方法,完成销毁。
- 或者 destroy-method 属性指定了 Bean 的销毁方法,,则 Spring 将调用该方法对 Bean 进行销毁。
🚁Spring 装配 bean 的方式有哪几种?
- 自动化装配 bean.
- 在 Java 中进行显式配置装配 bean.
- 通过 xml 进行显式配置装配 bean.
🚁解释下 Spring 支持的六种 bean 的作用域
- singleton:在每一个 Spring IOC 容器中,一个 bean 定义只有一个对象实例(默认为singleton)。
- prototype:允许 bean 的定义可以被实例化任意次(每次调用都创建一个实例)。
- request:在一个 http request 中,每个 bean 定义对应一个实例。每个 http 请求都有自己的 bean 实例,该实例是在单个 bean 定义后才创建的。该作用域仅在基于 web 的 Spring ApplicationContext(例如SpringMVC 中才有效)
- session:在一个 http session中,每个 bean 定义对应一个实例。该作用域仅在基于 web 的 Spring ApplicationContext(例如SpringMVC 中才有效)
- application:在一个 ServletContext 中,每个 bean 对应一个实例。该作用域仅在基于 web 的 Spring ApplicationContext(例如SpringMVC 中才有效)
- websocket:在一个 Websocket 中,每个 bean 对应一个实例。该作用域仅在基于 web 的 Spring ApplicationContext(例如SpringMVC 中才有效)
🚁Spring 框架中的单例 Bean 是线程安全的么
不是线程安全的。Spring 容器本身并没有提供 Bean 的线程安全策略。如果单例的 Bean 是一个无状态的 Bean,即线程中的操作不会对 Bean 的成员执行查询以外的操作,那么这个单例的 Bean 是线程安全的。例如,Controller、Service、DAO 这样的组件,通常都是单例且线程安全的。如果单例的Bean 是一个有状态的 Bean,则可以采用 ThreadLocal 对状态数据做线程隔离,来保证线程安全。
🚁Spring 里用到了哪些设计模式?
- 单例模式:Spring 中的所有 Bean 默认情况下都是单例的。
- 工厂模式:Spring 使用工厂模式通过 BeanFactory 和 ApplicationContext 来创建 Bean 对象。
- 代理模式:最常见的 AOP 的实现方式就是通过代理来实现,Spring 主要是使用 JDK 动态代理和 CGLIB 代理。
- 模板方法模式:Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
- 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式:定义对象间一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被自动更新,如 Spring 中 Listener 的实现 ApplicationListener.
- 适配器模式 : Spring AOP 的增强或通知(Advice) 使用到了适配器模式,Spring MVC 中也用到了适配器模式适配 Controller.
🚁通过注解和 XML 进行装配各有什么优缺点?
注解方式优点:
- 注解的解析可以不依赖于第三方库,可以之间使用 Java 自带的反射。
- 注解和代码在一起的,之间在类上,降低了维护两个地方的成本。
- 注解如果有问题,在编译期间,就可以验证正确性,如果出错更容易找。
注解方式缺点:
- 如果对 Annotation 进行修改,需要重新编译整个工程。
- 业务类之间的关系不如 XML 配置那样一目了然。
- 程序中过多的 Annotation,对于代码的简洁度有一定影响。
- 注解功能没有xml配置齐全。
XML 配置文件方式优点:
- XML 是集中式的元数据,不需要和代码绑定的
- 对象之间的关系一目了然。
- 使用 XML 配置可以让软件更具有扩展性。
- 基于 XML 配置的时候,只需要修改 XML 即可,不需要对现有的程序进行修改。
- 容易与其他系统进行数据交互。数据共享方便。
XML 配置文件方式缺点:
- 应用程序中如果使用了 XML 配置,需要解析 XML 的工具或者是是第三方类库的支持。
- XML 配置文件过多,会导致维护变得困难。
- 在程序编译期间无法对其配置项的正确性进行验证,只能在运行期发现。
- 开发的时候,既要维护代码又要维护配置文件,使得开发的效率降低。
🚁Spring 中有哪些常见的注解?
- @Component:用于指示类是组件。这些类用于自动注入,并在使用基于注解的配置时配置为 bean.
- @Controller:是一种特定类型的组件,用于 MVC 应用程序,主要与 @RequestMapping 注解一起使用。
- @Repository:用于表示组件用作存储库和存储/检索/搜索数据的操作。我们可以将此注解应用于 DAO 实现类。
- @Service:用于指示类是服务层。
- @Required:此注解简单地说明作用的 bean 属性必须在配置时通过 bean 定义中的显式属性值或通过自动注入填充。如果作用的 bean 属性未填充,容器将抛出BeanInitializationException。
- @ResponseBody:用于将对象作为 response,通常用于将 XML 或 JSON 数据作为response 发送。
- @PathVariable:用于将动态值从 URI 映射到处理方法参数。
- @Autowired:对自动注入的位置和方式提供了更细粒度的控制。它可以用于在 setter 方法上自动注入 bean. 就像 @Required 注解一样,修饰 setter 方法、构造器、属性或者具有任意名称和/或多个参数的 PN 方法。
- @Qualifier:当有多个相同类型的 bean 并且只需要将一个 bean 自动注入时,@Qualifier 注解与 @Autowired 注释一起使用,通过指定将连接哪个 bean 来消除歧义。
- @Scope:配置 Spring bean 的作用域。
- @Configuration:表示 Spring IOC 容器可以将该类用作 bean 定义的源。
- @ComponentScan:应用此注解时,将扫描包下的所有可用类。
- @Bean:对于基于 java 的配置,用 @Bean 注解修饰的方法将返回一个在 Spring 应用程序上下文中注册为 Bean 的对象。
- 用于配置切面和通知、@Aspect、@Before、@After、@Around、@Pointcut 等的 Aspect 注解。
🚁@Autowired 和 @Resource 注解有什么区别?
- @Autowired 是 Spring 提供的注解,@Resource 是 JDK 提供的注解。
- @Autowired 是只能按类型注入,@Resource 默认按名称注入,也支持按类型注入。
- @Autowired 是按照 Bean 的类型进行匹配的,如果这个属性的类型具有多个 Bean,可以通过 @Qualifier 指定 Bean 的名称,以此来消除歧义。
- @Resource有两个中重要的属性:name 和 type。name 属性指定 byName,如果没有指定name 属性,当注解标注在字段上,即默认取字段的名称作为 bean 名称寻找依赖对象,当注解标注在属性的 setter 方法上,即默认取属性名作为 bean 名称寻找依赖对象。需要注意的是,@Resource 如果没有指定 name 属性,并且按照默认的名称仍然找不到依赖对象时, @Resource 注解会回退到按类型装配。但一旦指定了 name 属性,就只能按名称装配了。
🚁Spring 事务的实现方式原理以及隔离级别
Spring 支持两种类型的事务管理:
- 声明式事务,在配置文件中配置(推荐使用)。
- 编程式事务,在代码中硬编码(不推荐使用)。但是,在有些场景下,我们需要获取事务的状态,是执行成功了还是失败回滚了,那么使用声明式事务就不够用了,需要编程式事务。
声明式事务又分为两种:
- 基于 XML 的声明式事务。
- 基于注解的声明式事务。
🪂追问:Spring 事务中的隔离级别有哪几种?
首先,事务这个概念是数据库层面的,Spring 只是基于数据库中的事务进行了扩展,以及提供了一些能让程序员更加方便操作事务的方式。
比如我们可以通过在某个方法上增加 @Transactional 注解,就可以开启事务,这个方法中所有的 sql 都会在一个事务中执行,统一成功或失败。在一个方法上加了 @Transactional 注解后,Spring 会基于这个类生成一个代理对象,会将这个代理对象作为 bean,当在使用这个代理对象的方法时,如果这个方法上存在 @Transactional 注解,那么代理逻辑会把事务的自动提交设置为 false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚。
当然,针对哪些异常回滚事务是可以配置的,可以利用 @Transactional 注解中的 rollbackFor 属性进行配置,默认情况下会对 RuntimeException 和 Error进行回滚。
Spring 事务隔离级别就是数据库的隔离级别,外加一个默认级别:
- read uncommitted(未提交读):一个事务可以读取到另一个事务未提交的事务记录。
- read committed(已提交读):一个事务只能读取到已经提交的记录,不能读取到未提交的记录。
- repeatable read(可重复读):一个事务可以多次从数据库读取某条记录,而且多次读取的那条记录都是一致的、相同的。这个隔离级别可以避免脏读和不可重复读的问题
- serializable(可串行化):事务执行时,会在所有级别上加锁,比如 read 和 write 时都会加锁,仿佛事务是以串行的方式进行的,而不是一起发生的。这会防止脏读、不可重复读和幻读的出现,但是,会带来性能的下降。
🚁Spring 事务传播机制
多个事务方法相互调用时,事务如何在这些方法间传播。
方法 A 是一个事务的方法,方法 A 执行过程中调用了方法 B,那么方法 B 有无事务以及方法 B 对事务的要求不同都会对方法 A 的事务具体执行造成影响,同时方法 A 的事务对方法 B 的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。
REQUIRED(Spring 默认的事务传播类型):如果当前没有事务,则自己创建一个事务,如果当前存在事务,则加入这个事务。
传播类型 | 如果当前无事务 | 如果当前有事务 |
---|---|---|
MANDATORY | 抛异常 | 使用当前事务 |
NEVER | 不创建新的事务,在无事务的状态下执行方法 | 抛异常 |
NOT_SUPPORTED | 不创建新的事务,在无事务的状态下执行方法 | 暂停当前事务,在无事务的状态下执行方法 |
SUPPORTS | 不创建新的事务,在无事务的状态下执行方法 | 使用当前事务 |
REQUIRED(默认) | 创建新的事务 | 使用当前事务 |
REQUIRES_NEW | 创建新的事务 | 暂停当前事务,创建新的独立事务 |
NESTED | 创建新的事务 | 创建新的内嵌事务 |
NESTED:如果当前事务存在,则在嵌套事务中执行,否则 REQUIRED 的操作一样(开启一个事务)。和 REQUIRES_NEW 的区别:
-
REQUIRES_NEW 是新建一个事务并且新开启这个事务与原有事务无关,而 NESTED 则是当前存在事务时(我们把当前事务称之为父事务) 会开启一个嵌套事务(称之为一个子事务)。在 NESTED 情况下父事务回滚时,子事务也会回滚,而在 REQUIRED_NEW 情况下,原有事务回滚,不会影响新开启的事务。
-
和 REQUIRED 的区别:
REQUIRED 情况下,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于共用一个事务,所以无论调用方法是否 catch 其异常,事务都会回滚,而在 NESTED 情况下,被调用方发生异常时,调用方可以 catch 其异常,这样只有子事务回滚,父事务不受影响。
🚁Spring 事务什么时候会失效
Spring 事务的原理是 AOP,进行了切面增强,那么失效的根本原因是这个 AOP 不起作用了!常见情况有如下几种:
- 发生自调用,类里面使用
this
调用本类的方法(this 通常省略),必须被其他类通过接口调用方法才会被代理
解决方法很简单:让那么 this 变成 UserService 的代理类即可! - 方法的访问修饰符不是
public
@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。
- 数据库不支持事务
- 异常被捕获并没有抛出,事务不会回滚
- Spring 默认只有在抛出的异常是 RuntimeException 时,事务才会回滚,对于 CheckedException 不能捕获,因此设置
rollbackFor
属性值为Exception.class
Spring 事务异常回滚
以下几个案例捕获异常时回滚的情况:
1、案例一:不回滚
try {
xxxDao.save(xxxObject);
int i = 3/0;
} catch(Exception e) {
e.printstack();
}
2、案例二:回滚
try {
xxxDao.save(xxxObject);
int i = 3/0;
} catch(Exception e) {
e.printstack();
//显式抛出异常
throw new RunTimeException();
}
3、案例三:回滚
try {
xxxDao.save(xxxObject);
int i = 3/0;
} catch(Exception e) {
e.printstack();
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
4、案例四:回滚
try {
xxxDao.save(xxxObject);
int i = 3/0;
}
Spring AOP 异常捕获规则:被拦截的方法需显式抛出异常,并不能经任何处理,这样 AOP 代理才能捕获到方法的异常,才能进行回滚,默认情况下 AOP 只捕获 Runtimeexception 的异常。
- 案例一中捕获了异常,但是没有显式抛出异常,并且也没有对事务做显式的提交,相当于将异常吃掉
- 案例二中显式抛出了异常,可以回滚
- 案例三中没有显式抛出异常,但是手动地回滚了事务
- 案例四中没有捕获异常,使得异常能够被 AOP 捕获,会进行回滚
🚁BeanFactory 和 ApplicationContext 有什么区别
ApplicationContext 是 BeanFactory 的子接口,ApplicationContext 提供了更完整的功能:
- ApplicationContext 接口继承了名为 MessageSource 的接口,因此提供国际化的功能。
- 方便访问底层资源。一个 ApplicationContext 是一个 ResourceLoader,可以用来加载 Resource 对象,一个 Resource 对象可以以透明的方式从几乎任何位置获取底层资源,包括类路径、文件系统位置以及用一个标准的 URL 可以描述的任何地方。
- 提供在监听器中注册 bean 的事件,使用 @EventListener 注解在托管 bean 的任何方法上注册事件监听器。
- 应用程序启动追踪。ApplicationContext 管理 Spring 应用的生命周期并且围绕组件提供丰富的编程模型。
- 载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的 web 层。
区别:
- BeanFactory 采用的是延迟加载形式来注入 Bean 的,即只有在使用某个 Bean 时(调用 getBean),才对该 Bean 进行加载实例化。这样,我们就不能发现一些存在的 Spring 的配置问题。如果 Bean 的某一个属性没有注入,BeanFactory 加载后,直至第一次使用调用 getBean 方法才会抛出异常。
- ApplicationContext 是在容器启动时,一次性创建所有的 Bean. 这样,在容器启动时,我们就可以发现 Spring 中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext 启动后预载入所有的单实例 Bean,通过预载入单实例 Bean,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
- 相对于基本的 BeanFactory,ApplicationContext 唯一的不足是占用的内存空间,当应用程序配置 Bean 较多时,程序启动较慢。
- BeanFactory 通常以编程的方式被创建,ApplicationContext 还能以声明的方式创建,如使用 ContextLoader.
- BeanFactory 和 ApplicationContext 都支持 BeanPostProcessor、BeanFactoryPostProcessor 的使用,但两者之间的区别是:BeanFactory 需要手动注册,而 ApplicationContext 则是自动注册。
🚁什么是 bean 的自动装配,有哪些方式?
对象无需自己查找或创建与其关联的其他对象,由 Spring 容器负责把需要相互协作的对象引用赋予各个对象,使用 autowire 来配置自动装配模式。
当使用基于 xml 的元数据配置时,想要开启自动装配,只需要在 xml 配置文件 <bean>
中定义 ”autowire“ 属性。
<bean id = "customer" class="com.xxx.xxx.Customer" autowire="mode"/>
autowire 属性有 4 种模式:
- no – 缺省情况下,不自动装配。bean 的引用必须通过
ref
元素定义。
手动装配:以 value 或 ref 的方式明确指定属性值都是手动装配。需要通过 ”ref“ 属性来连接 bean.
- byName – 根据 bean 的属性名称进行自动装配,Spring 寻找与需要自动装配的属性同名的 bean. 例如,
Customer
的属性名称是person
,Spring 会将 bean id 为person
的 bean 通过 setter 方法进行自动装配。
<bean id="customer" class="com.xxx.xxx.Customer" autowire="byName"/>
<bean id="person" class="com.xxx.xxx.Person"/>
- byType – 根据 bean 的类型进行自动装配。如果容器中存在一个属性类型的 bean,则让属性自动装配。例如,
Customer
的属性person
的类型为Person
,Spring 会将Person
类型通过 setter 方法进行自动装配。
<bean id="customer" class="com.xxx.xxx.Customer" autowire="byType"/>
<bean id="person" class="com.xxx.xxx.Person"/>
- constructor – 类似 byType,不过是应用于构造函数参数,如果存在一个 bean 与构造函数参数的类型相同,则进行自动装配,否则导致异常。例如,
Customer
构造函数的参数person
的类型为Person
,Spring 会将Person
类型通过构造方法进行自动装配。
<bean id="customer" class="com.xxx.xxx.Customer" autowire="constructor"/>
<bean id="person" class="com.xxx.xxx.Person"/>
@Autowired 自动装配 bean,可以在字段、setter 方法、构造函数上使用。
🚁bean 是不是线程安全的?
首先,bean 不是线程安全的。因为 Spring 容器本身并没有提供 bean 的线程安全策略,因此可以说 Spring 容器中的 bean 本身不具备线程安全的特性,但是具体还是要结合具体 scope(作用域) 的 bean 去探讨。
线程安全这个问题,要从单例与原型 bean 分别进行说明。
1、原型 bean。对于原型 bean,每次创建一个新对象,也就是线程之间并不存在 bean 共享,自然是不会有线程安全的问题。
2、单例 bean。对于单例 bean,所有线程都共享一个单例实例 bean,因此是存在资源的竞争。如果单例 bean,是一个无状态(不具有数据存储功能) bean,也就是线程中的操作不会对 bean 的成员执行查询以外的操作,那么这个单例 bean 是线程安全的。比如 Spring MVC 的 Controller、Service、Dao 等,这些 bean 大多是无状态的,只关注于方法本身。
🚁Spring 单例,为什么 Controller、Service 和 Dao 却能保证线程安全?
Spring 中的 bean 默认是单例模式的,框架并没有对 bean 进行多线程的封装处理。实际上大部分时间 bean 是无状态的(比如Dao) 所以说在某种程度上来说 bean 其实是安全的。
但是如果 bean 是有状态的,那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变 bean 的作用域 把 “singleton” 改为’‘protopyte’ ,这样每次请求 bean 就相当于是 new bean() 这样就可以保证线程的安全了。
Controller、Service 和 Dao 层本身并不是线程安全的,如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。
🚁自动装配的优缺点是什么呢?
自动装配有如下优点:
- 可以显著减少指定属性或构造函数参数的必要。
- 当你的对象发生变化时,自动装配可以自动更新配置。 例如,当需要向类中添加依赖项时,不需要你手动修改配置,就可以自动满足该依赖项。
自动装配的限制或缺点:
- 自动装配不如显式装配精确。由于 Spring 对 bean 的猜测可能会产生歧义从而带来意想不到的结果,因此 Spring 管理的对象之间的关系可能不会准确的记录。
- 在容器内定义的多个 bean 可能会与要自动装配的 setter 方法或构造函数参数指定的类型匹配。 对于期望单个值的依赖项,这种歧义不会消除,如果没有唯一的 bean,则会引发异常。
🚁Spring、Spring MVC 和 Spring Boot 有什么区别?
Spring 是一个 IOC 容器,用来管理 bean,使用依赖注入实现控制反转,可以很方便地整合各种框架,提供 AOP 机制弥补 OOP 的代码重复问题,更方便地将不同类不同方法中的共同点抽取成切面,自动注入给方法执行,比如日志、异常等。
Spring MVC 是 Spring 对 Web 框架的一个解决方案,提供了一个总的前端控制器 DispatcherServlet,用来接受请求,然后定义了一套路由策略(url 到 handler 的映射)及适配执行 handler,将 handler 结果使用视图解析技术生成视图展现给前端。
SpringBoot 提供的一个快速开发工具包,让程序员能更方便、更快速的开发 Spring + Spring MVC 应用,简化了配置(约定大于配置),整合了一系列的解决方案(starter 机制)、redis、mongodb、jpa,可以开箱即用。
- 可以快速构建项目
- 项目可独立运行,无需外部依赖 Servlet 容器
- 提供运行时的应用监控
- 对主流开发框架的无配置集成
- 可以极大地提高开发、部署效率
🚁SpringMVC 执行流程
- 客户端发送请求至前端控制器 DispatcherServlet.
- 前端控制器 DispatcherServlet 收到请求调用 HandlerMapping 处理器映射器。
- 处理器映射器通过 xml 配置或注解找到具体的处理器,生成处理器对象以及拦截器(如果有则生成)一并返回给前端控制器 DispatcherServlet.
- 前端控制器 DispatcherServlet 调用 HandlerAdapter 处理器适配器。
- HandlerAdapter 经过适配调用具体的处理器(Controller,也叫后端控制器)。
- Controller 执行完成后返回 ModelAndView.
- HandlerAdapter 将 ModelAndView 返回给 DispatcherServlet.
- DispatcherServlet 将 ModelAndView 发送给 ViewReslover 视图解析器。
- ViewResolver 解析后返回具体 View.
- DispatcherServlet 根据 View 进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet 响应用户。
🚁Spring MVC 的主要组件?
Handler:也就是处理器。它直接应对着 MVC 中的 C 也就是 Controller 层,它的具体表现形式有很多,可以是类,也可以是方法。在Controller 层中 @RequestMapping 标注的所有方法都可以看成是一个 Handler,只要可以实际处理请求就可以是 Handler.
1、HandlerMapping
initHandlerMappings(context),处理器映射器,根据用户请求的资源 url 来查找 Handler 的。在 SpringMVC 中会有很多请求,每个请求都需要一个 Handler 处理,具体接收到一个请求之后使用哪个 Handler 进行,这就是 HandlerMapping 需要做的事。
2、HandlerAdapter
initHandlerAdapters(context),处理器适配器。因为 SpringMVC 中的 Handler 可以是任意的形式,只要能处理请求就 ok,但是 Servlet 需要的处理方法的结构却是固定的,都是以 request 和 response 为参数的方法。如何让固定的 Servlet 处理方法调用灵活的 Handler 来进行处理呢?这就是 HandlerAdapter 要做的事情。
Handler 是用来干活的工具;HandlerMapping 用于根据需要干的活找到相应的工具;HandlerAdapter 是使用工具干活的人。
3、HandlerExceptionResolver
initHandlerExceptionResolvers(context), 其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在 SpringMVC 中就是 HandlerExceptionResolver. 具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给 render 方法进行渲染。
4、ViewResolver
initViewResolvers(context),ViewResolver 用来将 String 类型的视图名和 Locale 解析为 View 类型的视图。View 是用来渲染页面的,也就是将程序返回的参数填入模板里,生成 html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是 ViewResolver 主要要做的工作,ViewResolver 需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。
5、RequestToViewNameTranslator
initRequestToViewNameTranslator(context),ViewResolver 是根据 ViewName 查找 View,但有的 Handler 处理完后并没有设置 View 也没有设置 ViewName,这时就需要从 request 获取 ViewName 了,如何从 request 中获取 ViewName 就是RequestToViewNameTranslator要做的事情了。
RequestToViewNameTranslator 在 Spring MVC 容器里只可以配置一个,所以所有 request 到 ViewName 的转换规则都要在一个Translator 里面全部实现。
6、LocaleResolver
initLocaleResolver(context), 解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是 LocaleResolver 要做的事情。LocaleResolver 用于从 request 解析出 Locale,Locale 就是 zh-cn 之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC 主要有两个地方用到了 Locale:一是 ViewResolver 视图解析的时候;二是用到国际化资源或者主题的时候。
7、ThemeResolver
initThemeResolver(context),用于解析主题。SpringMVC 中一个主题对应一个 properties 文件,里面存放着跟当前主题相关的所有资源、如图片、css 样式等。SpringMVC 的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC 中跟主题相关的类有 ThemeResolver、ThemeSource 和 Theme. 主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是 ThemeResolver 的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是 ThemeSource 的工作。最后从主题中获取资源就可以了。
8、MultipartResolver
initMultipartResolver(context),用于处理上传请求。处理方法是将普通的 request 包装成 MultipartHttpServletRequest,后者可以直接调用 getFile 方法获取 File,如果上传多个文件,还可以调用 getFileMap 得到 FileName->File 结构的 Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将 request 包装成 MultipartHttpServletRequest 处理完后清理上传过程中产生的临时资源。
9、FlashMapManager
initFlashMapManager(context),用来管理 FlashMap 的,FlashMap 主要用在 redirect 中传递参数。
🚁谈谈 SpringMVC 拦截器
拦截器会对处理器进行拦截,这样通过拦截器就可以增强处理器的功能。在 Spring MVC 中,所有的拦截器都需要实现 HandlerInterceptor 接口,该接口包含如下三个方法:preHandle()、postHandle()、afterCompletion(). SpringMVC 拦截器拦截流程如下图所示:
Spring MVC 拦截器的开发步骤如下:
- 开发拦截器:实现 HandlerInterceptor 接口,从三个方法中选择合适的方法,实现拦截时要执行的具体业务逻辑
- 注册拦截器:定义配置类,并让它实现 WebMvcConfigurer 接口,在接口的 addInterceptors方法中注册拦截器,并定义该拦截器匹配哪些请求路径
🚀SpringBoot
🚁谈谈 SpringBoot 的启动流程?
SpringBoot 项目创建完成会默认生成一个名为 *Application 的入口类,我们是通过该类的 main方法启动 SpringBoot项目的。在 main 方法中,通过 SpringApplication 的静态方法,即 run 方法进行 SpringApplication 类的实例化操作,然后再针对实例化对象调用另外一个 run 方法来完成整个项目的初始化和启动。
SpringApplication 调用的 run 方法的大致流程,如下图:
SpringApplication 在 run 方法中重点做了以下操作:
- 获取监听器和参数配置
- 打印 Banner 信息
- 创建并初始化容器
- 监听器发送通知
🚁SpringBoot 自动装配原理
具体详细讲解见 你一定能看懂的 SpringBoot 自动装配原理
总结版:
- SpringBoot 通过
@EnableAutoConfiguration
注解开启自动配置,加载spring.factories
中注册的各种AutoConfiguration
类,当某个AutoConfiguration
类满足其注解@Conditional
指定的生效条件(starters 提供的依赖、配置或 Spring 容器中是否存在某个 Bean 等)时,实例化该 AutoConfiguration 类中定义的 Bean(组件等),并注入 Spring 容器,就可以完成依赖框架的自动配置。
🚁如何理解 SpringBoot 中的 starter
SpringBoot 通过提供众多起步依赖(starter)降低项目依赖的复杂度。起步依赖本质上是一个maven 项目对象模型(Project Object Model, POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。很多起步依赖的命名都暗示了它们提供的某种或某类功能。
🚁SpringBoot 有哪些常用注解?
- @SpringBootApplication
- @EnableAutoConfiguration:由
@SpringBootApplication引入
,它的主要功能是启动 Spring 应用程序上下文时进行自动配置,会扫描各个 jar 包下的 spring.factories 文件,并加载文件中注册的 AutoConfiguration 类等。它的关键功能是通过@Import
注解导入的ImportSelector
来完成的 - @Configuration
- @ComponentScan:用于定义 Bean 的扫描策略,默认扫描启动类同级或者启动类下面的包中的spring注解。
- @Repository
- @Service
- @RestController
- @Component
- @Bean
- @AutoWired:Spring 提供的注解。
- @Resource:JDK 提供的注解。
- @Qualifier
- @ResponseBody:将控制层的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到 response 对象的 body 区,在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,它的效果等同于通过 response 对象输出指定格式的数据。通常用来返回 JSON 数据或者是 XML 数据,一般在异步获取数据时使用(ajax 异步请求)
- @RequestBody:用于从请求体中获取参数。
- @RequestMapping:声明类或方法的访问路径,还可以声明请求的方式。
- @RequestParam:将请求对象中的参数,绑定到控制器中方法的参数
- @PathVariable:将请求路径中的参数,绑定到控制器中方法的参数。
- @ConfigurationProperties
🚀Mybatis
🚁Mybatis 的优缺点
优点:
- 基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL 写在 xml 里,解除 SQL 与程序代码的耦合,便于统一管理;提供 xml 标签,支持编写动态 SQL 语句,并可重用。
- 与 JDBC 相比,减少了 50% 以上的代码量,消除了 JDBC 大量冗余的代码,不需要手动开关连接。
- 很好的与各种数据库兼容(因为 mybatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 mybatis 都支持)。
- 能够与 Spring 很好的集成。
- 提供映射标签,支持对象与数据库的 ORM 字段关系映射,提供对象关系映射标签,支持对象关系组件维护。
缺点:
- SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写 SQL 语句的功底有一定的要求。
- SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
🚁Mybatis 与 Hibernate 对比
SQL 和 ORM 的争论,永远都不会终止。
1. 开发速度的对比:
Hibernate 的真正掌握要比 Mybatis 难些。Mybatis 框架相对简单很容易上手,但也相对简单些。
比起两者的开发速度,不仅仅要考虑到两者的特性及性能,更要根据项目需求去考虑究竟哪一个更适合项目开发。比如,一个项目中用到的复杂查询基本没有,就是简单的增删改查,这样选择 Hibernate 效率就很快了,因为基本的 SQL 语句已经被封装好了,根本不需要你去写 SQL 语句,这就节省了大量的时间。但是对于一个大型项目,复杂语句较多,这样再去选择 Hibernate 就不是一个太好的选择,选择 Mybatis 就会加快许多,而且语句的管理也比较方便。
2. 开发工作量的对比:
Hibernate 和 Mybatis 都有相应的代码生成工具,可以生成简单基本的 DAO 层方法。针对高级查询,Mybatis 需要手动编写 SQL 语句,以及 ResultMap. 而 Hibernate 有良好的映射机制,开发者无需关心 SQL 的生成与结果映射,可以更专注于业务流程。
3. SQL 优化方面:
Hibernate 的查询会将表中的所有字段查询出来,这一点会有性能消耗。Hibernate 也可以自己写 SQL 来指定需要查询的字段,但这样就破坏了 Hibernate 开发的简洁性。而 Mybatis 的 SQL 是手动编写的,所以可以按需求指定查询的字段。
Hibernate HQL 语句的调优需要将 SQL 打印出来,而 Hibernate 的 SQL 被很多人嫌弃因为太丑了。Mybatis 的 SQL 是自己手动写的所以调整方便。但 Hibernate 具有自己的日志统计,Mybatis 本身不带日志统计,使用 Log4j 进行日志记录。
4. 对象管理的对比:
Hibernate 是完整的对象/关系映射解决方案,它提供了对象状态管理(state management)的功能,使开发者不再需要理会底层数据库系统的细节。也就是说,相对于常见的 JDBC/SQL 持久层方案中需要管理 SQL 语句,Hibernate 采用了更自然的面向对象的视角来持久化 Java 应用中的数据。换句话说,使用 Hibernate 的开发者应该总是关注对象的状态(state),不必考虑 SQL 语句的执行。这部分细节已经由 Hibernate 掌管妥当,只有开发者在进行系统性能调优的时候才需要进行了解。而 Mybatis 在这一块没有文档说明,用户需要对对象自己进行详细的管理。
5. 缓存机制对比:
相同点: 都可以实现自己的缓存或使用其他第三方缓存方案,创建适配器来完全覆盖缓存行为。
不同点: Hibernate 的二级缓存配置在 SessionFactory 生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是哪种缓存。
Mybatis 的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制,并且 Mybatis 可以在命名空间中共享相同的缓存配置和实例,通过 cache-ref 来实现。
两者比较:因为 Hibernate 对查询对象有着良好的管理机制,用户无需关心 SQL,所以使用二级缓存时如果出现脏数据,系统会报错并提示。
而 Mybatis 在这一方面,使用二级缓存时需要特别小心,如果不能完全确定数据更新操作的波及范围,避免 cache 的盲目使用,否则,脏数据的出现会给系统的正常运行带来很大的隐患。
Hibernate功能强大,数据库无关性好,O/R 映射能力强,如果你对 Hibernate 相当精通,而且对 Hibernate 进行了适当的封装,那么你的项目整个持久层代码会相当简单,需要写的代码很少,开发速度很快,非常爽。
Hibernate 的缺点就是学习门槛不低,要精通门槛更高,而且怎么设计 O/R 映射,在性能和对象模型之间如何权衡取得平衡,以及怎样用好 Hibernate 方面需要你的经验和能力都很强才行。
iBATIS入门简单,即学即用,提供了数据库查询的自动对象绑定功能,而且延续了很好的SQL使用经验,对于没有那么高的对象模型要求的项目来说,相当完美。
iBATIS 的缺点就是框架还是比较简陋,功能尚有缺失,虽然简化了数据绑定代码,但是整个底层数据库查询实际还是要自己写的,工作量也比较大,而且不太容易适应快速数据库修改。
🚁谈谈 Mybatis 的缓存机制
MyBatis 的缓存分为一级缓存和二级缓存:
1、一级缓存:
- 一级缓存也叫本地缓存,它默认会启用,并且不能关闭。一级缓存存在于 SqlSession 的生命周期中,即它是 SqlSession 级别的缓存。
- 在同一个 SqlSession 中查询时,MyBatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个 Map 对象中。
- 如果同一个 SqlSession 中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当Map 缓存对象中己经存在该键值时,则会返回缓存中的对象。
2、二级缓存:
- 二级缓存存在于 SqlSessionFactory 的生命周期中,即它是 SqlSessionFactory 级别的缓存。
- 若想使用二级缓存,需要在如下两处进行配置。在 MyBatis 的全局配置 settings 中有一个参数
cacheEnabled
,这个参数是二级缓存的全局开关,默认值是true
,初始状态为启用状态。 - MyBatis 的二级缓存是和命名空间绑定的,即二级缓存需要配置在 Mapper.xml 映射文件中。在保证二级缓存的全局配置开启的情况下,给 Mapper.xml 开启二级缓存只需要在 Mapper. xml 中添加如下代码:
<cache />
.
二级缓存具有如下效果:
- 映射语句文件中的所有 SELECT 语句将会被缓存。
- 映射语句文件中的所有 INSERT 、UPDATE 、DELETE 语句会刷新缓存。
- 缓存会使用 LRU (最近最少使用)算法来收回。
- 根据时间表(如no Flush Interval ,没有刷新间隔),缓存不会以任何时间顺序来刷新。
- 缓存会存储集合或对象(无论查询方法返回什么类型的值)的 1024 个引用。
- 缓存会被视为 read/write(可读/可写)的,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
🚁Mybatis 的工作原理
- 读取 mybatis-config.xml 配置文件。
- 加载 xxx-mapper.xml 映射文件。
- 构建 SqlSessionFactory 会话工厂。
- 构建 SqlSession 会话对象。
- Executor 执行器根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句操作数据库。
🚁#{} 和 ${} 的区别是什么
- #{} 是预编译处理,是占位符,${} 是字符串替换,是拼接符。
- Mybatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ?号,调用 PreparedStatement 来赋值。
- Mybatis 在处理 ${} 时,就是把 ${} 替换成变量的值,调用 Statement 来赋值。
- #{} 的变量替换是在 DBMS 中、变量替换后,#{} 对应的变量自动加上单引号。$ {} 的变量替换是在 DBMS 外、变量替换后,${} 对应的变量不会加上单引号。
- 使用 #{} 可以有效防止 SQL 注入,提高系统安全性。
🚁简述 Mybatis 的插件运行原理,如何编写一个插件
Mybatis 只支持针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4 种接口的插件,Mybatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke() 方法,拦截那些你指定需要拦截的方法。
编写插件:实现 Mybatis 的 Interceptor 接口并复写 interceptor() 方法,然后再给插件编写注解,指定要拦截哪一个接口的哪些方法即可,在配置文件中配置编写的插件。
@Intercepts({@Signature(type = StatementHandler.class, method = "query", args =
{Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args =
{Statement.class}), @Signature(type = StatementHandler.class, method = "batch", args = { Statement.class })})
@Component
invocation.proceed() 执行具体的业务逻辑
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/157051.html