页面缓存是应对高并发的一个比较常见的方案,当请求页面的时候,会先查询redis缓存中是否存在,若存在则直接从缓存中返回页面,否则会通过代码逻辑去渲染页面,并将渲染后的页面缓存到redis中,然后返回。下面通过简单的demo来描述这一过程:

 一、准备工作:

1、新建一个springboot工程,命名为novel,添加如下依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.dtouding</groupId>
<artifactId>novel</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>novel</name>
<description>Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency> <!--mysql connector-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency> <!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency> <!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency> <!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

pom.xml

2、创建一个novel表,并插入几条数据,如下:

DROP TABLE IF EXISTS `t_novel`;
CREATE TABLE `t_novel` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '小说ID',
`novel_name` varchar(16) DEFAULT NULL COMMENT '小说名称',
`novel_category` varchar(64) DEFAULT NULL COMMENT '小说类别',
`novel_img` varchar(64) DEFAULT NULL COMMENT '小说图片',
`novel_summary` longtext COMMENT '小说简介',
`novel_author` varchar(16) DEFAULT NULL COMMENT '小说作者',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4;
INSERT INTO `t_novel` VALUES ('', '诛仙', '仙侠', '/img/zhuxian.jpg', '该小说以“天地不仁,以万物为刍狗”为主题,讲述了青云山下的普通少年张小凡的成长经历以及与两位奇女子凄美的爱情故事,整部小说构思巧妙、气势恢宏,开启了一个独具魅力的东方仙侠传奇架空世界,情节跌宕起伏,人物性格鲜明,将爱情、亲情、友情与波澜壮阔的正邪搏斗、命运交战汇集在一起,文笔优美,故事生动。它与小说《飘邈之旅》、《小兵传奇》并称为“网络三大奇书”,又被称为“后金庸时代的武侠圣经”。', '萧鼎');
INSERT INTO `t_novel` VALUES ('', '英雄志', '武侠', '/img/yingxiongzhi.jpg', '《英雄志》为一虚构中国明朝历史的古典小说,借用明英宗土木堡之变为背景,以复辟为舞台,写尽了英雄们与时代间的相互激荡,造反与政变、背叛与殉道……书中无人不可以为英雄,贩夫走卒、市井小民、娼妇与公主、乞丐与皇帝,莫不可以为英雄。孙晓一次又一次建立英雄的面貌,又一次一次拆解英雄的形象。于穷途末路之时的回眸一笑,是孙晓笔下的安慰与沧桑。', '孙晓');

t_novel

3、在application.yml文件中配置数据库连接信息和redis连接信息:

