1. 程式人生 > 實用技巧 >SpringBoot與快取

SpringBoot與快取

SpringBoot與快取

1、JSR107 快取規範

1.1、Java Caching定義了5個核心介面,分別是CachingProvider, CacheManager, Cache, Entry和 Expiry.

  • CachingProvider定義子建立、配置、獲取、管理和控制多個CacheManager,一個應用程式可以在執行期訪問多個CachingProvider
  • CacheManager定義了建立、配置,獲取、管理和控制多個唯一命名的Cache,這些Cache存在於CacheManager的上下文中。一個CacheManager僅被一個CachingProvider所擁有。
  • Cache是一個類似Map的資料結構並臨時儲存以Key為索引的值。一個Cache僅被一個CacheManager所擁有。
  • Entry是一個儲存在Cache中的key-value對。
  • Expiry每一個儲存在Cache中的條目有一個定義的有效期。一旦超過這個時間,條目為過期的狀態。一旦過期,條目將不可訪問、更新和刪除。快取有效期可以通過ExpiryPolicy設定。

2、Spring 快取抽象

2.1、Spring從3.1開始定義了org.springframework.cache.Cache,和org.springframework.cache.CacheManager介面來統一不同的快取技術;並支援使用JCache (JSR-107)

  • Cache介面為快取的元件規範定義,包含快取的各種操作集合;
  • Cache介面下Spring提供了各種xxxCache的實現;如RedisCache,EhCacheCache ,ConcurrentMapCache等;
  • 每次呼叫需要快取功能的方法時,Spring會檢查檢查指定引數的指定的目標方法是否已經被呼叫過;如果有就直接從快取中獲取方法呼叫後的結果,如果沒有就呼叫方法並快取結果後返回給使用者。下次呼叫直接從快取中獲取。
  • 已經被呼叫過;如果有就直接從快取中獲取方法呼叫後的結果,如果沒有就呼叫方法並快取結果後返回給使用者。下次呼叫直接從快取中獲取。
    • 確定方法需要被快取以及他們的快取策略
    • 從快取中讀取之前快取儲存的資料

2.2 、重要的快取註解

Cache 快取介面,定義快取操作。實現有:RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager 愛存管理器,管理各種快取(Cache)元件
@Cacheable 主要針對方法配置,能夠根據方法的請求引數對其結果進行快取
@CacheEvict @CacheEvict
@CachePut 保證方法被呼叫,又希望結果被快取。
@EnableCaching 開啟基於註解的快取
keyGenerator 快取資料時key生成策略
serialize 快取資料時value序列化策略

3、建立專案(使用預設的快取)

3.1、pom

<?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">
    <parent>
        <artifactId>Cloud</artifactId>
        <groupId>com.riest</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>SpringBoot-Cache</artifactId>
    <dependencies>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

	<!--一以下配置用來執行mybatis-generator-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.2</version>
                <configuration>
                    <configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                </configuration>
                <executions>
                    <execution>
                        <id>Generate MyBatis Artifacts</id>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.3.2</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

</project>

3.2、yml

資料庫用的postgresql

server:
  port: 9090


spring:
  application:
    name: SpringBootCache

  datasource:
    url: jdbc:postgresql://xxx:xxx/xxx?useUnicode=true&characterEncoding=utf8&useSSL=true
    username: xxx
    password: xxx
    driver-class-name: org.postgresql.Driver
    druid:
      # 連線池配置
      initial-size: 1
      max-active: 20
      min-idle: 1
      max-wait: 10000
      pool-prepared-statements: true
      max-open-prepared-statements: 20
      validation-query: SELECT 'x'
      validation-query-timeout: 5000
      test-on-borrow: false
      test-on-return: false
      test-while-idle: true
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 30000
      max-evictable-idle-time-millis: 60000
      removeAbandoned: true
      removeAbandonedTimeout: 1800
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      max-pool-prepared-statement-per-connection-size: 20
      filters: stat,wall #filters: #配置多個英文逗號分隔(統計,sql注入,log4j過濾)
    type: com.alibaba.druid.pool.DruidDataSource


