1. 程式人生 > >springboot和redis處理頁面快取

springboot和redis處理頁面快取

      頁面快取是應對高併發的一個比較常見的方案,當請求頁面的時候,會先查詢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 ('5', '誅仙', '仙俠', '/img/zhuxian.jpg', '該小說以“天地不仁,以萬物為芻狗”為主題,講述了青雲山下的普通少年張小凡的成長經歷以及與兩位奇女子悽美的愛情故事,整部小說構思巧妙、氣勢恢巨集,開啟了一個獨具魅力的東方仙俠傳奇架空世界,情節跌宕起伏,人物性格鮮明,將愛情、親情、友情與波瀾壯闊的正邪搏鬥、命運交戰彙集在一起,文筆優美,故事生動。它與小說《飄邈之旅》、《小兵傳奇》並稱為“網路三大奇書”,又被稱為“後金庸時代的武俠聖經”。', '蕭鼎');
INSERT INTO `t_novel` VALUES ('6', '英雄志', '武俠', '/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

          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;
}
}