5、Spring Boot快取

HOsystem發表於2020-11-22

1.JSR107

  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快取抽象

  Spring從3.1開始定義了org.springframework.cache.Cache

org.springframework.cache.CacheManager介面來統一不同的快取技術;

並支援使用JCache(JSR-107)註解簡化我們開發;

 

  Cache介面為快取的元件規範定義,包含快取的各種操作集合;

  Cache介面下Spring提供了各種xxxCache的實現;如RedisCache、EhCacheCache、ConcurrentMapCache等;

  每次呼叫需要快取功能的方法時,Spring會檢查檢查指定引數的指定的目標方法是否已經被呼叫過;如果有就直接從快取中獲取方法呼叫後的結果,如果沒有就呼叫方法並快取結果後返回給使用者。下次呼叫直接從快取中獲取。

  使用Spring快取抽象時我們需要關注以下兩點

確定方法需要被快取以及他們的快取策略

從快取中讀取之前快取儲存的資料

 

(1).搭建環境

1).匯入資料庫檔案

com.hosystem.cache.bean.Department

package com.hosystem.cache.bean;

 

import java.io.Serializable;

 

public class Department implements Serializable {

   

   private Integer id;

   private String departmentName;

   

   

   public Department() {

      super();

      // TODO Auto-generated constructor stub

   }

   public Department(Integer id, String departmentName) {

      super();

      this.id = id;

      this.departmentName = departmentName;

   }

   public Integer getId() {

      return id;

   }

   public void setId(Integer id) {

      this.id = id;

   }

   public String getDepartmentName() {

      return departmentName;

   }

   public void setDepartmentName(String departmentName) {

      this.departmentName = departmentName;

   }

   @Override

   public String toString() {

      return "Department [id=" + id + ", departmentName=" + departmentName + "]";

   }

}

com.hosystem.cache.bean.Employee

package com.hosystem.cache.bean;

 

import java.io.Serializable;

 

public class Employee implements Serializable{

   

   private Integer id;

   private String lastName;

   private String email;

   private Integer gender; //性別 1  0

   private Integer dId;

   

   

   public Employee() {

      super();

   }

 

   

   public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) {

      super();

      this.id = id;

      this.lastName = lastName;

      this.email = email;

      this.gender = gender;

      this.dId = dId;

   }

   

   public Integer getId() {

      return id;

   }

   public void setId(Integer id) {

      this.id = id;

   }

   public String getLastName() {

      return lastName;

   }

   public void setLastName(String lastName) {

      this.lastName = lastName;

   }

   public String getEmail() {

      return email;

   }

   public void setEmail(String email) {

      this.email = email;

   }

   public Integer getGender() {

      return gender;

   }

   public void setGender(Integer gender) {

      this.gender = gender;

   }

   public Integer getdId() {

      return dId;

   }

   public void setdId(Integer dId) {

      this.dId = dId;

   }

   @Override

   public String toString() {

      return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + ", dId="

            + dId + "]";

   }

}

(2).註解使用

 

com.hosystem.cache.service.EmployeeService

package com.hosystem.cache.service;

 

import com.hosystem.cache.bean.Employee;

import com.hosystem.cache.mapper.EmployeeMapper;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.cache.annotation.*;

import org.springframework.stereotype.Service;

 

@Service

@CacheConfig(cacheNames = "emp")

public class EmployeeService {

 

    @Autowired

    EmployeeMapper employeeMapper;

 