# mybatis 配置
mybatis:
  mapper-locations: classpath:mapper/*/*.xml
  type-aliases-package: com.riest.modal
  configuration:
    #mabatis列印sql
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
debug: true

3.3、druid配置

package com.riest.config.druid;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.ResourceServlet;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * ClassName:DruidConf
 * Describe:
 * Author:DGJ
 * Data:2020/11/23 14:34
 */
@Slf4j
@Configuration
public class DruidConf {

//    @Value("${spring.datasource.publicKey}")
//    private String publicKey;


    @Value("${spring.datasource.url}")
    private String dbUrl;

    @Value("${spring.datasource.username}")
    private String username;

    @Value("${spring.datasource.password}")
    private String password;

    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;

    @Value("${spring.datasource.druid.initial-size}")
    private int initialSize;

    @Value("${spring.datasource.druid.min-idle}")
    private int minIdle;

    @Value("${spring.datasource.druid.max-active}")
    private int maxActive;

    @Value("${spring.datasource.druid.max-wait}")
    private int maxWait;

    @Value("${spring.datasource.druid.time-between-eviction-runs-millis}")
    private int timeBetweenEvictionRunsMillis;

    @Value("${spring.datasource.druid.min-evictable-idle-time-millis}")
    private int minEvictableIdleTimeMillis;

    @Value("${spring.datasource.druid.validation-query}")
    private String validationQuery;

    @Value("${spring.datasource.druid.test-while-idle}")
    private boolean testWhileIdle;

    @Value("${spring.datasource.druid.test-on-borrow}")
    private boolean testOnBorrow;

    @Value("${spring.datasource.druid.test-on-return}")
    private boolean testOnReturn;

    @Value("${spring.datasource.druid.pool-prepared-statements}")
    private boolean poolPreparedStatements;

    @Value("${spring.datasource.druid.max-pool-prepared-statement-per-connection-size}")
    private int maxPoolPreparedStatementPerConnectionSize;

    @Value("${spring.datasource.druid.filters}")
    private String filters;

    @Value("{spring.datasource.druid.connection-properties}")
    private String connectionProperties;


    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() throws SQLException {
        DruidDataSource datasource = new DruidDataSource();
//        datasource.setFilters("config");
//        datasource.setConnectionProperties("config.decrypt=true;config.decrypt.key=" + publicKey);

//        Properties conkey = new Properties();
//        conkey.setProperty("config.decrypt","true");
//        conkey.setProperty("config.decrypt.key",publicKey);

        datasource.setUrl(dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);
        //configuration
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setValidationQuery(validationQuery);
        datasource.setTestWhileIdle(testWhileIdle);
        datasource.setTestOnBorrow(testOnBorrow);
        datasource.setTestOnReturn(testOnReturn);
        datasource.setPoolPreparedStatements(poolPreparedStatements);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);

        try {
            datasource.setFilters(filters);
        } catch (Exception e) {
            log.error("druid configuration initialization filter", e);
        }
        datasource.setConnectionProperties(connectionProperties);

        return datasource;
    }

        /**
         * 主要實現web監控的配置處理
         * @return
         */
    @Bean
    @ConditionalOnMissingBean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        //白名單:
        servletRegistrationBean.addInitParameter("allow","127.0.0.1");
        //IP黑名單 (存在共同時,deny優先於allow) : 如果滿足deny的話提示:Sorry, you are not permitted to view this page.
        servletRegistrationBean.addInitParameter("deny","192.168.6.73");
        //登入檢視資訊的賬號密碼, 用於登入Druid監控後臺
        servletRegistrationBean.addInitParameter("loginUsername", "admin");
        servletRegistrationBean.addInitParameter("loginPassword", "admin");
        //是否能夠重置資料.
        servletRegistrationBean.addInitParameter("resetEnable", "true");
        return servletRegistrationBean;
    }

    /**
     *  監控
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }

}

3.4、用mybatis-generator 生成sql、mapper、modal

該步驟省略

3.5、Service

package com.riest.service.device;

import com.riest.dao.device.PowerControllerdeviceMapper;
import com.riest.model.device.PowerControllerdevice;
import com.riest.model.device.PowerControllerdeviceAll;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sun.awt.SunHints;


import javax.annotation.Resource;
import java.util.List;

/**
 * ClassName:TermService
 * Describe:
 * Author:DGJ
 * Data:2020/11/6 17:01
 *
 * 預設使用的是 ConcurrentMapCacheManager ,將資料儲存在  ConcurrentMap<String, Cache>
 */
