口语化讲解Spring、Mybatis
前言
本文对Spring、Mybatis相关知识做了一个相对完整的总结,涉及到Spring IOC\DI、Bean、AOP、一二三级缓存、Mybatis运行原理、组件等。特别注意,口语化八股文系列,仅作突击复习核心知识点用,推荐有一定八股基础的人食用,更细的点需要大家自行查询相关详细图文资料
正文
Spring的IOC和DI
IOC意为控制反转,在Spring中就是用户将Bean的管理权限交给Spring,由Spring IOC容器来创建、配置和销毁Bean。DI意为依赖注入,Spring IOC容器将外部的文件、对象、数据等资源注入给程序中的对象。IOC是设计思想,DI就是它的具体实现。
BeanFactory、FactoryBean、ApplicationContext区别
BeanFactory是Spring IOC容器的顶层接口,定义了一些基础功能和规范,内部只包含getBean、contains等获取和判断Bean的接口,装载Bean时是懒加载。
ApplicationContext是它的子类,在BeanFactory基础上有更丰富的功能,采用预加载,在启动时创建所有Bean。
FactoryBean同样也是一个接口,用来生成复杂的bean,单独用意义不大,通常配合Bean生命周期中的InitializingBean、BeanNameAware等接口,实现更复杂的操作。
@Component 和 @Bean 的区别是什么?
- 作用对象不同,@Component注解作用于类,@Bean作用于方法。
- @Component注解的类,需要通过@ComponentScan注解指定路径扫描到才能装载到Spring IOC容器中。@Bean除了需要在扫描路径下,还需要当前类加上@Configuration注解。
- @Bean比@Component更加灵活,比如引用三方库中的类来创建bean只能通过@Bean实现,因为我们不能直接在三方库的类上注解。
解释 Spring 支持的几种 Bean 的作用域
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 情形下有效。缺省的 Spring bean 的作用域是 Singleton。
Bean生命周期
总结一下,Bean容器查找并加载Spring管理的Bean,进行实例化,然后进行属性赋值,如果实现了BeanNameAware之类的以Aware为后缀的接口,则调用对应的方法。如果有实现BeanPostProcessor接口,则在Bean调用初始化方法的前后执行BeanPostProcessor的前后置方法。当要销毁Bean,调用对应的销毁方法。个人在使用时,比较常见的是实现BeanDefinitionRegistryPostProcessor接口,重写方法将IOC容器中的某个Bean给移除,通常是将三方框架中的某些配置类给干掉。
- Bean 容器找到配置文件中 Spring Bean 的定义。
- Bean 容器利用 Java Reflection API 创建一个Bean的实例。
- 如果涉及到一些属性值 利用 set()方法设置一些属性值。
- 如果 Bean 实现了 BeanNameAware 接口,调用 setBeanName()方法,传入Bean的名字。
- 如果 Bean 实现了 BeanClassLoaderAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoader对象的实例。
- 如果Bean实现了 BeanFactoryAware 接口,调用 setBeanClassLoader()方法,传入 ClassLoade r对象的实例。
- 与上面的类似,如果实现了其他 *.Aware接口,就调用相应的方法。
- 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessBeforeInitialization() 方法
- 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。
- 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
- 如果有和加载这个 Bean的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法
- 当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接口,执行 destroy() 方法。
- 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
Spring 为何需要三级缓存解决循环依赖
Spring有三级缓存,其实就是三个Map,一级缓存存储实例化和初始化都完成的成品Bean。二级缓存存的是半成品,仅完成实例化,没有完成属性注入和初始化,用以解决对象创建时的循环依赖问题。三级缓存存储Bean工厂对象,用来生成半成品Bean放入二级缓存中,这一级缓存用来解决存在AOP时的循环依赖问题。
普通循环依赖,a依赖b,b依赖a。获取a,先看一级缓存有没有数据,没有的话将a的BeanFactory对象放入三级缓存。a开始属性注入,发现b没有创建,实例化b,同样将b的BeanFactory对象放入三级缓存。b也开始属性注入,发现一、二级缓存没有a,调用三级缓存中a的BeanFactory.getObject方法,将a放入二级缓存并清除三级缓存(一二三顺序获取,清除释放空间)。此时b成功注入a,完成初始化,放入一级缓存,并清除二三级缓存。继续a属性注入,此时从一级缓存中获取b,完成初始化,并清除a的二三级缓存。最后获取b,从一级缓存中取出即可。
干掉二级缓存,只用一三级行不行?不行,如果一级缓存中没有值,从BeanFactory.getObject中每次都拿到新的代理对象,如果有二级缓存,那么就会有单例的对象放入二级缓存中,根据一二三顺序获取,就不会每次在三级缓存中重新生成一个新的。
如果干掉三级缓存,只有一二级行不行?如果存在AOP则不行,因为三级缓存生成的对象可能是原始的,而不是我们想要的代理增强对象,从而导致初始化异常。
Spring使用三级缓存解决循环依赖?终于完全弄明白了_spring三级缓存如何解决循环依赖_三七有脾气的博客-CSDN博客
Spring设计模式
工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
代理设计模式 : Spring AOP 功能的实现。
单例设计模式 : Spring 中的 Bean 默认都是单例的。
模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
AOP原理(Cglib和JDK动态代理)
- 解析AOP配置,注册切面类,读取增强方法,获取切入点等等
- spring容器启动,创建bean时,对符合条件的bean生成代理类和增强方法链
- bean方法被调用时,代理类的增强链方法依次执行
代理分为静态和动态两种,动态代理根据实现方式细分为Cglib和JDK两种。静态代理需要实现接口,将原接口的实现类放入代理类作为代理对象,再重写接口的每个方法做增强。一般不会用静态代理,耦合严重,一般使用动态代理。JDK动态代理是用过反射机制生成一个和被代理类实现相同接口且继承Proxy类的代理类,实现InvocationHandler接口重写invoke方法进行代理增强。Cglib动态代理通过使用ASM开源包,加载被代理对象的class文件,并修改其字节码文件生成一个继承被代理对象的子类来作为代理对象,并重写代理方法使其调用自定义的方法拦截器去执行。
两种动态代理的区别:
- JDK动态代理要求⽬标对象实现⼀个接⼝,但是有时候⽬标对象只是⼀个单独的对象,并没有实现任何的接⼝,这个时候就可以⽤CGLib动态代理
- CGLib动态代理,它是在内存中构建⼀个⼦类对象从⽽实现对⽬标对象功能的扩展 ,基于继承来实现代理,所以⽆法对final类、private⽅法和static⽅法实现代理
- JDK动态代理是⾃带的,CGlib需要引⼊第三⽅包
Spring AOP中的代理使⽤的默认策略:
- 如果⽬标对象实现了接⼝,则默认采⽤JDK动态代理
- 如果⽬标对象没有实现接⼝,则采⽤CgLib进⾏动态代理
- 如果⽬标对象实现了接⼝,程序⾥⾯依旧可以指定使⽤CGlib动态代理
注解 @Autowired 和 @Resource 有什么区别?
- Resource 是 JDK 提供的,而 Autowired 是 Spring 提供的
- Resource 不允许找不到 bean 的情况,而 Autowired 允许(@Autowired(required = false))
- 指定 name 的方式不一样,@Resource(name = "baseDao"),@Autowired()@Qualifier("baseDao")
- Resource 默认通过 name 查找,而 Autowired 默认通过 type 查找
Spring事务的种类、事务传播行为、隔离级别
Spring支持编程式事务管理和声明式事务管理两种方式:
- 编程式事务管理使用TransactionTemplate。
- 声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。唯一不足地方是,声明式事务管理最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
spring的事务传播行为
spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。
① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘
③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。
④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
Spring中的隔离级别
① ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
② ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务可以看到这个事务未提交的数据。
③ ISOLATION_READ_COMMITTED:读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。
④ ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。
⑤ ISOLATION_SERIALIZABLE:一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。
Spring MVC 请求处理流程
- 客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
- DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
- 解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
- HandlerAdapter 会根据 Handler来调用真正的处理器开处理请求,并处理相应的业务逻辑。
- 处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。
- ViewResolver 会根据逻辑 View 查找实际的 View。
- DispaterServlet 把返回的 Model 传给 View(视图渲染)。
- 把 View 返回给请求者(浏览器)
Spring MVC 九大组件
HandlerMapping(处理器映射器)
HandlerMapping 是⽤来查找 Handler 的,也就是处理器,具体的表现形式可以是类,也可以是 ⽅法。⽐如,标注了@RequestMapping的每个⽅法都可以看成是⼀个Handler。Handler负责具 体实际的请求处理,在请求到达后,HandlerMapping 的作⽤便是找到请求相应的处理器 Handler 和 Interceptor.
HandlerAdapter(处理器适配器)
HandlerAdapter 是⼀个适配器。因为 Spring MVC 中 Handler 可以是任意形式的,只要能处理请求即可。但是把请求交给 Servlet 的时候,由于 Servlet 的⽅法结构都是 doService(HttpServletRequest req,HttpServletResponse resp)形式的,要让固定的 Servlet 处理 ⽅法调⽤ Handler 来进⾏处理,便是 HandlerAdapter 的职责。
HandlerExceptionResolver
HandlerExceptionResolver ⽤于处理 Handler 产⽣的异常情况。它的作⽤是根据异常设置 ModelAndView,之后交给渲染⽅法进⾏渲染,渲染⽅法会将 ModelAndView 渲染成⻚⾯。
ViewResolver
ViewResolver即视图解析器,⽤于将String类型的视图名和Locale解析为View类型的视图,只有⼀ 个resolveViewName()⽅法。从⽅法的定义可以看出,Controller层返回的String类型视图名 viewName 最终会在这⾥被解析成为View。View是⽤来渲染⻚⾯的,也就是说,它会将程序返回 的参数和数据填⼊模板中,⽣成html⽂件。ViewResolver 在这个过程主要完成两件事情: ViewResolver 找到渲染所⽤的模板(第⼀件⼤事)和所⽤的技术(第⼆件⼤事,其实也就是找到 视图的类型,如JSP)并填⼊参数。默认情况下,Spring MVC会⾃动为我们配置⼀个 InternalResourceViewResolver,是针对 JSP 类型视图的。
RequestToViewNameTranslator
RequestToViewNameTranslator 组件的作⽤是从请求中获取 ViewName.因为 ViewResolver 根据 ViewName 查找 View,但有的 Handler 处理完成之后,没有设置 View,也没有设置 ViewName, 便要通过这个组件从请求中查找 ViewName。
LocaleResolver
ViewResolver 组件的 resolveViewName ⽅法需要两个参数,⼀个是视图名,⼀个是 Locale。 LocaleResolver ⽤于从请求中解析出 Locale,⽐如中国 Locale 是 zh-CN,⽤来表示⼀个区域。这 个组件也是 i18n 的基础。
ThemeResolver
ThemeResolver 组件是⽤来解析主题的。主题是样式、图⽚及它们所形成的显示效果的集合。 Spring MVC 中⼀套主题对应⼀个 properties⽂件,⾥⾯存放着与当前主题相关的所有资源,如图 ⽚、CSS样式等。创建主题⾮常简单,只需准备好资源,然后新建⼀个“主题名.properties”并将资 源设置进去,放在classpath下,之后便可以在⻚⾯中使⽤了。SpringMVC中与主题相关的类有 ThemeResolver、ThemeSource和Theme。ThemeResolver负责从请求中解析出主题名, ThemeSource根据主题名找到具体的主题,其抽象也就是Theme,可以通过Theme来获取主题和 具体的资源。
MultipartResolver
MultipartResolver ⽤于上传请求,通过将普通的请求包装成 MultipartHttpServletRequest 来实现MultipartHttpServletRequest 可以通过 getFile() ⽅法 直接获得⽂件。如果上传多个⽂件,还 可以调⽤ getFileMap()⽅法得到Map这样的结构,MultipartResolver 的作⽤就 是封装普通的请求,使其拥有⽂件上传的功能。
FlashMapManager
FlashMap ⽤于重定向时的参数传递,⽐如在处理⽤户订单时候,为了避免重复提交,可以处理完 post请求之后重定向到⼀个get请求,这个get请求可以⽤来显示订单详情之类的信息。这样做虽然 可以规避⽤户重新提交订单的问题,但是在这个⻚⾯上要显示订单的信息,这些数据从哪⾥来获得 呢?因为重定向时么有传递参数这⼀功能的,如果不想把参数写进URL(不推荐),那么就可以通 过FlashMap来传递。只需要在重定向之前将要传递的数据写⼊请求(可以通过 ServletRequestAttributes.getRequest()⽅法获得)的属性OUTPUT_FLASH_MAP_ATTRIBUTE 中,这样在重定向之后的Handler中Spring就会⾃动将其设置到Model中,在显示订单信息的⻚⾯ 上就可以直接从Model中获取数据。FlashMapManager 就是⽤来管理 FalshMap 的。
监听器、过滤器和拦截器对比
- Servlet:处理Request请求和Response响应 过滤器(Filter):对Request请求起到过滤的作⽤,作⽤在Servlet之前,如果配置为/*可以对所 有的资源访问(servlet、js/css静态资源等)进⾏过滤处理
- 监听器(Listener):实现了javax.servlet.ServletContextListener 接⼝的服务器端组件,它随 Web应⽤的启动⽽启动,只初始化⼀次,然后会⼀直运⾏监视,随Web应⽤的停⽌⽽销毁
-
- 作⽤⼀:做⼀些初始化⼯作,web应⽤中spring容器启动ContextLoaderListener
- 作⽤⼆:监听web中的特定事件,⽐如HttpSession,ServletRequest的创建和销毁;变量的创建、 销毁和修改等。可以在某些动作前后增加处理,实现监控,⽐如统计在线⼈数,利⽤ HttpSessionLisener等。
- 拦截器(Interceptor):是SpringMVC、Struts等表现层框架⾃⼰的,不会拦截 jsp/html/css/image的访问等,只会拦截访问的控制器⽅法(Handler)。
从配置的⻆度也能够总结发现:serlvet、filter、listener是配置在web.xml中的,⽽interceptor是 配置在表现层框架⾃⼰的配置⽂件中的 在Handler业务逻辑执⾏之前拦截⼀次 在Handler逻辑执⾏完毕但未跳转⻚⾯之前拦截⼀次 在跳转⻚⾯之后拦截⼀次
@SpringBootApplication组成
- @SpringBootConfiguration
-
- @Configuration //通过javaConfig的方式来添加组件到IOC容器中
- @EnableAutoConfiguration
-
- @AutoConfigurationPackage //自动配置包,与@ComponentScan扫描到的添加到IOC
- @Import(AutoConfigurationImportSelector.class) //到METAINF/spring.factories中定义的bean添加到IOC容器中
- @ComponentScan //包扫描
项目初始化启动过程
第一步:获取并启动监听器
this.getRunListeners(args)和listeners.starting()方法主要用于获取SpringApplication实例初始化过程中初始化的SpringApplicationRunListener监听器并运行。
第二步:根据SpringApplicationRunListeners以及参数来准备环境
this.prepareEnvironment(listeners, applicationArguments)方法主要用于对项目运行环境进 行预设置,同时通过this.configureIgnoreBeanInfo(environment)方法排除一些不需要的运行环境
第三步:创建Spring容器
根据webApplicationType进行判断, 确定容器类型,如果该类型为SERVLET类型,会通过反射装载对应的字节码,也就是AnnotationConfigServletWebServerApplicationContext,接着使用之前初始化设置的context(应用上下文环境)、environment(项目运行环境)、listeners(运行监听器)、applicationArguments(项目参数)和printedBanner(项目图标信息)进行应用上下文的组装配置,并刷新配置
第四步:Spring容器前置处理
这一步主要是在容器刷新之前的准备动作。设置容器环境,包括各种变量等等,其中包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础
第五步:刷新容器
开启刷新spring容器,通过refresh方法对整个IOC容器的初始化(包括bean资源的定位,解析,注册等等),同时向JVM运行时注册一个关机钩子,在JVM关机时会关闭这个上下文,除非当时它已经关闭
第六步:Spring容器后置处理
扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理。
第七步:发出结束执行的事件
获取EventPublishingRunListener监听器,并执行其started方法,并且将创建的Spring容器传进去了,创建一个ApplicationStartedEvent事件,并执行ConfigurableApplicationContext 的publishEvent方法,也就是说这里是在Spring容器中发布事件,并不是在SpringApplication中发布事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的监听器发布启动事件。
第八步:执行Runners
用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序。其中,Spring Boot提供的执行器接口有ApplicationRunner 和CommandLineRunner两种,在使用时只需要自定义一个执行器类实现其中一个接口并重写对应的run()方法接口,然后Spring Boot项目启动后会立即执行这些特定程序
MyBatis的主要成员
- Configuration MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中
- SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互时的会话,完成必要数据库增删改查功能
- Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
- StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数等
- ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所对应的数据类型
- ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
- TypeHandler 负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换
- MappedStatement MappedStatement维护一条节点的封装
- SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
- BoundSql 表示动态生成的SQL语句以及相应的参数信息
Mybatis工作原理
- SqlSessionFactoryBuilder通过Configuration对象生成SqlSessionFactory,用来开启SqlSession。
- SqlSession对象完成和数据库的交互:
- 用户程序调用mybatis接口层api(即Mapper接口中的方法) 1. SqlSession通过调用api的Statement ID找到对应的MappedStatement对象 1. 通过Executor(负责动态SQL的生成和查询缓存的维护)将MappedStatement对象进行解析,sql参数转化、动态sql拼接,生成jdbc Statement对象,使用Paramterhandler填充参数,使用statementHandler绑定参数。 1. JDBC执行sql。 1. 借助MappedStatement中的结果映射关系,使用ResultSetHandler将返回结果转化成HashMap、JavaBean等存储结构并返回。 1. 关闭sqlsession会话。
{}和${}的区别是什么?
#{}是预编译处理,${}是字符串替换。使用#{}可以有效的防止 SQL 注入,提高系统安全性。
在处理#{}时,MyBatis会自动生成PreparedStatement,使用参数绑定(?)的方式来设置值;
在处理时,就是把{}时,就是把时,就是把{}替换成变量的值。
浅析count(1)、count(*)与count(列名)的执行区别
count(1) 包括了忽略所有列,用1代表代码行,在统计结果的时候, 不会忽略列值为NULL
count(*) 包括了所有的列,相当于行数,在统计结果的时候, 不会忽略列值为NULL。MySQL推荐统计行数语法,和count(1)效果一致。
count(列名) 只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是指空字符串或者0,而是表示null)的计数, 即某个字段值为NULL时,不统计。
Mybatis都有哪些Executor执行器?它们之间的区别是什么?
Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
- SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。
- ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。
- BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
Mybatis是如何进行分页的?分页插件的原理是什么?Mybatis 的插件运行原理?
Mybatis 分页的三种方式:
- RowBounds 对象进行分页
- 直接编写 sql 实现分页
- 使用Mybatis 的分页插件。
分页插件的原理:
实现 Mybatis 提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql。举例:select * from student,拦截 sql 后重写为:select t.* from (select * from student)t limit 0,10
Mybatis插件
Mybatis 仅可以编写针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4 种接口的插件,Mybatis 通过动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler 的 invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
Mybatis动态sql有什么用?执行原理?有哪些动态sql?
Mybatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql,执行原理是根据表达式的值 完成逻辑判断并动态拼接sql的功能。
Mybatis提供了9种动态sql标签: trim|where|set|foreach|if|choose|when|otherwise|bind 。其执行原理为,使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。
Mybatis的一级、二级缓存
一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Sessionflush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;
对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D操作后,默认该作用域下所有 select 中的缓存将被 clear。
为什么说 Mybatis 是半自动 ORM 映射工具?它与全自动的区别在哪里?
Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而 Mybatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。
Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?
Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。
它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原理
写在最后
本文后续持续补充中,以上仅为个人对以往资料的粗略整合,留待后续根据实际情况精细整理。2023.04.02晚22点,大家早些休息,身体健康,生活愉快。朝もお昼も夢の中も-MACO,希望这首歌给大家清早带来快乐
转载自:https://juejin.cn/post/7217601930917773349