说明
上节课我们已经将前台页面公共部分替换成了动态的,但这个时候就有问题了,因为我们每次访问其它页面的时候都会去查多次数据库,这样会加大数据库的压力,也会拖慢我们网站的速度,因为我们知道一个程序的性能瓶颈大部分情况下是由IO性能决定的,而内存的性能比磁盘快了不知多少倍,所以我们这节课会为我们的项目加上一个缓存
缓存处理
我们项目使用的缓存是ehcache
这个缓存中间件,可以看下百度词条对其的描述https://baike.baidu.com/item/ehcache/6036099?fr=aladdin
我们可以利用springboot-cache
帮我们集成ehcache
,这样我们只用简单的做几个配置就可以了
准备工作
- 引入
springboot-cache
的依赖及ehcache
的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
- 在
application.yml
中配置springboot-cache
让其使用ehcache
,并指定ehcache
的配置文件路径
cache:
type: ehcache
ehcache:
config: classpath:/ehcache-spring.xml
- 创建
ehcache
的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false">
<!-- 磁盘缓存位置 -->
<diskStore path="./cache"/>
<!-- maxElementsInMemory: 在内存中缓存的element的最大数目。-->
<!-- eternal:elements是否永久有效,如果为true,timeouts将被忽略,element将永不过期 -->
<!-- timeToIdleSeconds:发呆秒数,发呆期间未访问缓存立即过期,当eternal为false时,这个属性才有效,0为不限制 -->
<!-- timeToLiveSeconds:总存活秒数,当eternal为false时,这个属性才有效,0为不限制 -->
<!-- overflowToDisk: 如果内存中数据超过内存限制,是否要缓存到磁盘上 -->
<!-- statistics:是否收集统计信息。如果需要监控缓存使用情况,应该打开这个选项。默认为关闭(统计会影响性能)。设置statistics="true"开启统计 -->
<!--
默认缓存
无过期时间,但 3600 秒内无人访问缓存立即过期
-->
<cache name="blog-cache"
maxElementsInMemory="1000"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="true">
</cache>
</ehcache>
自定义缓存Key的生成策略
spring cache
缓存的key
默认是通过KeyGenerator
生成的,其默认生成策略如下
- 如果方法没有参数,则使用0作为
key
- 如果只有一个参数的话则使用该参数作为
key
- 如果参数多于一个的话则使用所有参数的
hashCode
作为key
可以看出默认的key生成策略中并没有涉及方法名称和类,这就意味着如果我们有两个参数列表相同的方法,我们用相同的参数分别调用两个方法,当调用第二个方法的时候,spring cache将会返回缓存中的第一个方法的缓存值,因为他们的key是一样的
- 我们可以去自定一个
CacheConfig
的类用作缓存的配置,继承一下CachingConfigurerSupport
类,覆写它的keyGenerator
方法 - 然后我们去定义一个
BlogCacheKey
的内部类,把调用缓存的类名、方法名、参数列表传过来,在这个类里覆写一下它的equals
方法,综合考虑上述参数完成key的定义
package cn.kevinlu98.config;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.util.StringUtils;
import java.util.Arrays;
/**
* Author: Mr丶冷文
* Date: 2022/10/11 16:37
* Email: kevinlu98@qq.com
* Description:
*/
@Configuration
public class CacheConfig extends CachingConfigurerSupport {
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> new BlogCacheKey(target.getClass().getName(),method.getName(),params);
}
static class BlogCacheKey {
private final String className;
private final String methodName;
private final Object[] params;
private final int hashCode;
public BlogCacheKey(String className, String methodName, Object[] params) {
this.className = className;
this.methodName = methodName;
this.params = params;
String sign = className + "_" + methodName + "_" + Arrays.deepHashCode(params);
this.hashCode = sign.hashCode();
}
@Override
public int hashCode() {
return this.hashCode;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (!(obj instanceof BlogCacheKey)) return false;
BlogCacheKey other = (BlogCacheKey) obj;
if (this.hashCode == other.hashCode) return true;
return StringUtils.equals(this.className, other.className)
&& StringUtils.equals(this.methodName, other.methodName)
&& Arrays.deepEquals(this.params, other.params);
}
}
}
启用缓存
- 在启动类上加上
EnableCaching
的注解开启缓存 - 给我们要缓存的对象对应类实现下
Serializable
接口 - 我们在查询友情链接及导航栏还有轮播图这里先加上缓存注解
CacheConfig
,然后在需要加缓存的方法上加上Cacheabel
- 在更新及删除操作时清空缓存
CacheEvict
package cn.kevinlu98.service;
import cn.kevinlu98.mapper.FriendlyMapper;
import cn.kevinlu98.pojo.Friendly;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Author: Mr丶冷文
* Date: 2022/10/6 22:20
* Email: kevinlu98@qq.com
* Description:
*/
@CacheConfig(cacheNames = {"blog-cache"})
@Service
public class FriendlyService {
private final FriendlyMapper mapper;
public FriendlyService(FriendlyMapper mapper) {
this.mapper = mapper;
}
/**
* 查询所有的友情链接
*
* @return 友情链接列表
*/
@Cacheable
public List<Friendly> list() {
return mapper.findAll();
}
@CacheEvict(allEntries = true)
public void save(Friendly friendly) {
mapper.save(friendly);
}
/**
* 根据id从数据库表中删除数据
*
* @param id 主键
*/
@CacheEvict(allEntries = true)
public void delete(Integer id) {
mapper.deleteById(id);
}
}
首页文章列表的展示
- 给
index
方法加上pn
的参数用于标识是分页 - 我们可以直接调用之前我们写的
search
方法来进行搜索
@GetMapping("/")
public String index(@RequestParam(required = false, defaultValue = "1") Integer pn, Model model) {
pn = pn < 1 ? 1 : pn;
model.addAttribute("articlePage", articleService.search(ArticleSearch.builder().pageSize(2).pageNum(pn).status(Article.STATUS_PUBLISH).type(Article.TYPE_ARTICLE).build()));
model.addAttribute("banners", bannerService.list());
return "index";
}
- 补充评论数
// commentMapper
long countByArticle(Article article);
//search
return PageHelper.<Article>builder()
.rows(articlePage.getContent().stream().peek(x -> x.setCommentCount(commentMapper.countByArticle(x))).collect(Collectors.toList()))
.total(articlePage.getTotalElements())
.current(search.getPageNum())
.totalPage(articlePage.getTotalPages())
.build();
- 首页文章列表展示
<article th:each="article:${articlePage.rows}" class="lw-article-item lw-posr">
<div class="lw-article-cover lw-posa lw-xs-hidden">
<img th:src="${@defaultImage.cover(article.cover)}" alt="">
</div>
<div class="lw-article-info">
<h2>
<a class="lw-xs-hidden"><span class="lw-category" th:text="${article.category.name}"></span></a>
<a th:href="@{/{id}.html(id=${article.id})}" th:text="${article.title}"></a>
</h2>
<p class="lw-desc" th:text="${article.summary()}"></p>
<p class="lw-text-hidden lw-article-more">
<i class="fa fa-clock-o lw-mr5"></i> <th:block th:text="${#dates.format(article.created,'yyyy-MM-dd HH:mm')}"></th:block>
<i class="fa fa-eye lw-mr5 lw-ml10"></i><th:block th:text="${article.views}"></th:block>
<i class="fa fa-comment lw-ml10 lw-mr5"></i><th:block th:text="${article.commentCount}"></th:block>
</p>
</div>
</article>
- 分页部分处理
<ul th:if="${articlePage.totalPage>1}" class="lw-pagenation">
<li th:if="${articlePage.current != 1}"><a th:href="@{/}">首页</a></li>
<li th:each="num:${#numbers.sequence(1,articlePage.totalPage)}">
<a th:class="${articlePage.current eq num}?'lw-active':''" th:href="@{/(pn=${num})}" th:text="${num}"></a>
</li>
<li th:if="${articlePage.current != articlePage.totalPage}">
<a th:href="@{/(pn=${articlePage.totalPage})}">尾页</a>
</li>
</ul>
看看
多谢大佬分享
强强强
感谢作者!
感谢!