spring:
##thymeleaf.#
thymeleaf:
##默认前缀
prefix: classpath:/templates/
##默认后缀
suffix: .html
cache: false
servlet:
content-type: text/html
enabled: true
encoding: UTF-8
mode: HTML5 ##datasource.#
datasource:
url: jdbc:mysql://localhost:3306/novel?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
tomcat:
max-active: 2
max-wait: 60000
initial-size: 1
min-idle: 1
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: select 'dtouding'
test-while-idle: true
test-on-borrow: false
test-on-return: false ##mybatis.#
mybatis:
type-aliases-package: com.dtouding.novel.domain
configuration:
map-underscore-to-camel-case: true
default-fetch-size: 100
default-statement-timeout: 30
mapper-locations: classpath:com/dtouding/novel/dao/*.xml redis:
host: 127.0.0.1
port: 6379
timeout: 3
password: 123456
poolMaxTotal: 10
poolMaxIdle: 10
poolMaxWait: 3

application.yml

4、做一个简单的查询小说列表的功能,编写dao、service、controller、html:

public class Novel {

    private Long id;

    private String novelName;

    private String novelCategory;

    private String novelImg;

    private String novelSummary;

    private String novelAuthor;

    public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public String getNovelName() {
return novelName;
} public void setNovelName(String novelName) {
this.novelName = novelName;
} public String getNovelCategory() {
return novelCategory;
} public void setNovelCategory(String novelCategory) {
this.novelCategory = novelCategory;
} public String getNovelImg() {
return novelImg;
} public void setNovelImg(String novelImg) {
this.novelImg = novelImg;
} public String getNovelSummary() {
return novelSummary;
} public void setNovelSummary(String novelSummary) {
this.novelSummary = novelSummary;
} public String getNovelAuthor() {
return novelAuthor;
} public void setNovelAuthor(String novelAuthor) {
this.novelAuthor = novelAuthor;
}
}

Novel

@Mapper
public interface NovelDao { @Select("select * from t_novel")
List<Novel> list(); }

NovelDao

@Service
public class NovelService { @Resource
private NovelDao novelDao; public List<Novel> list() {
return novelDao.list();
}
}

NovelService

@Controller
@RequestMapping(value = "/novel")
public class NovelController { @Autowired
private NovelService novelService; @RequestMapping(value = "/list", method = RequestMethod.GET)
public String list(Model model) {
List<Novel> list = novelService.list();
model.addAttribute("novelList", list);
return "novel_list";
}
}

NovelController

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>小说列表</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- jquery -->
<script type="text/javascript" th:src="@{/js/jquery.min.js}"></script>
<!-- bootstrap -->
<link rel="stylesheet" type="text/css" th:href="@{/bootstrap/css/bootstrap.min.css}" />
<script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script>
</head>
<body> <div class="panel panel-default">
<div class="panel-heading">小说列表</div>
<table class="table" id="goodslist">
<tr><td>小说名称</td><td>小说图片</td><td>小说类别</td><td>小说作者</td><td>小说简介</td>
<tr th:each="novel : ${novelList}">
<td th:text="${novel.novelName}"></td>
<td ><img th:src="@{${novel.novelImg}}" width="100" height="100" /></td>
<td th:text="${novel.novelCategory}"></td>
<td th:text="${novel.novelAuthor}"></td>
<td th:text="${novel.novelSummary}"></td>
</tr>
</table>
</div>
</body>
</html>

novel_list

5、通过http://localhost:8080/novel/list,可访问。

二、缓存novel_list页面

1、引入redis依赖:

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

2、编写redis配置类和通用的redis工具类:

@Component
@ConfigurationProperties(prefix = "redis")
public class RedisConfig { private String host;
private int port;
private int timeout;//秒
private String password;
private int poolMaxTotal;
private int poolMaxIdle;
private int poolMaxWait;//秒 public String getHost() {
return host;
} public void setHost(String host) {
this.host = host;
} public int getPort() {
return port;
} public void setPort(int port) {
this.port = port;
} public int getTimeout() {
return timeout;
} public void setTimeout(int timeout) {
this.timeout = timeout;
} public String getPassword() {
return password;
} public void setPassword(String password) {
this.password = password;
} public int getPoolMaxTotal() {
return poolMaxTotal;
} public void setPoolMaxTotal(int poolMaxTotal) {
this.poolMaxTotal = poolMaxTotal;
} public int getPoolMaxIdle() {
return poolMaxIdle;
} public void setPoolMaxIdle(int poolMaxIdle) {
this.poolMaxIdle = poolMaxIdle;
} public int getPoolMaxWait() {
return poolMaxWait;
} public void setPoolMaxWait(int poolMaxWait) {
this.poolMaxWait = poolMaxWait;
} @Bean
public JedisPool jedisPoolFactory() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(poolMaxIdle);
jedisPoolConfig.setMaxTotal(poolMaxTotal);
jedisPoolConfig.setMaxWaitMillis(poolMaxWait * 1000);
JedisPool jedisPool = new JedisPool(jedisPoolConfig,
host,
port,
timeout * 1000,
password);
return jedisPool;
}
}

RedisConfig

@Service
public class RedisService { @Autowired
private JedisPool jedisPool; /**
* 获取存储对象
* @param key
* @param clazz
* @param <T>
* @return
*/
public <T> T get(String key, Class<T> clazz) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String str = jedis.get(key);
T t = stringToBean(str, clazz);
return t;
} finally {
returnToPool(jedis);
}
} /**
* 设置对象
* @param key
* @param expireSeconds
* @param value
* @param <T>
* @return
*/
public <T> boolean set(String key, int expireSeconds, T value) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String str = beanToString(value);
if (null == str) {
return false;
}
if (expireSeconds <= 0) {
jedis.set(key, str);
} else {
jedis.setex(key, expireSeconds, str);
}
return true;
} finally {
returnToPool(jedis);
}
} /**
* 判断key是否存在
* */
public <T> boolean exists(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.exists(key);
}finally {
returnToPool(jedis);
}
} /**
* 增加值
* */
public <T> Long incr(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.incr(key);
}finally {
returnToPool(jedis);
}
} /**
* 减少值
* */
public <T> Long decr(String key) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
return jedis.decr(key);
}finally {
returnToPool(jedis);
}
} private <T> String beanToString(T value) {
if (null == value) {
return null;
}
if (value instanceof Integer || value instanceof Long) {
return "" + value;
} else if (value instanceof String) {
return (String) value;
} else {
return JSON.toJSONString(value);
}
} private <T> T stringToBean(String str, Class<T> clazz) {
if (StringUtils.isEmpty(str) || clazz==null) {
return null;
}
if (clazz==int.class || clazz==Integer.class) {
return (T) Integer.valueOf(str);
} else if (clazz == String.class) {
return (T) str;
} else if (clazz==long.class || clazz==Long.class) {
return (T)Long.valueOf(str);
} else {
return JSON.toJavaObject(JSON.parseObject(str), clazz);
}
} private void returnToPool(Jedis jedis) {
if (null != jedis) {
jedis.close();
}
}
}