// 公共的快取配置,有了該配置,下面方法上的value就可以省略
//@CacheConfig(cacheNames = "con")
@Service
public class ConService {

    @Resource
    private PowerControllerdeviceMapper controllerdeviceDao;


    /**將方法的執行結果快取
     *
     * CacheManager 管理多個Cache快取元件,對快取的操作在其元件之中,每一個快取元件都有一個自己的名字
     * @Cacheable
     *  屬性:
     *      value/cacheNames:指定快取元件的名字
     *      key:快取資料的key 預設用方法引數的值
     *      keyGenerator:key的生成器,key/keyGenerator 2選1
     *      cacheManager:快取管理器
     *      cacheResolver:快取解析器  cacheManager/cacheResolver 2選1
     *      condition:符合條件的情況下進行快取
     *      unless:指定的條件 true,方法的返回值不快取,可以獲取到結果進行判斷
     *      sync:非同步模式
     *
     * @param
     * @return
     */
    @Cacheable(cacheNames = {"con"})
    public PowerControllerdevice GetSingleData(Long condevid){
        return controllerdeviceDao.selectByPrimaryKey(condevid);

    }

    /**
     *  @Caching:配置複雜的快取規則
     *
     * @param data
     * @return
     */
    @Caching(
            cacheable = {
                 @Cacheable(value = "con",key = "#data.conname")
            },
            put = {
                    @CachePut(value = "con",key = "#data.condevid"),
                    @CachePut(value = "con",key = "#data.condevsn"),
            }
    )
    public  List<PowerControllerdevice> Query(PowerControllerdevice data){
        PowerControllerdeviceAll rundata = new PowerControllerdeviceAll();
        PowerControllerdeviceAll.Criteria cri = rundata.createCriteria();
        List<PowerControllerdevice> powerControllerdevices = controllerdeviceDao.selectByExample(rundata);
        return powerControllerdevices;
    }

    /**getCondevid
     * @CachePut:
     *  先呼叫目標方法,然後將方法的但結果快取
     *  key = "#data.condevid",同步更新快取
     * @param data
     * @return
     * @throws Exception
     */
    @CachePut(value = "con" ,key = "#data.condevid")
    @Transactional(rollbackFor = Exception.class)
    public int Update(PowerControllerdevice data) throws  Exception{
        return controllerdeviceDao.updateByPrimaryKeySelective(data);
    }

    /**
     * @CacheEvict
     * 清除快取,指定key clean
     * allEntries = true,清除快取中的所有資料
     * beforeInvocation = false(預設) 默爾是在方法之後執行,方法出現異常,快取不清除
     *                  true 快取的清除是否在方法之前執行,方法出現異常,快取也會刪除
     * @param data
     * @return
     * @throws Exception
     */
    @CacheEvict(value = "con",key = "#data.condevid")
    @Transactional(rollbackFor = Exception.class)
    public int Delete(PowerControllerdevice data) throws Exception{
        return controllerdeviceDao.deleteByPrimaryKey(data.getCondevid());
    }
}

3.6、controller

package com.riest.controller.device;

import com.riest.model.device.PowerControllerdevice;
import com.riest.service.device.ConService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

/**
 * ClassName:TermController
 * Describe:
 * Author:DGJ
 * Data:2020/11/6 17:00
 */
@RestController
@RequestMapping("/con")
public class ConDevController {

    @Autowired
    private ConService termService;

    @RequestMapping(value = "/getsingledata",method = RequestMethod.POST)
    public PowerControllerdevice GetSingleData(Long  condevid){
        return termService.GetSingleData(condevid);
    }

    @RequestMapping(value = "/select",method = RequestMethod.POST)
    public Object Select(PowerControllerdevice data, HttpServletRequest request, HttpServletResponse response){
        List<PowerControllerdevice> query = termService.Query(data);
        return query;
    }

