文章目录
1. Spring
1.1 Spring是什么?
介绍
Spring 是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性。 其是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础***的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。主要包括以下七个模块:
Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);
Spring Core:核心类库,所有功能都依赖于该类库,提供IOC和DI服务;
Spring AOP:AOP服务;
Spring Web:提供了基本的面向Web的综合特性,提供对常见框架如Struts2的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器;
Spring MVC:提供面向Web应用的Model-View-Controller,即MVC实现。
Spring DAO:对JDBC的抽象封装,简化了数据访问异常的处理,并能统一管理JDBC事务;
Spring ORM:对现有的ORM框架的支持;
Spring的优点
-
Spring 最核心的思想就是不重新造轮子,开箱即用!
-
Spring的控制反转将对象之间的依赖关系交由框架处理,减低组件的耦合性;
-
Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。
-
Spring属于低侵入式设计,代码的污染极低;
-
Spring对于主流的应用框架提供了集成支持。
1.3 Spring的IOC
IoC(Inverse of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spirng 特有,在其他语言中也有应用。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
最直观的表达就是,以前创建对象的主动权和时机都是由自己把控的,IOC让对象的创建不用去new了,可以由Spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。
(3)Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。
1.4 Spring的AOP
AOP(面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib ,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
1.4.1 Spring AOP与AspectJ AOP
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
-
AspectJ 是静态代理的增强,所谓静态代理,就是 AOP 框架会在编译阶段生成 AOP 代理类,因此也称为编译时增强,它会在编译阶段将切面织入到 Java 字节码中,运行的时候就是增强之后的 AOP 对象。
-
Spring AOP 使用的是动态代理,所谓的动态代理就是说 AOP 框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个 AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。(Spring AOP 中的动态代理主要有两种方式, JDK 动态代理和 CGLIB 动态代理)。
-
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
1.4.2 JDK动态代理和CGLIB动态代理
JDK动态代理
JDK动态代理只提供接口的代理,不支持类的代理,要求被代理类实现接口。JDK动态代理的核心是InvocationHandler接口和Proxy类,在获取代理对象时,使用Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自Proxy并实现了我们定义的接口),当代理对象调用真实对象的方法时, InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理对象; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
CGLIB动态代理
如果被代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类,CGLIB是通过继承的方式做的动态代理。
CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。
1.5 Spring bean
简单来说,bean 代指的就是那些被 IoC 容器所管理的对象。
我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据的定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。
1.5.1 Spring bean的作用域
-
singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。
-
prototype:为每一个bean请求创建一个实例。
-
request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
-
session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。
-
global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。
1.5.2 Spring bean的生命周期
简单来说,Spring Bean的生命周期只有四个阶段:实例化 Instantiation --> 属性赋值 Populate --> 初始化 Initialization --> 销毁 Destruction
但具体来说,Spring Bean的生命周期包含下图的流程:
-
实例化Bean:对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
-
设置对象属性(依赖注入):实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入。
-
处理Aware接口:Spring会检测该对象是否实现了xxxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的一些资源:
- 如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,传入Bean的名字;
- 如果这个Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
- 如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
- 如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;
-
BeanPostProcessor前置处理:如果想对Bean进行一些自定义的前置处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。
-
InitializingBean:如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法。
-
init-method:如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
-
BeanPostProcessor后置处理:如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;
以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。
-
DisposableBean:当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;
-
destroy-method:最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
1.6 Spring容器
Spring容器是Spring的核心,一切Spring bean都存储在Spring容器内,并由其通过IoC技术管理。Spring容器也就是一个bean工厂(BeanFactory)。应用中bean的实例化,获取,销毁等都是由这个bean工厂管理的。
1.6.1 Spring容器的启动过程
1.6.2 BeanFactory和ApplicationContext有什么区别?
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。
-
国际化
- BeanFactory是不支持国际化功能的,因为BeanFactory没有扩展Spring中MessageResource接口。
- 相反,由于ApplicationContext扩展了MessageResource接口,因而具有消息处理的能力(i18N)
-
强大的事件机制(Event)
基本上牵涉到事件(Event)方面的设计,就离不开观察者模式,ApplicationContext的事件机制主要通过ApplicationEvent和ApplicationListener这两个接口来提供的,和java swing中的事件机制一样。即当ApplicationContext中发布一个事件的时,所有扩展了ApplicationListener的Bean都将会接受到这个事件,并进行相应的处理。
-
底层资源的访问
- ApplicationContext扩展了ResourceLoader(资源加载器)接口,从而可以用来加载多个Resource,
- 而BeanFactory是没有扩展ResourceLoader
-
延迟加载
- BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化,这样,我们就不能发现一些存在的spring的配置问题。
- 而ApplicationContext则相反,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误。
-
BeanFactory和ApplicationContext都支持BeanPostProcessor(增强处理)、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册
-
BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
1.7 循环依赖与三级缓存
1.7.1 循环依赖的解决方法
Spring通过三级缓存解决了循环依赖。
一级缓存为单例池,二级缓存为早期曝光对象,三级缓存为早期曝光对象工厂。
当A、B两类发生循环引用,在A实例化之后,将自己提早曝光(即加入三级缓存),如果A初始AOP代理,该工厂对象返回的是被代理的对象,若未被代理,返回对象本身。当A进行属性注入时,经过之前实例化步骤,
此时轮到B属性注入,调用getBean(a)获取A对象,由于A处理正在创建集合中,此时也发了循环依赖,所以可以从三级缓存获取对象工厂获得A(如果A被AOP代理,此时返回就是代理对象),并把A对象放到二级缓存中,这样保证A只经过一次AOP代理。接下来,B走完Spring生命周期流程,并放入单例池中。当B创建完后,会将B注入A,A走完Spring生命周期流程。到此,循环依赖结束。
1.7.2 为什么用三级缓存而不是二级缓存?
在bean被AOP进行切面代理的前提下,我们会发现再执行一遍singleFactory.getObject()方法又是一个新的代理对象,这就会有问题了,因为AService是单例的,每次执行singleFactory.getObject()方法又会产生新的代理对象,假设这里只有一级和三级缓存的话,我每次从三级缓存中拿到singleFactory对象,执行getObject()方法又会产生新的代理对象,造成大量的资源浪费。
所以如果没有AOP的话确实可以两级缓存就可以解决循环依赖的问题,如果加上AOP,两级缓存是无法解决的,不可能每次执行singleFactory.getObject()方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象
1.7.3 其他问题
- 单例bean的循环引用是因为每个对象都是固定的,只是提前暴露对象的引用,最终这个引用对应的对象是创建完成的。而多例之间的循环引用是无法解决的。
- 构造器注入方式的循环依赖一般无法解决,除非A中注入B的方式为setter方法,B中注入A的方式为构造器
1.8 Spring框架中的Bean是线程安全的么?
Spring框架中的Bean是线程安全的么?如果线程不安全,那么如何处理?
Spring 框架并没有对单例 bean 进行任何多线程的封装处理,需要开发者自行处理。如果 Spring bean 并没有可变的状态,那么它是线性安全的。
如果 bean 有多种状态,则需要自行保证线程安全,例如将其作用域从 singleton 改为 prototype。
- 对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。
- 对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。
bean的状态
- 有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。
- 无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。
对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。
1.9 Spring事务
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,Spring是无法提供事务功能的。Spring只提供统一事务管理接口,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过binlog或者undo log实现的。Spring会在事务开始时,根据当前环境中设置的隔离级别,调整数据库隔离级别,由此保持一致。
1.9.1 事务的种类
Spring支持编程式事务管理以及声明式事务管理两种方式。
编程式事务管理
编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。
声明式事务管理
建立在 AOP 之上,其本质是通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
1.10 Spring的自动装配
在Spring中,使用autowire来配置自动装载模式,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象。
在Spring框架xml配置***有5种自动装配
- no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
- byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。
- byType:通过参数的数据类型进行自动装配。
- constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
- autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。
基于注解的自动装配方式
如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
如果查询的结果不止一个,那么@Autowired会根据名称来查找;
如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。
@Autowired可用于:构造函数、成员变量、Setter方法
注:@Autowired和@Resource之间的区别:
@Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。
@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。
Spring 框架中都用到了哪些设计模式?
Spring设计模式的详细使用案例可以阅读这篇文章:https://blog.csdn.net/a745233700/article/details/112598471
(1)工厂模式:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象
(2)单例模式:Bean默认为单例模式
(3)策略模式:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略
(4)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
(5)模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如RestTemplate, JmsTemplate, JpaTemplate
(6)适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller
(7)观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用。
(8)桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库
2. Springboot
2.1 Springboot是什么?
Spring Boot使创建独立的、基于生产级Spring的应用程序变得很容易,您可以“直接运行”这些应用程序。
我们对Spring平台和第三方库有自己的见解,这样您就可以轻松入门了。大多数Spring引导应用程序只需要很少的Spring配置。
Springboot具备的特征:
(1)可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和WARs;
(2)内嵌Tomcat或Jetty等Servlet容器;
(3)提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置;
(4)尽可能自动配置Spring容器;
(5)提供准备好的特性,如指标、健康检查和外部化配置;
(6)绝对没有代码生成,不需要XML配置。
Springboot的两个重要策略:
- 开箱即用:是指在开发过程中,通过在MAVEN项目的pom文件中添加相关依赖包,然后使用对应注解来代替繁琐的XML配置文件以管理对象的生命周期。
- 约定优于配置:是一种由SpringBoot本身来配置目标结构,由开发者在结构中添加信息的软件设计范式。这一特点虽降低了部分灵活性,增加了BUG定位的复杂性,但减少了开发人员需要做出决定的数量,同时减少了大量的XML配置,并且可以将代码编译、测试和打包等工作自动化。
**Springboot的优点:**独立运行、简化配置、自动配置、无代码生成无xml配置、无需部署内嵌Tomcat服务器
2.2 @SpringBootApplication的作用
@SpringBootApplication仅仅是组合了三个注解,见下方
2.2.1 @ComponentScan
@ComponentScan的作用就是根据定义的扫描路径,把符合扫描规则的类装配到spring容器中,默认扫描路径为启动入口文件所在目录及其下的所有子包。
@ComponentScan默认将@Controller,@Service,@Repository,@Component注解的类扫描到Spring 容器中。
2.2.2 @EnableAutoConfiguration
@EnableAutoConfiguration的作用是开启自动配置,也就是你要用一些框架时,不要再自己配置,它直接给你导入进来。
通过深入@EnableAutoConfiguration的源码,会发现其最后会加载"/META-INF/spring.factories"配置文件,,当你要用的时候,他就根据这个配置文件去对应的包下面找具体配置类,并加载到应用中,这就是自动配置。
2.2.3 @SpringBootConfiguration
@SpringBootConfiguration继承自@Configuration,二者功能也一致,标注当前类是配置类,
并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名。
2.3 Spring boot中的Starters
Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包,你可以一站式集成Spring及其他技术,而不需要到处找示例代码和依赖包。如你想使用Spring JPA访问数据库,只要加入spring-boot-starter-data-jpa启动器依赖就能使用了。
Starters包含了许多项目中需要用到的依赖,它们能快速持续的运行,都是一系列得到支持的管理传递性依赖。
3. Mybatis
3.1 什么是Mybatis
Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,加载驱动、创建连接、创建statement等繁杂的过程,开发者开发时只需要关注如何编写SQL语句,可以严格控制sql执行性能,灵活度高。
MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
3.2 Mybatis的优缺点
优点:
- 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
- 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
- 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)
- 能够与Spring很好的集成,简单易学,快速上手
缺点:
- SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
- SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库
3.3 Mybatis的底层原理
3.4 #{}与${}的区别
-
#传入的参数在SQL中显示为字符串,#方式能够很大程度防止SQL注入,属于预编译处理
-
$传入的参数在SQL中直接显示为传入的值,$方式无法防止SQL注入,属于字符串替换
Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;
Mybatis在处理 时 , 就 是 把 {}时,就是把 时,就是把{}替换成变量的值。
使用#{}可以有效的防止SQL注入,提高系统安全性,一般能用#的就别用$。而$方式一般用于传入数据库对象,例如传入表名,传入Order by的动态参数等
3.5 Mybatis缓存机制
**缓存是一般的ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。**跟Hibernate 一样,MyBatis 也有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。
MyBatis提供了一级缓存和二级缓存:
-
一级缓存:也称为本地缓存,用于保存用户在一次会话过程中查询的结果,用户一次会话中只能使用一个sqlSession,一级缓存是自动开启的,不允许关闭。
-
二级缓存:也称为全局缓存,是mapper级别的缓存,是针对一个表的查结果的存储,可以共享给所有针对这张表的查询的用户。也就是说对于mapper级别的缓存不同的sqlsession是可以共享的。
3.5.1 一级缓存
在应用运行过程中,在一次数据库会话中,执行多次查询条件完全相同的SQL,会优先命中一级缓存,避免直接对数据库中直接查询。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LVeqAmo0-1631113768860)(…/images/Spring中Mybatis一级缓存.PNG)]
每个SqlSession中都持有Excutor,每个Excutor中有一个LocalCache。当用户发起询问时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。
总结:
- MyBatis一级缓存的生命周期和SqlSession一致。
- MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
- MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据。
- mybatis和spring整合后进行mapper代理开发,不支持一级缓存。
- 当某一作用域下(一级缓存Session、二级缓存NameSpace)进行了CRUD操作时,默认该作用下所有select中的缓存被clear。
3.5.2 二级缓存
开启二级缓存后,会使用CachingExecutor装饰Executor,**进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,**具体的工作流程如下所示。
二级缓存开启后,同一个namespace/mapper下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
源码分析:
MyBatis在为SqlSession对象创建Excutor对象时候,会给Executor对象加上一个装饰者:CachingExecutor,这时SqlSession使用CachingExecutor对象来完成操作请求。CachingExecutor对于查询请求,会先判断该查询请求在二级缓存中是否有缓存,如果有则直接返回缓存结果;
如果没有再交给真正的Executor对象来完成查询操作,之后CachingExecutor会将真正Executor返回的查询结果放置到缓存中,然后再返回给用户。
二级缓存失效的原因:
- flushCache属性在查询中作用针对二级缓存导致失效
- flushCache属性在查询中作用针对一级缓存导致失效
- flushCache属性在更新中作用导致两次查询结果完全一样
总结:
-
二级缓存的生命周期和服务的生命周期一样
-
MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
-
MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
-
**在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,**需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis,Memcached等分布式缓存可能成本更低,安全性也更高。