    /**

     *  將方法的執行結果進行快取;下次在呼叫相同的資料,直接從快取中獲取,不再呼叫方法;

     *

     *  CacheManager管理多個cache元件,對快取的真正CRUD操作在Cache元件中,每一個快取元件有自己唯一一個名字

     *  工作原理:

     *      1.自動配置類:CacheAutoConfiguration

     *      2.快取配置類:GenericCacheConfigurationJCacheCacheConfigurationEhCacheCacheConfigurationHazelcastCacheConfigurationInfinispanCacheConfigurationCouchbaseCacheConfigurationRedisCacheConfigurationCaffeineCacheConfigurationSimpleCacheConfigurationNoOpCacheConfiguration

     *          org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration

     *          org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration

     *          org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration

     *          org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration

     *          org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration

     *          org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration

     *          org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration

     *          org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration

     *          org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration

     *          org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration

     *      3.配置類預設生效:SimpleCacheConfiguration

     *      4.給容器中註冊了一個cacheManager:ConcurrentMapCacheManager

     *      5.可以獲取、建立ConcurrentMapCache型別的快取元件;它的作用是將資料儲存在ConcurrentMap

     *

     *  @Cacheable 執行流程:

     *      1.方法執行之前,先去查詢Cache(快取元件),按照cacheNames指定的名字獲取;(CacheManager先獲取相應的快取)第一次獲取快取如果沒有該快取則會自動建立

     *      2.Cache中查詢快取的內容,使用一個key,預設為方法的引數;

     *        (1).key是按照某種策略生成的;預設是使用keyGenerator生成的,預設使用SimpleKeyGenerator生成key

     *        (2).預設使用SimpleKeyGenerator生成key預設策略:若無引數,key = new SimpleKey();|如果有單個引數,key=引數值;|如果有多個引數,key = new SimpleKey(params);

     *      3.若為查詢到快取就呼叫方法

     *      4.將方法返回的結果,放入快取中

     *      @Cacheable 標註的方法執行之前先來檢查快取中有沒有這個資料,預設按照引數的值作為key查詢快取,如果快取不存在,則執行方法並將結果放入快取

     *  核心:

     *      1.使用CacheManager[ConcurrentMapCacheManager]按照名字獲取cache[ConcurrentHashMapCache]元件

     *      2.key使用keyGenerator生成,預設是SimpleKeyGenerator

     *

     *  屬性:valuecacheNameskeykeyGeneratorcacheManagercacheResolverconditionunlesssync

     *      value/cacheNames:指定快取元件的名字

     *      key:快取資料使用的key,可以用它指定引數。預設是使用方法引數的值

     *          SpEL: #id:引數id的值  #a0 #p0 #root.args[0]

     *      keyGenerator:key生成器;可以指定key生成器元件id;

     *          注:keyGeneratorkey只能二選一

     *      cacheManager:指定快取管理器

     *      cacheResolver:指定獲取解析器

     *      condition:指定符合條件情況下快取

     *      unless:否定快取;unless指定條件為true,方法返回值不會被快取;可以獲取結果進行判斷

     *      sync:是否使用非同步模式

     */

    //cacheNames = "emp":

    //condition = "#id>0":只有當id>0的時候再進行快取

    //condition = "#a0>1":只有當第一個引數>1時候才進行快取

    //unless = "#result==null":當返回結果為空時不進行快取

    //unless = "#a0==2":如果第一個引數的結果為2,則結果不快取

    //key = "#root.methodName+'['+#id+']'"

    //keyGenerator = "myKeyGenerator":自定義key

    @Cacheable(cacheNames = "emp"/*,condition = "#a0>1",unless = "#a0==2"*/)

    public Employee getEmp(Integer id){

        System.out.println("查詢"+id+"號員工");

        Employee emp = employeeMapper.getEmpById(id);

        return emp;

    }

 

    /**

     *  @CachePut:呼叫方法同時更新快取資料

     *  修改資料庫某個資料 同時更新快取

     *

     *  執行時間:

     *    1.先呼叫方法

     *    2.將方法的結果快取起來

     *

     *  測試步驟:

     *    1.查詢1號員工;查詢到的結果會放在快取中 key:1 value:lastName:張三

     *    2.查詢結果照舊

     *    3.更新1號員工資訊[emp?id=1&lastName=zhangs&gender=0];將方法的返回值也放進快取中 key:傳入的employee物件 值:返回的employee物件

     *    4.查詢1號員工;查詢結果為未更新前的資料[1號員工的資訊沒有在快取中更新]

     *      key = "#employee.id":使用傳入引數的員工id進行更新

     *      key = "#result.id":使用返回後的id

     *      :@Cacheablekey是不能夠使用#result

     */