    @RequestMapping(value = "/update",method = RequestMethod.POST)
    public Object Update(PowerControllerdevice data) throws Exception {
        int update = termService.Update(data);
        return update;
    }

    @RequestMapping(value = "/delete",method = RequestMethod.POST)
    public Object Delete(PowerControllerdevice data) throws Exception {
        return termService.Delete(data);
    }
}

3.7、主啟動

package com.riest;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

/**
 * ClassName:CacheApplication
 * Describe:
 * Author:DGJ
 * Data:2020/11/12 19:30
 */
@SpringBootApplication
// 開啟快取
@EnableCaching
@MapperScan({"mapper","com.riest.dao.*"})
public class CacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(CacheApplication.class,args);
    }
}

3.8 測試該快取走的是預設的快取(測試只測試了一個方法)

postman post請求,可以看到第一次,走資料庫查詢(根據控制檯日誌檢視)

postman post請求,可以看到第二次,走快取查詢(根據控制檯日誌檢視)

4、整合redis

redis不用多說,想必大家都或多或少有了解

redis中文網:http://www.redis.cn/

4.1、docker 安裝redis

	·docker pull redis
	·docker run -d -p 6379:6379 --name myredis redis

4.2、測試redis

4.3、pom

​ 加入reids-start

      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

4.4 、yml(所有配置)

server:
  port: 9090


spring:
  application:
    name: SpringBootCache

  datasource:
    url: jdbc:postgresql://xxx:xxx/xxx?useUnicode=true&characterEncoding=utf8&useSSL=true
    username: xxx
    password: xxx
    driver-class-name: org.postgresql.Driver
    druid:
      # 連線池配置
      initial-size: 1
      max-active: 20
      min-idle: 1
      max-wait: 10000
      pool-prepared-statements: true
      max-open-prepared-statements: 20
      validation-query: SELECT 'x'
      validation-query-timeout: 5000
      test-on-borrow: false
      test-on-return: false
      test-while-idle: true
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 30000
      max-evictable-idle-time-millis: 60000
      removeAbandoned: true
      removeAbandonedTimeout: 1800
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      max-pool-prepared-statement-per-connection-size: 20
      filters: stat,wall #filters: #配置多個英文逗號分隔(統計,sql注入,log4j過濾)
    type: com.alibaba.druid.pool.DruidDataSource


  ## Redis 配置
  redis:
    ## Redis資料庫索引(預設為0)
    database: 1
    ## Redis伺服器地址
    host: xxxxxxxx
    ## Redis伺服器連線埠
    port: 6379
    ## Redis伺服器連線密碼(預設為空)
    password:

# mybatis 配置
mybatis:
  mapper-locations: classpath:mapper/*/*.xml
  type-aliases-package: com.riest.modal
  configuration:
    #mabatis列印sql
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
debug: true

4.5、service

package com.riest.service.device;

import com.riest.dao.device.PowerControllerdeviceMapper;
import com.riest.dao.device.PowerTerminaldeviceMapper;
import com.riest.model.device.PowerControllerdevice;
import com.riest.model.device.PowerControllerdeviceAll;
import com.riest.model.device.PowerTerminaldevice;
import com.riest.model.device.PowerTerminaldeviceAll;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;

/**
 * ClassName:ConService
 * Describe:
 * Author:DGJ
 * Data:2020/11/25 9:34
 */
@Service
public class TermService {

    @Resource
    private PowerTerminaldeviceMapper terminaldeviceDao;

    /**terminaldeviceDao
     *  將查詢的的結果快取
     * @param termdevid
     * @return
     */
    @Cacheable(cacheNames = {"term"},key = "#termdevid")
    public PowerTerminaldevice GetSingleData(Long termdevid){
        return terminaldeviceDao.selectByPrimaryKey(termdevid);
    }

    public List<PowerTerminaldevice> Query(PowerTerminaldevice data){
        PowerTerminaldeviceAll rundata = new PowerTerminaldeviceAll();
        PowerTerminaldeviceAll.Criteria cri = rundata.createCriteria();
        List<PowerTerminaldevice> powerControllerdevices = terminaldeviceDao.selectByExample(rundata);
        return powerControllerdevices;
    }

