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.快取配置類:GenericCacheConfiguration、JCacheCacheConfiguration、EhCacheCacheConfiguration、HazelcastCacheConfiguration、InfinispanCacheConfiguration、CouchbaseCacheConfiguration、RedisCacheConfiguration、CaffeineCacheConfiguration、SimpleCacheConfiguration、NoOpCacheConfiguration * 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 * * 屬性:value、cacheNames、key、keyGenerator、cacheManager、cacheResolver、condition、unless、sync * value/cacheNames:指定快取元件的名字 * key:快取資料使用的key,可以用它指定引數。預設是使用方法引數的值 * SpEL: #id:引數id的值 #a0 #p0 #root.args[0] * keyGenerator:key生成器;可以指定key生成器元件id; * 注:keyGenerator和key只能二選一 * 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 * 注:@Cacheable的key是不能夠使用#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 * 第二次從快取中查詢就不能恢復反序列化 * 存的是dept的json;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. 匯入資料庫檔案 建立department和employee表 * 2. 建立javaBean封裝資料 * 3. 整合mybatis運算元據庫 * 1).配置資料來源 * 2).使用註解版mybatis * (1).@MapperScan指定需要掃描的mapper介面所在的包 * * 使用快取 * 1. 開啟註解快取 @EnableCaching * 2. 標註快取註解 * @Cacheable:針對方法配置,能夠根據方法的請求引數對其結果進行快取 * @CacheEvict:清空快取 * @CachePut:保證方法被呼叫,又希望結果被快取 * * 預設使用的ConcurrentMapCacheManager--->ConcurrentMapCache 將資料儲存在ConcurrentMap<Object,Object>中 * 開發中常使用其它快取中介軟體:Redis、memcahced * * 整合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).引入redis的starter,容器中儲存的是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).引入了redis的starter,cachemanager變為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).引入redis的starter,容器中儲存的是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).引入了redis的starter,cachemanager變為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 --> |