    @CachePut(value = "emp",key = "#result.id")

    public Employee updateEmp(Employee employee){

        System.out.println("update" + employee);

        employeeMapper.updateEmp(employee);

        return employee;

    }

 

    /**

     *  @CacheEvict:快取清除

     */

    //key = "#id":指定key刪除快取

    //allEntries = true:刪除快取中所有資料 預設引數為false

    //beforeInvocation=false:快取的清除是否在方法之前執行 預設是false,即清除快取操作在方法執行之後執行 如果方法出現異常快取就不會清除

    //beforeInvocation = true:清除快取操作在方法執行之前執行 如果方法出現異常快取也會清除

    @CacheEvict(value = "emp"/*,key = "#id"*//*,allEntries = true*/,beforeInvocation = true)

    public void deleteEmp(Integer id){

        System.out.println("delete"+id);

//        employeeMapper.deleteEmpById(id);

        int i = 10/0;

    }

 

    @Caching(

        cacheable =  {

                @Cacheable(value="emp",key="#lastName")

        },

        put = {

              @CachePut(value = "emp",key = "#result.id"),

              @CachePut(value = "emp",key = "#result.email")

        }

    )

    public Employee getEmpByLastName(String lastName){

        return employeeMapper.getEmpByLastName(lastName);

    }

}

com.hosystem.cache.service.DeptService

package com.hosystem.cache.service;

 

import com.hosystem.cache.bean.Department;

import com.hosystem.cache.mapper.DepartmentMapper;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.cache.annotation.Cacheable;

import org.springframework.stereotype.Service;

 

@Service

public class DeptService {

 

    @Autowired

    DepartmentMapper departmentMapper;

 

    /**

     *  快取的資料能存入redis

     *  第二次從快取中查詢就不能恢復反序列化

     *  存的是deptjson;cachemanager預設使用RedisTemplate<Object, Employee>操作Redis

     *

     * @param id

     * @return

     */

    @Cacheable(cacheNames = "dept")

    public Department getDeptById(Integer id){

        System.out.println("查詢部門"+id);

        Department mapper = departmentMapper.getDeptById(id);

        return mapper;

    }

}

 

 

 

 

 

com.hosystem.cache.controller.EmployeeController

package com.hosystem.cache.controller;

 

 

import com.hosystem.cache.bean.Employee;

import com.hosystem.cache.service.EmployeeService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RestController;

 

@RestController

public class EmployeeController {

 

    @Autowired

    EmployeeService employeeService;

 

    @GetMapping("/emp/{id}")

    public Employee getEmployee(@PathVariable("id") Integer id){

        Employee employee = employeeService.getEmp(id);

        return employee;

    }

 

    @GetMapping("/emp")

    public Employee update(Employee employee){

        Employee emp = employeeService.updateEmp(employee);

        return emp;

    }

 

    @GetMapping("/delemp")

    public String deleteEmp(Integer id){

        employeeService.deleteEmp(id);

        return "success";

    }

 

    @GetMapping("/emp/lastname/{lastName}")

    public Employee getEmpByLastName(@PathVariable("lastName") String lastName){

        return employeeService.getEmpByLastName(lastName);

    }

}

com.hosystem.cache.Springboot01CacheApplication

package com.hosystem.cache;

 

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cache.annotation.EnableCaching;

 