    /**
     *  將方法的返回值更新到快取
     * @param data
     * @return
     * @throws Exception
     */
    @CachePut(value = "term" ,key = "#data.termdevid")
    @Transactional(rollbackFor = Exception.class)
    public PowerTerminaldevice Update(PowerTerminaldevice data) throws Exception{
        terminaldeviceDao.updateByPrimaryKeySelective(data);
        PowerTerminaldevice terminaldevice = GetSingleData(data.getTermdevid());
        return terminaldevice;

    }

    @Transactional(rollbackFor = Exception.class)
    public int Delete(PowerTerminaldevice data) throws Exception{
        return terminaldeviceDao.deleteByPrimaryKey(data.getCondevid());
    }
}

4.6、controller

package com.riest.controller.device;

import com.riest.model.device.PowerTerminaldevice;
import com.riest.service.device.TermService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

/**
 * ClassName:TermController
 * Describe:
 * Author:DGJ
 * Data:2020/11/6 17:00
 */
@RestController
@RequestMapping(value = "/term")
public class TermController {

    @Autowired
    private TermService termService;

    @Autowired
    private RedisTemplate redisTemplate;


    @RequestMapping(value = "/getsingledata",method = RequestMethod.POST)
    public PowerTerminaldevice GetSingleData(Long termdevid){
        return termService.GetSingleData(termdevid);
    }

    @RequestMapping(value = "/select",method = RequestMethod.POST)
    public Object Select(PowerTerminaldevice data, HttpServletRequest request, HttpServletResponse response){
        List<PowerTerminaldevice> query = termService.Query(data);
        return query;
    }

    /**query
     *  修改資料的同屬修改快取中的資料
     * @param data
     * @return
     * @throws Exception
     */
    @RequestMapping(value = "/update",method = RequestMethod.POST)
    public Object Update(PowerTerminaldevice data) throws Exception {
        PowerTerminaldevice update = termService.Update(data);
        return update;
    }

    @RequestMapping(value = "/delete",method = RequestMethod.POST)
    public Object Delete(PowerTerminaldevice data) throws Exception {
        return termService.Delete(data);

    }
}

4.7、redisconf

package com.riest.config.redis;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * ClassName:RedisConf
 * Describe: 資料序列化
 * Author:DGJ
 * Data:2020/11/25 9:33
 */
@Configuration
public class RedisConf {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;


    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(objectMapper);

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
        return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
    }

}

