Da última vez, recebi um pedido para otimizar a página de carregamento e a velocidade do sistema antigo. No início, queria otimizar o SQL. Mais tarde, o chefe mencionou que usei o cache para verificar vários fóruns. Não encontrei um artigo que introduziu a paginação de cache. Reservei um momento para compartilhar com vocês.
nome | explicar |
---|---|
Cache | Interface de cache, define as operações de cache. As implementações incluem: RedisCache, EhCacheCache, ConcurrentMapCache, etc. |
Gerenciador de Cache | Gerenciador de cache, gerencia vários componentes de cache (cache) |
@Cacheable | Principalmente para configuração do método, pode ser armazenado em cache de acordo com os parâmetros de solicitação do método |
@CacheEvict | Esvaziar o cache |
@CachePut | É garantido que o método será chamado e o resultado deverá ser armazenado em cache. A diferença de @Cacheable é se o método é chamado toda vez, o que geralmente é usado para atualizar |
@EnableCaching | Ativar cache baseado em anotação |
chave | A chave do cache pode estar vazia. Se for especificada, deve ser escrita de acordo com a expressão SpEL. Se não for especificada, será combinada de acordo com todos os parâmetros do método por padrão. Por exemplo: @Cacheable(value= ”testcache”,chave=”#id”) |
valor | O nome do cache, definido no arquivo de configuração do spring, deve especificar pelo menos um como: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
allEntries (@CacheEvict) |
Se deseja limpar todo o conteúdo do cache, o padrão é falso, se especificado como verdadeiro, todos os caches serão limpos imediatamente após o método ser chamado Por exemplo: @CachEvict(value=”testcache”,allEntries=true) |
//Se você quiser saber mais anotações, pode ler primeiro o link deste artigo. Sinto que as anotações estão bem organizadas
<!-- Redis Client 3版本以上会报错与spring-boot-starter-data-redis冲突 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
1- método de configuração do arquivo yml
spring:
thymeleaf:
cache: false
redis:
#REDIS 数据库索引(默认为0)
database: 1
#redis服务地址
host: 127.0.0.1
#redis服务连接端口
port: 6379
#redis密码(没有密码默认为空)
password:
#连接池最大连接数(使用负值表示没有限制)
jedis:
pool:
max-active: 8
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 8
# 连接池中的最大空闲连接
max-idle: 500
# 连接池最大连接数(使用负值表示没有限制)
max-active: 2000
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: 10000
#连接超时时间(毫秒)
timeout: 5000
#打印sql日志
logging:
level:
root: debug
2-Comentários adicionados à inicialização do SpringBoot
@SpringBootApplication
//扫描容器
@ComponentScan(basePackages = {"com.lwq.demo.*"})
//扫描mapper层
@MapperScan("com.lwq.demo.dao")
//启动缓存
@EnableCaching
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3- configuração do coifg Se configurado apenas desta forma, também pode rodar normalmente, neste caso os dados armazenados em cache no redis são binários
/**
* @author redis 配置
* 继承缓存配置
*/
@Configuration
public class redisConfig extends CachingConfigurerSupport {
}
3-1 A configuração coifg significa que não há serialização e desserialização, embora não afete o uso normal, mas sempre me sinto desconfortável. Este ponto deve ser observado que se seu projeto usou cache antes e você o desserializou, se você fizer não limpa o cache Vai reportar um erro de 500, aí é só limpar o cache
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.CacheKeyPrefix;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.*;
import java.time.Duration;
/**
* @author redis 配置
* 继承缓存配置
*/
@Configuration
public class redisConfig extends CachingConfigurerSupport {
/**
* 二进制转成json 数据
* 配置文件的配置没有用上
*
* @return
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
//过期时间 可有可无 如果不写就是永久保存 看需求 这里是缓存一个小时
//建议是永久保存
config = config.entryTtl(Duration.ofHours(1));
//序列化
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
//反序列化
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
//将配置文件中所有的配置都生效
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());;
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
4- Camada do controlador de cache de consulta de paginação de demonstração
/**
* 分页缓存测试 分页携带数据
*/
//@RequestMapping("/test5")
//@GetMapping("/test5")
@PostMapping("/test5")
@ResponseBody
public List<Daliy> test5( QueryRequest request,String test) {
List<Daliy> daliys = daliyService.paGeTwo(request,test);
System.out.println(daliys);
return daliys;
}
4-1 Serviço de consulta de paginação Observe que, se um dos valores da chave for nulo, o nulo será armazenado no cache. A explicação oficial é fácil de causar penetração no cache de avalanche. A solução oficial não pode ser nula, mas é pode estar vazio. Eu verifiquei Se estiver vazio, esse valor não será armazenado no cache. Isso demonstrará a todos e irá direto ao tópico.
//优先级最低的 这个表示在redis 创建一个文件夹 DaliyService 在这个文件夹做缓存
@CacheConfig(cacheNames = "DaliyService")
public interface DaliyService extends IService<Daliy> {
/**
* 分页缓存2携带多一个参数
* @param request
* @param test
* @return
*/
//表示是否缓存空值,一般情况下是允许的。因为这涉及到缓存的三大问题:缓存穿透、缓存雪崩、缓存击穿。官方是怎么说的
@Cacheable(key = "#p0.toString() + (#p1 != null ? # p1.toString() : '')")
List<Daliy> paGeTwo(QueryRequest request, String test);
}
4-2 Exclusão de cache de consulta paginada de demonstração
/**
* 根据id 删除数据
*/
@PostMapping("/test6")
@ResponseBody
public Boolean test6( String id) {
int bool = daliyService.deletes(id);
System.out.println(bool);
return bool >0 ? true:false;
}
4-3 Serviço de exclusão de paginação @CacheEvict(key = "#p0",allEntries = true) Observe que allEntries = true significa excluir todos os dados do arquivo DaliyService Se @CacheEvict(key = "#p0") excluir sozinho o cache sem efeito
//优先级最低的 这个表示在redis 创建一个文件夹 DaliyService 在这个文件夹做缓存
@CacheConfig(cacheNames = "DaliyService")
public interface DaliyService extends IService<Daliy> {
//根据id 删除数据 缓存也跟着删除 allEntries 默认为 false 只会删除数据库得不会删除缓存这里要改成true
//@CacheEvict(key = "#p0")
@CacheEvict(key = "#p0",allEntries = true)
int deletes(String id);
}
4-4 Ainda há dados na consulta de paginação, mas após a exclusão dos dados, todos os dados na pasta DliyService serão excluídos
4-5 Os dois métodos de adicionar dados e atualizar a camada do controlador de dados por paginação são os mesmos, então não farei muita introdução aqui
/**
* 根据id 更新缓存
*/
@PostMapping("/test7")
@ResponseBody
public Boolean test7( String id,String projectNo) {
int bool = daliyService.updataTest(id,projectNo);
System.out.println(bool);
return bool >0 ? true:false;
}
/**
* 创建数据
*/
@PostMapping("/test8")
@ResponseBody
public Daliy test8( @RequestBody Daliy daliy) {
int bool = daliyService.createDayliy(daliy);
System.out.println(bool);
return daliy;
}
4-6 Paginação para adicionar dados e atualizar serviço de dados
//优先级最低的 这个表示在redis 创建一个文件夹 DaliyService 在这个文件夹做缓存
@CacheConfig(cacheNames = "DaliyService")
public interface DaliyService extends IService<Daliy> {
/**
* 更新数据
* @param id id
* @param projectNo 更新得值
* @return
*/
// 无论使用更新或添加 都把缓存删除
@CacheEvict(key = "#p0",allEntries = true)
int updataTest(String id, String projectNo);
/**
* 创建数据
* @param daliy 前端返回的数据
* @return
*/
// 无论使用更新或添加 都把缓存删除
@CacheEvict(key = "#p0",allEntries = true)
int createDayliy(Daliy daliy);
}
4-7 Este é o mesmo resultado allEntries = true exclui todos os caches em sua pasta atual
4-8 O cache de paginação é assim, não é difícil de fazer, mas não usei a anotação oficial @CachePut porque o uso da paginação de cache acima é minha recomendação pessoal
5 A atualização, exclusão e adição acima não são usadas @CachePut, a propósito, para mostrar a você
5-1 controlador de dados de consulta de cache
/**
* 根据id查询数据 缓存没有就在数据库获取
* @param id 根据id 获取数据源
* @return
*/
@PostMapping("/test9")
@ResponseBody
public Daliy test9(String id) {
Daliy daliy = daliyService.selectByid(id);
return daliy;
}
5-2 O valor do servidor de dados de consulta de cache significa recriar uma pasta chamada text. Antes de armazenar os dados de consulta no cache de acordo com o id, primeiro avaliamos se o id passado do front end é nulo. Se for nulo, ele é propenso a penetração e quebra de avalanche. Se estiver vazio, não será armazenado no cache
/**
* 根据id 的查到的数据
* @param id
* @return
*/
//这里重新创建文件夹text 这里重新定义key
@Cacheable(value = "text",key = "#id.toString() != null ? 'user_'+#id.toString() : ''")
Daliy selectByid(String id);
5-3 Cache de dados de atualização de uso do @CachePut
/**
* 更改缓存里面的数据
* @param daliy 根据id 更改的数据
* @return
*/
@PostMapping("/test10")
@ResponseBody
public Daliy test10(@RequestBody Daliy daliy) {
System.out.println(daliy);
Daliy updataDaliy = daliyService.updataByid(daliy);
return updataDaliy;
}
5-4 Atualizar o valor do cache de dados Conforme mencionado acima, isso significa criar uma pasta. Isso é para substituir os dados no cache de acordo com o id. @CachePut é para substituir o conjunto de resultados retornado no cache.
/**
* 更新数据
* @param daliy
* @return
*/
//CachePut 返回的数据的结果集 替换之前的缓存数据
//不能直接这样 这样容易出现 穿透 跟 雪崩 要先判断不为null 的时候 官网提供的是可以为空但不能为null
// @CachePut(value = "text",key = "#daliy.id")
//如果更新全部字段的可以忽略这句话 更新某个字段的注意这边特别容易踩坑这是将结果集返回做缓存的 我们要先跟id把这条数据全部查出来 把要改的数据替换 在返回结果集,
@CachePut(value = "text",key = "#daliy.id.toString() != null ? 'user_'+#daliy.id.toString() : ''")
Daliy updataByid(Daliy daliy);
Os dados originais encontraram os dados id22
Alterar dados @CachePut significa substituir o cache
Dê uma olhada nos dados em comparação com a imagem anterior
5-5 @CachePut O princípio de adicionar dados é o mesmo que atualizar os dados. Se houver um cache para substituir o cache, adicione o cache
/**
* 添加数据缓存 有就替换 没有就添加
* @param daliy 根据id 更改的数据
* @return
*/
@PostMapping("/test11")
@ResponseBody
public Daliy test11(@RequestBody Daliy daliy) {
System.out.println(daliy);
Daliy daliyAdd = daliyService.daliyAdd(daliy);
return daliyAdd;
}
5-6 Aqui devemos prestar atenção quando adicionamos dados sem id @CachePut é para armazenar o conjunto de resultados no cache. Após adicionarmos os dados, haverá preenchimento de chave primária. Como retornar a classe de entidade?
/**
* 添加数据
* @param daliy 要添加得数据
* @return
*/
//注意 daliy.id 现在实体类添加的数据id 是为空的 添加完数据以后为会主键回填生成id的 缓存存的是结果集
@CachePut(value = "text", key = "#daliy.id.toString() != null ? 'user_'+#daliy.id.toString() : ''")
Daliy daliyAdd(Daliy daliy);
dao camada
/**
* 添加数据
*
* @param daliy 要添加得数据
* @return
*/
@Override
public Daliy daliyAdd(Daliy daliy) {
//进行了主键回填
this.daliyMapper.insert(daliy);
return daliy;
}
Se não houver chave primária, use a chave primária para preencher
6- Excluir cache de dados
/**
* 根据id删除数据
* @param id 根据id 删除数据
* @return
*/
@PostMapping("/test12")
@ResponseBody
public void test12(String id) {
System.out.println(id);
int states = daliyService.deleteByid(id);
}
6-1 Excluir o cache de acordo com o id allEntries = true Para excluir todos os dados da pasta de texto, preciso apenas excluir de acordo com o id
/**
* 根据id删除数据
* @param id
* @return
*/
//allEntries = true 这里不可以加这个哦 加上这个表示这个文件夹的text 会全部删除
//下面这样写只是根据user_id 删除的
@CacheEvict(value = "text",key = "'user_'+#id")
int deleteByid(String id);
//Excluir os dados armazenados em cache com o id de 127 também são excluídos
7- A demonstração acima acabou, mas ainda há um ponto. Algumas empresas detalharam a divisão do trabalho e criaram muitas pastas. Neste momento, você pode usar anotações combinadas. Você pode excluir, adicionar e substituir caches de acordo com seus arquivos .
@Caching(
//这是替换文件夹的 userCache, key id 的结果集
put = {@CachePut(value = "userCache", key = "#user.id")},
//这是删除 文件夹 allUsersCache 所有的缓存的数据
evict = {@CacheEvict(value = "allUsersCache", allEntries = true)}
)
public AppUser createUser(AppUser user) {
logger.info("创建用户start..., user.id=" + user.getId());
appuserRepository.save(user);
return user;
}
O princípio desta demonstração é o mesmo da anterior.