/**

 *  搭建環境

 *  1. 匯入資料庫檔案  建立departmentemployee

 *  2. 建立javaBean封裝資料

 *  3. 整合mybatis運算元據庫

 *     1).配置資料來源

 *     2).使用註解版mybatis

 *        (1).@MapperScan指定需要掃描的mapper介面所在的包

 *

 *  使用快取

 *  1. 開啟註解快取 @EnableCaching

 *  2. 標註快取註解

 *     @Cacheable:針對方法配置,能夠根據方法的請求引數對其結果進行快取

 *     @CacheEvict:清空快取

 *     @CachePut:保證方法被呼叫,又希望結果被快取

 *

 * 預設使用的ConcurrentMapCacheManager--->ConcurrentMapCache 將資料儲存在ConcurrentMap<Object,Object>

 * 開發中常使用其它快取中介軟體:Redismemcahced

 *

 * 整合Redis作為快取

 * Redis 是一個開源(BSD許可)的,記憶體中的資料結構儲存系統,它可以用作資料庫、快取和訊息中介軟體。

 * 1.安裝docker:https://www.cnblogs.com/HOsystem/p/13789551.html

 * 2.安裝Redis(通過docker):https://www.cnblogs.com/HOsystem/p/13850049.html

 * 3.配置Redis

 * 4.測試快取

 *    原理:CacheManager===Cache 快取元件來實際給快取中儲存資料

 *    (1).引入redisstarter,容器中儲存的是org.springframework.data.redis.cache.RedisCacheManager

 *    (2).org.springframework.data.redis.cache.RedisCacheManager幫忙建立org.springframework.data.redis.cache.RedisCache作為快取元件;

 *       org.springframework.data.redis.cache.RedisCache通過操作redis快取資料的

 *    (3).預設儲存資料k-v都是object 利用序列化儲存;如何儲存為json;

 *       1).引入了redisstartercachemanager變為RedisCacheManage

 *       2).預設建立RedisCacheManage操作redis的時候使用的是RedisTemplate<Object,Object>

 *       3).RedisTemplate<Object,Object>預設使用jdk的序列化機制

 *    (4).自定義CacheManager

 *

 */

@MapperScan("com.hosystem.cache.mapper")

@EnableCaching

@SpringBootApplication

public class Springboot01CacheApplication {

 

   public static void main(String[] args) {

      SpringApplication.run(Springboot01CacheApplication.class, args);

   }

 

}

 

(3).docker

1).安裝docker

https://www.cnblogs.com/HOsystem/p/13789551.html

2).安裝redis

https://www.cnblogs.com/HOsystem/p/13850049.html

3).測試Redis

com.hosystem.cache.Springboot01CacheApplicationTests

package com.hosystem.cache;

 

import com.hosystem.cache.bean.Employee;

import com.hosystem.cache.mapper.EmployeeMapper;

import com.sun.xml.internal.ws.api.ha.StickyFeature;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.data.redis.core.StringRedisTemplate;

import org.springframework.data.redis.serializer.RedisSerializer;

import org.springframework.lang.Nullable;

 

@SpringBootTest

class Springboot01CacheApplicationTests {

 

   @Autowired

   EmployeeMapper employeeMapper;

 

   @Autowired

   StringRedisTemplate stringRedisTemplate;  //操作k-v是字串形式

 

   @Autowired

   RedisTemplate redisTemplate;   //k-v都是物件

 

   @Autowired

   RedisTemplate<Object,Employee> empRedisTemplate;

 

   /**

    *     Redis常見五大資料型別

    *        String(字串)List(列表)Hash(雜湊)Set(集合)ZSet(有序集合)

    *        stringRedisTemplate.opsForValue():String(字串)

    *        stringRedisTemplate.opsForList():List(列表)

    *        stringRedisTemplate.opsForHash():Hash(雜湊)

    *        stringRedisTemplate.opsForSet():Set(集合)

    *        stringRedisTemplate.opsForZSet():ZSet(有序集合)

    */

   @Test

