一、Spring MVC回顾

1.1、Spring MVC的五大组件

1、DispatcherServlet 前端控制器

这个控件是 SpringMVC 最核心的一个控件,Spring MVC写好的一个Servlet,它是整个流程的控制中心,由它调用其他组件处理用户请求,DispatcherServlet的存在降低了组件间的耦合。

主要负责捕获来自客户端的请求及调度各个组件。

2、HandlerMapping 处理器映射器

HandlerMapping负责根据用户请求找到Handler,即处理器,Spring MVC提供了不同的映射器来实现不同的映射方式,例如:配置文件方式、实现接口方式、注解方式。

**根据URL查找后端控制器 Handler **

3、Handler 处理器

即Controller,它是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到Handler。由Handler对具体的用户请求进行处理。

负责处理前端请求,完成业务逻辑,生成 ModelAndView 对象后将结果返回给前端控制器

4、HandlerAdapter 处理器适配器

通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

调用后端控制器(Handler),拿到后端控制器返回的结果 ModelAndView 后将结果返回给前端控制器 DispatcherServlet

5、View Resolver 视图解析器

View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名
即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。

主要负责将从 DispatcherServlet 中拿到的 ModelAndView 对象进行解析,生成View对象返回给 DispatcherServlet

1.2、Spring MVC 的执行流程

执行流程图

image-20210327203451695

  • 客户端浏览器向前端控制器(DispatcherServlet)发出请求。

  • DispatcherServlet接收到请求后,调用处理器映射器(HandlerMapping)。

  • HandlerMapping 根据请求的 url 查找对应的处理器(Handler,也称后端控制器),返回处理器对象(Handler),并且如果有处理器拦截器(HandlerInterceptor)的话,会将处理器对象(Handler)和处理器拦截器对象(HandlerInterceptor)一并返回给DispatcherServlet

  • DispatcherServlet拿到这些信息后,会调用处理器适配器(HandlerAdapter),HandlerAdapter会调用Handler,Handler执行处理DispatcherServlet发来的请求,生成ModelAndView对象返回给HandlerAdapter

  • HandlerAdapterModelAndView对象返回给DispatcherServlet

  • DispatcherServlet在拿到ModelAndView对象之后,将ModelAndView对象发给视图解析器(ViewResolver)。

  • ViewResolverModelAndView对象进行解析,生成View对象,将View对象返回给DispatcherServlet

  • DispatcherServlet拿到View对象,对jsp页面进行渲染(将模型数据填充到视图中),将渲染后的页面呈现给用户。

二、Mybatis回顾

2.1、Mybatis的缓存机制

1、概述

  • Mybatis 包含了一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
  • Mybatis系统中默认定义了两级缓存

2、一级缓存和二级缓存

  • 默认情况下,只有一级缓存(SqlSession级别地缓存,也称为本地缓存)开启。
  • 二级缓存(全局缓存)需要手动开启和配置,是基于 namespace 级别的缓存
  • 为了提高扩展性。Mybatis 定义了缓存接口 Cache 。我们可以通过实现 Cache 接口来自定义二级缓存

2.2、一级缓存

1、说明

与数据库同一次会话期间查询到的数据会放在本地缓存中,以后如果需要获取相同的数据,直接从缓存中获取。

2、一级缓存失效的四种情况

  • SqlSession不同
  • SqlSession相同,但查询条件不同
  • SqlSession相同,但两次查询期间执行了增删改操作
  • SqlSession相同,但手动清除了一级缓存

使用以下语句可以清除缓存

1
openSession.clearCache();

3、缓存原理

一级缓存实际上是一个Map,定义如下:

1
private Map<Object, Object> cache = new HashMap<Object, Object>();

2.3、二级缓存

1、说明

之所以称之为“二级缓存”,是相对于“一级缓存”而言的。既然有了一级缓存,那么为什么要提供二级缓存呢?我们知道,在一级缓存中,不同session进行相同SQL查询的时候,是查询两次数据库的。显然这是一种浪费,既然SQL查询相同,就没有必要再次查库了,直接利用缓存数据即可,这种思想就是MyBatis二级缓存的初衷。

另外,Spring和MyBatis整合时,每次查询之后都要进行关闭sqlsession,关闭之后数据被清空。所以MyBatis和Spring整合之后,一级缓存是没有意义的。如果开启二级缓存,关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到mapper namespace的二级缓存中。这样,缓存在sqlsession关闭之后依然存在。

二级缓存是Mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

  1. 一个会话,查询一条数据,这个数据就会被存放在当前会话的一级缓存中;
  2. 如果会话关闭,一级缓存中的数据就会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存

2、使用

  • 开启全局二级缓存配置,在mybatis全局配置文件中添加以下配置
1
<setting name="cacheEnabled" value="true" />
  • mapper.xml 中配置使用二级缓存
1
2
3
4
5
6
<cache
eviction=""
flushInterval=""
size=""
readOnly=""
type=""/>
  1. 在cache标签中,eviction属性用于配置缓存的回收策略
  • LRU - 最近最少使用的,移除最长时间不被使用的对象。
  • FIFO - 先进先出,按对象进入缓存的顺序来移除它们。
  • SOFT - 软引用,基于垃圾回收器状态和软引用规则移除对象
  • WEAK - 弱引用,更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认清除策略为 LRU

  1. flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
  2. size(引用数目)属性可以被设置为任意正整数,要注意缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
  3. readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
  4. type 用于指定缓存的实现类型, 默认是PERPETUAL, 对应的是 mybatis 本身的缓存实现类 org.apache.ibatis.cache.impl.PerpetualCache
  • 后续如果我们要实现自己的缓存或者使用第三方的缓存, 都需要更改此处。
  • 我们的 POJO 需要实现序列化接口

3、示意图

image-20210327215317108

2.4、自定义缓存并整合 ehCache

在实现 Cache 接口的自定义缓存类的 put 方法中,可以使其向Redis或者其他 NoSQL 中存储数据,在自定义缓存类的 getObject 方法中,让其从 Redis 中取数据。

1、导入 ehCache 所需依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.8</version>
</dependency>

<!-- 以下是ehCache所需的日志依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
<scope>test</scope>
</dependency>

2、下载mybatisehcache的适配包

这里通过 Maven 方式引入

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.3</version>
</dependency>

3、编写 ehCache.xml 配置文件

  • 属性说明:
  1. diskStore:当内存中不够存储时,存储到指定数据在磁盘中的存储位置。
  2. 必要属性
  • maxElementsInMemory - 在内存中缓存的element的最大数目
  • maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
  • eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSecondstimeToLiveSeconds判断
  • overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
  1. 可选属性
  • timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
  • timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="" />

<defaultCache
maxElementsInMemory="1"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>

4、在mapper.xml中使用自定义缓存

1
2
3
4
5
6
7
8
9
10

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hzx.mybatis.dao.EmployeeMapper">

<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
...
</mapper>

5、其他Mapper使用该mapper的缓存

1
<cache-ref namespace="com.hzx.mybatis.dao.EmployeeMapper"/>

2.5、实体类和表中字段名不一致的解决方案

  • 写SQL时起别名
  • 在全局配置文件中开启驼峰命名规则
  • 使用resultMap来自定义映射规则