前回、古いシステムの読み込みページと速度を最適化してほしいという依頼を受けました。最初は SQL を最適化したいと考えていました。その後、上司が複数のフォーラムをチェックするためにキャッシュを使用していると言いました。記事は見つかりませんでしたキャッシュ ページングが導入されました。少し時間を割いて共有させていただきました。
名前 | 説明 |
---|---|
キャッシュ | キャッシュ インターフェイス。キャッシュ操作を定義します。実装には、RedisCache、EhCacheCache、ConcurrentMapCache などが含まれます。 |
キャッシュマネージャー | キャッシュマネージャー、さまざまなキャッシュ(キャッシュ)コンポーネントを管理します |
@キャッシュ可能 | 主にメソッド設定用で、メソッドのリクエストパラメータに従ってキャッシュできます。 |
@CacheEvict | キャッシュを空にしてください |
@CachePut | このメソッドは呼び出されることが保証されており、結果はキャッシュされることが期待されます。 @Cacheableとの違いは、更新時によく使われるメソッドを毎回呼び出すかどうかです。 |
@EnableCaching | 注釈ベースのキャッシュを有効にする |
鍵 | キャッシュのキーは空にすることができます。指定されている場合は、SpEL 式に従って書き込まれる必要があります。 指定されていない場合は、デフォルトでメソッドのすべてのパラメータに従って結合されます。 例: @Cacheable(value= ”テストキャッシュ”,key=”#id”) |
価値 | Spring 構成ファイルで定義されるキャッシュの名前には、次のように少なくとも 1 つを指定する必要があります。 @Cacheable(value=”mycache”) または @Cacheable(value={”cache1”,”cache2”} |
allEntries (@CacheEvict ) |
すべてのキャッシュの内容をクリアするかどうか。デフォルトは false です。true に指定すると、 メソッドが呼び出された直後にすべてのキャッシュがクリアされます。 例: @CachEvict(value=”testcache”,allEntries=true) |
//さらに詳しいアノテーションを知りたい場合は、まずこの記事のリンクを読んでください。アノテーションがよく整理されていると感じます
Spring Boot の spring-boot-starter-cache (redis を統合)_MrLee のブログ-CSDN Blog_spring-boot-starter-cache
<!-- 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-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-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- coifg 設定 この方法だけでも正常に動作しますが、この場合、redis にキャッシュされるデータはバイナリになります
/**
* @author redis 配置
* 继承缓存配置
*/
@Configuration
public class redisConfig extends CachingConfigurerSupport {
}
3-1 coifg 設定はシリアル化とデシリアライズがないことを意味し、通常の使用には影響しませんが、常に不快に感じます。キャッシュをクリアしないと、エラー 500 が報告され、キャッシュをクリアするだけです
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- デモ ページング クエリ キャッシュ コントローラー層
/**
* 分页缓存测试 分页携带数据
*/
//@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 ページングクエリサービス キー値のいずれかが null の場合、null がキャッシュに格納されることに注意してください。公式の説明では、アバランシェ キャッシュによる侵入を引き起こしやすいと説明されています。公式の解決策では null にすることはできませんが、空である可能性があります。確認しました。空の場合、この値はキャッシュに保存されません。これにより、全員にそれが示され、トピックに直接進みます。
//优先级最低的 这个表示在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 ページングクエリキャッシュ削除のデモ
/**
* 根据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 ページネーション削除サービス @CacheEvict(key = "#p0",allEntries = true) なお、この allEntries = true は DaliyService ファイルのデータをすべて削除することを意味します。 @CacheEvict(key = "#p0") だけで削除すると、キャッシュは効果なし
//优先级最低的 这个表示在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 ページネーションクエリにデータが残っていますが、データを削除すると、DliyService フォルダー内のデータがすべて削除されます
4-5 ページングによるデータの追加とデータ コントローラー層の更新の 2 つの方法は同じなので、ここではあまり説明しません。
/**
* 根据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 データの追加とデータの更新のためのページング サービス
//优先级最低的 这个表示在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 これは同じ結果です allEntries = true 現在のフォルダー内のすべてのキャッシュを削除します
4-8 ページング キャッシュはこのようなもので、実行するのは難しくありませんが、上記のキャッシュ ページングの使用法が個人的な推奨事項であるため、公式の @CachePut アノテーションは使用しませんでした
5 上記の更新、削除、追加は使用されません @CachePut ちなみに説明します
5-1 キャッシュクエリデータコントローラー
/**
* 根据id查询数据 缓存没有就在数据库获取
* @param id 根据id 获取数据源
* @return
*/
@PostMapping("/test9")
@ResponseBody
public Daliy test9(String id) {
Daliy daliy = daliyService.selectByid(id);
return daliy;
}
5-2 キャッシュクエリデータサーバーの値とは、textというフォルダを再作成することを意味しており、クエリデータをidに応じてキャッシュに格納する前に、まずフロントエンドから渡されたidがnullかどうかを判定し、nullであれば、雪崩の侵入や破壊が起こりやすいため、空の場合はキャッシュに保存されません
/**
* 根据id 的查到的数据
* @param id
* @return
*/
//这里重新创建文件夹text 这里重新定义key
@Cacheable(value = "text",key = "#id.toString() != null ? 'user_'+#id.toString() : ''")
Daliy selectByid(String id);
5-3 @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 データキャッシュ値の更新 前述の通り、フォルダーの作成を意味しますが、これはキャッシュ内のデータをIDに応じて置き換えること、@CachePutはキャッシュに返された結果セットを置き換えることです。
/**
* 更新数据
* @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);
元のデータで id22 データが見つかりました
データの変更 @CachePut はキャッシュを置き換えることを意味します
データを前の写真と比較して見てください
5-5 @CachePut データ追加の原理はデータ更新と同じですが、キャッシュに代わるキャッシュがある場合はキャッシュを追加します
/**
* 添加数据缓存 有就替换 没有就添加
* @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 ID なしでデータを追加するときに注意する必要があります @CachePut は結果セットをキャッシュに保存します データを追加した後、主キーのバックフィルが行われます エンティティ クラスを返すにはどうすればよいですか?
/**
* 添加数据
* @param daliy 要添加得数据
* @return
*/
//注意 daliy.id 现在实体类添加的数据id 是为空的 添加完数据以后为会主键回填生成id的 缓存存的是结果集
@CachePut(value = "text", key = "#daliy.id.toString() != null ? 'user_'+#daliy.id.toString() : ''")
Daliy daliyAdd(Daliy daliy);
ダオレイヤー
/**
* 添加数据
*
* @param daliy 要添加得数据
* @return
*/
@Override
public Daliy daliyAdd(Daliy daliy) {
//进行了主键回填
this.daliyMapper.insert(daliy);
return daliy;
}
主キーがない場合は、主キーを使用してバックフィルします。
6- データキャッシュを削除する
/**
* 根据id删除数据
* @param id 根据id 删除数据
* @return
*/
@PostMapping("/test12")
@ResponseBody
public void test12(String id) {
System.out.println(id);
int states = daliyService.deleteByid(id);
}
6-1 IDに従ってキャッシュを削除します allEntries = true テキストフォルダー内のデータをすべて削除するには、IDに従って削除するだけです
/**
* 根据id删除数据
* @param id
* @return
*/
//allEntries = true 这里不可以加这个哦 加上这个表示这个文件夹的text 会全部删除
//下面这样写只是根据user_id 删除的
@CacheEvict(value = "text",key = "'user_'+#id")
int deleteByid(String id);
//ID 127 でキャッシュされたデータも削除される
7- 以上でデモは終わりですが、まだポイントが 1 つあります。企業によっては細かく分業して多くのフォルダーを作成している場合があります。このとき、アノテーションを組み合わせて使用できます。ファイルに応じてキャッシュを削除、追加、置換できます。 。
@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;
}
このデモの原理は上記と同じです。