   public void test01(){

      //redis儲存資料

//    stringRedisTemplate.opsForValue().append("msg","hello");

      String msg = stringRedisTemplate.opsForValue().get("msg");

      System.out.println(msg);

 

//    stringRedisTemplate.opsForList().leftPush("mylist","1");

//    stringRedisTemplate.opsForList().leftPush("mylist","2");

   }

 

   //測試儲存物件

   @Test

   public void test02(){

      Employee empById = employeeMapper.getEmpById(1);

      //預設儲存物件,使用jdk序列化機制,序列化後的資料儲存到redis

//    redisTemplate.opsForValue().set("emp-01",empById);

      //1.將資料以json的方式儲存

         //(1).將物件轉為json

         //(2).redisTemplate預設序列化規則;自定義預設序列化規則

            //    private RedisSerializer keySerializer = null;

            //    private RedisSerializer valueSerializer = null;

            //    private RedisSerializer hashKeySerializer = null;

            //    private RedisSerializer hashValueSerializer = null;

            //    private RedisSerializer<String> stringSerializer = RedisSerializer.string();

      empRedisTemplate.opsForValue().set("emp-01",empById);

   }

 

 

 

   @Test

   public void contextLoads() {

      Employee empById = employeeMapper.getEmpById(1);

      System.out.println(empById);

   }

 

}

docker啟動redis失敗

Error response from daemon: Cannot start container 53fe1fcb2e05214c6f853ef2fe9f65539e69fdc7d6a454bfb073c10c2fba82dd: iptables failed: iptables -t nat -A DOCKER -p tcp -d 0/0 --dport 6379 -j DNAT --to-destination 172.17.0.3:6379 ! -i docker0: iptables: No chain/target/match by that name.

我們首先對iptables進行防火牆規則配置 允許6379埠可以訪問

docker啟動redis失敗

[root@pluto sysconfig]# docker run -d -p 6379:6379 --name myredis redis

Error response from daemon: Conflict. The name "myredis" is already in use by container 53fe1fcb2e05. You have to delete (or rename) that container to be able to reuse that name.

[root@pluto sysconfig]# docker ps -a

CONTAINER ID        IMAGE                       COMMAND                CREATED             STATUS                      PORTS               NAMES

53fe1fcb2e05        redis                       "docker-entrypoint.s   2 minutes ago                                                       myredis       

[root@pluto sysconfig]# docker rm 53fe1fcb2e05

(4).自定義CacheManager

*   整合Redis作為快取

*  Redis 是一個開源(BSD許可)的,記憶體中的資料結構儲存系統,它可以用作資料庫、快取和訊息中介軟體。

*  1.安裝docker:https://www.cnblogs.com/HOsystem/p/13789551.html

*  2.安裝Redis(通過docker):https://www.cnblogs.com/HOsystem/p/13850049.html

*  3.配置Redis

*  4.測試快取

*     原理:CacheManager===Cache 快取元件來實際給快取中儲存資料

*     (1).引入redisstarter,容器中儲存的是org.springframework.data.redis.cache.RedisCacheManager

*     (2).org.springframework.data.redis.cache.RedisCacheManager幫忙建立org.springframework.data.redis.cache.RedisCache作為快取元件;

*        org.springframework.data.redis.cache.RedisCache通過操作redis快取資料的

*     (3).預設儲存資料k-v都是object 利用序列化儲存;如何儲存為json;

*        1).引入了redisstartercachemanager變為RedisCacheManage

*        2).預設建立RedisCacheManage操作redis的時候使用的是RedisTemplate<Object,Object>

*        3).RedisTemplate<Object,Object>預設使用jdk的序列化機制

*     (4).自定義CacheManager

@Autowired

StringRedisTemplate stringRedisTemplate;

 

 

@Autowired

RedisTemplate redisTemplate;

 

pom.xml

<?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 https://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.3.4.RELEASE</version>

      <relativePath <!-- lookup parent from repository -->

 

相關文章