4.8、redisutils(該utils來自https://www.cnblogs.com/zeng1994/p/03303c805731afc9aa9c60dbbd32a323.html)

package com.riest.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * ClassName:RedisUtils
 * Describe:
 * Author:DGJ
 * Data:2020/11/25 9:52
 */
@Component
public class RedisUtils {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    // =============================common============================

    /**
     * 指定快取失效時間
     *
     * @param key  鍵
     * @param time 時間(秒)
     *             29
     * @return 30
     */


    public boolean expire(String key, long time) {

        try {

            if (time > 0) {

                redisTemplate.expire(key, time, TimeUnit.SECONDS);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }


    /**
     * 根據key 獲取過期時間
     *
     * @param key 鍵 不能為null
     * @return 時間(秒) 返回0代表為永久有效
     * 47
     */


    public long getExpire(String key) {

        return redisTemplate.getExpire(key, TimeUnit.SECONDS);

    }


    /**
     * 判斷key是否存在
     *
     * @param key 鍵
     * @return true 存在 false不存在
     */


    public boolean hasKey(String key) {

        try {

            return redisTemplate.hasKey(key);

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }


    /**
     * 刪除快取
     *
     * @param key 可以傳一個值 或多個
     */

    @SuppressWarnings("unchecked")


    public void del(String... key) {

        if (key != null && key.length > 0) {

            if (key.length == 1) {

                redisTemplate.delete(key[0]);

            } else {

                redisTemplate.delete(CollectionUtils.arrayToList(key));

            }

        }

    }


    // ============================String=============================

    /**
     * 普通快取獲取
     *
     * @param key 鍵
     * @return 值
     */


    public Object get(String key) {

        return key == null ? null : redisTemplate.opsForValue().get(key);

    }


    /**
     * 普通快取放入
     *
     * @param key   鍵
     * @param value 值
     * @eturn true成功 false失敗
     */


    public boolean set(String key, Object value) {

        try {

            redisTemplate.opsForValue().set(key, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }


    /**
     * 普通快取放入並設定時間
     *
     * @param key   鍵
     * @param value 值
     * @param time  時間(秒) time要大於0 如果time小於等於0 將設定無限期
     * @return true成功 false 失敗
     */


    public boolean set(String key, Object value, long time) {

        try {

            if (time > 0) {

                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);

            } else {

                set(key, value);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }


    /**
     * 遞增
     *
     * @param key   鍵
     * @param delta 要增加幾(大於0)
     * @return 134
     */


    public long incr(String key, long delta) {

        if (delta < 0) {

            throw new RuntimeException("遞增因子必須大於0");

        }

        return redisTemplate.opsForValue().increment(key, delta);

    }

    /**
     * 遞減
     *
     * @param key   鍵
     * @param delta 要減少幾(小於0)
     * @return
     */


    public long decr(String key, long delta) {

        if (delta < 0) {

            throw new RuntimeException("遞減因子必須大於0");

        }

        return redisTemplate.opsForValue().increment(key, -delta);

    }


    // ================================Map=================================

    /**
     * HashGet
     *
     * @param key  鍵 不能為null
     * @param item 項 不能為null
     * @return 值
     */


    public Object hget(String key, String item) {

        return redisTemplate.opsForHash().get(key, item);

    }

    /**
     * 獲取hashKey對應的所有鍵值
     *
     * @param key 鍵
     * @return 對應的多個鍵值
     */


    public Map<Object, Object> hmget(String key) {

        return redisTemplate.opsForHash().entries(key);

    }

    /**
     * HashSet
     *
     * @param key 鍵
     * @param map 對應多個鍵值
     * @return true 成功 false 失敗
     */


    public boolean hmset(String key, Map<String, Object> map) {

        try {

            redisTemplate.opsForHash().putAll(key, map);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * HashSet 並設定時間
     *
     * @param key  鍵
     * @param map  對應多個鍵值
     * @param time 時間(秒)
     * @return true成功 false失敗
     */


    public boolean hmset(String key, Map<String, Object> map, long time) {

        try {

            redisTemplate.opsForHash().putAll(key, map);

            if (time > 0) {

                expire(key, time);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 向一張hash表中放入資料,如果不存在將建立
     *
     * @param key   鍵
     * @param item  項
     * @param value 值
     * @return true 成功 false失敗
     */


    public boolean hset(String key, String item, Object value) {

        try {

            redisTemplate.opsForHash().put(key, item, value);

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 向一張hash表中放入資料,如果不存在將建立
     *
     * @param key   鍵
     * @param item  項
     * @param value 值
     * @param time  時間(秒) 注意:如果已存在的hash表有時間,這裡將會替換原有的時間
     * @return true 成功 false失敗
     */


    public boolean hset(String key, String item, Object value, long time) {

        try {

            redisTemplate.opsForHash().put(key, item, value);

            if (time > 0) {

                expire(key, time);

            }

            return true;

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 刪除hash表中的值
     *
     * @param key  鍵 不能為null
     * @param item 項 可以使多個 不能為null
     */


    public void hdel(String key, Object... item) {

        redisTemplate.opsForHash().delete(key, item);

    }


    /**
     * 判斷hash表中是否有該項的值
     *
     * @param key  鍵 不能為null
     * @param item 項 不能為null
     * @return true 存在 false不存在
     */

    public boolean hHasKey(String key, String item) {

        return redisTemplate.opsForHash().hasKey(key, item);

    }

    /**
     * hash遞增 如果不存在,就會建立一個 並把新增後的值返回
     *
     * @param key  鍵
     * @param item 項
     * @param by   要增加幾(大於0)
     * @return 274
     */


    public double hincr(String key, String item, double by) {

        return redisTemplate.opsForHash().increment(key, item, by);

    }


    /**
     * hash遞減
     *
     * @param key  鍵
     * @param item 項
     * @param by   要減少記(小於0)
     * @return 285
     */


    public double hdecr(String key, String item, double by) {

        return redisTemplate.opsForHash().increment(key, item, -by);

    }

    // ============================set=============================

    /**
     * 292
     * 根據key獲取Set中的所有值
     * 293
     *
     * @param key 鍵
     *            294
     * @return 295
     */


    public Set<Object> sGet(String key) {

        try {

            return redisTemplate.opsForSet().members(key);

        } catch (Exception e) {

            e.printStackTrace();

            return null;

        }

    }

    /**
     * 根據value從一個set中查詢,是否存在
     *
     * @param key   鍵
     * @param value 值
     * @return true 存在 false不存在
     */


    public boolean sHasKey(String key, Object value) {

        try {

            return redisTemplate.opsForSet().isMember(key, value);

        } catch (Exception e) {

            e.printStackTrace();

            return false;

        }

    }

    /**
     * 將資料放入set快取
     *
     * @param key    鍵
     * @param values 值 可以是多個
     * @return 成功個數
     */


    public long sSet(String key, Object... values) {

        try {

            return redisTemplate.opsForSet().add(key, values);

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }

    /**
     * 將set資料放入快取
     *
     * @param key    鍵
     * @param time   時間(秒)
     * @param values 值 可以是多個
     * @return 成功個數
     */


    public long sSetAndTime(String key, long time, Object... values) {

        try {

            Long count = redisTemplate.opsForSet().add(key, values);

            if (time > 0)

                expire(key, time);

            return count;

        } catch (Exception e) {

            e.printStackTrace();

            return 0;

        }

    }


    /**
     * 355
     * 獲取set快取的長度
     * 356
     *
     * @param key 鍵
     *            357
     * @return 358
     */


    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 移除值為value的
     *
     * @param key    鍵
     * @param values 值 可以是多個
     * @return 移除的個數
     */


    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    // ===============================list=================================

    /**
     * 獲取list快取的內容
     *
     * @param key   鍵
     * @param start 開始
     * @param end   結束 0 到 -1代表所有值
     * @return 391
     */


    public List<Object> lGet(String key, long start, long end) {

        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }


    /**
     * 獲取list快取的長度
     *
     * @param key 鍵
     * @return 405
     */


    public long lGetListSize(String key) {

        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 通過索引 獲取list中的值
     *
     * @param key   鍵
     * @param index 索引 index>=0時, 0 表頭,1 第二個元素,依次類推;index<0時,-1,表尾,-2倒數第二個元素,依次類推
     * @return 420
     */


    public Object lGetIndex(String key, long index) {

        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 將list放入快取
     *
     * @param key
     * @param value
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 將list放入快取
     *
     * @param key   鍵
     * @param value 值
     * @param time  時間(秒)
     * @return 453
     */


    public boolean lSet(String key, Object value, long time) {

        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }


    /**
     * 將list放入快取
     *
     * @param key
     * @param value
     * @return
     */
    public boolean lSet(String key, List<Object> value) {

        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 將list放入快取
     *
     * @param key
     * @param value
     * @param time
     * @return
     */

    public boolean lSet(String key, List<Object> value, long time) {

        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 根據索引修改list中的某條資料
     *
     * @param key
     * @param index
     * @param value
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 移除N個值為value
     *
     * @param key
     * @param count
     * @param value
     * @return
     */
    public long lRemove(String key, long count, Object value) {

        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }

    }
}

4.9、測試

  • 將查詢的結果快取

  • 將資料更的結果快取

4.10、以上只是介紹了最基本的快取配置,很多細節性的沒有說到,有很多原理性的東西暫未講解到

  • redis的RedisTemplate的自動配置(redisconf)原理?
  • 快取key的生成策略、如何自定義key?
  • 如何更加優雅的用快取?
  • 真實專案中快取怎麼用等等......
  • 以上細節後續會馬上補充

5、最終專案結構圖

TO BE CONTINUED