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