RedisService

3、改写NovelController中的list方法,添加页面缓存逻辑,具体包括:

1)、在list方法上添加@ResponseBody注解,并修改返回类型为text/html,可以避免返回的html再次被渲染,因为缓存在redis中的页面是通过代码手工渲染的。

2)、判断redis中是否有novel_list的页面缓存,若有,则直接返回该缓存页面:

String html = redisService.get(NovelRedisKeys.NOVEL_LIST_PAGE, String.class);
if (!StringUtils.isEmpty(html)) {
return html;
}

3、若缓存中没有,则借助ThymeleafViewResolver去渲染html页面:

html = thymeleafViewResolver.getTemplateEngine().process("novel_list", webContext);

4、将渲染后的页面缓存到redis中:

if (!StringUtils.isEmpty(html)) {
//将渲染后的页面缓存到redis中
redisService.set(NovelRedisKeys.NOVEL_LIST_PAGE, 60, html);
}

4、修改后的完整代码如下:

@Controller
@RequestMapping(value = "/novel")
public class NovelController { @Autowired
private NovelService novelService; @Autowired
private RedisService redisService; @Autowired
private ThymeleafViewResolver thymeleafViewResolver; @RequestMapping(value = "/list", method = RequestMethod.GET)
@ResponseBody
public String list(HttpServletRequest request, HttpServletResponse response, Model model) {
//判断redis是否有缓存
String html = redisService.get(NovelRedisKeys.NOVEL_LIST_PAGE, String.class);
if (!StringUtils.isEmpty(html)) {
return html;
}
List<Novel> list = novelService.list();
model.addAttribute("novelList", list);
WebContext webContext = new WebContext(request,
response,
request.getServletContext(),
request.getLocale(),
model.asMap());
//渲染页面
html = thymeleafViewResolver.getTemplateEngine().process("novel_list", webContext);
if (!StringUtils.isEmpty(html)) {
//将渲染后的页面缓存到redis中
redisService.set(NovelRedisKeys.NOVEL_LIST_PAGE, 60, html);
}
return html;
}
}

最新文章

  1. 【JS】heatmap.js v1.0 到 v2.0,详细总结一下:)
  2. hadoop学习笔记:zookeeper学习(上)
  3. 附12 grafana配置文件
  4. 如何判断CPU字节序之[Big-endian vs Little-endian]
  5. 细雨学习笔记:JMeter 的主要测试组件总结
  6. org.apache.common.io-FileUtils详解
  7. 添加MIME类型
  8. 【VMware虚拟机】【克隆问题】在VMware 9.0下克隆CentOS6.5虚拟机无法识别eth网卡
  9. slabs.c
  10. js 判断时间,满足执行框架
  11. 我工作这几年(五)-- Android学习4.5月总结(一)
  12. 在基于阿里云serverCentOS6.5下安装Subversion 1.6.5服务
  13. 【学习】Zepto与jQuery 差别
  14. thinkphp使用PHPMailer发送邮件
  15. sigmoid_cross_entropy_with_logits
  16. [Poi2012]A Horrible Poem BZOJ2795
  17. Vue通过路由 query传递参数
  18. 首页技术支持常见问题宽带外网IP显示为10、100、172开头,没有公网IP,如何解决?
  19. MT【307】周期数列
  20. windows清空电脑的DNS缓存

热门文章

  1. iOS长按控件
  2. BZOJ 4815 [Cqoi2017]小Q的表格 ——欧拉函数
  3. USACO Hamming Codes
  4. BZOJ3532 [Sdoi2014]Lis 【网络流退流】
  5. 【CCF】棋局评估
  6. bzoj2748 [HAOI2012]音量调节 背包
  7. Python脚本实现单据体首行过滤
  8. toolbarlite随笔之插件的闭包写法
  9. IntelliJ IDEA常用的快捷键(代码提示/注释代码/加入类注释和方法注释Javadoc)
  10. 【Todo】Java Callable和Future学习