緩存中間件

緩存與分布式鎖

即時性、數據一致要求不高的
訪問量大且更新頻率不高的數據
(讀多,寫少)

常用緩存中間件 redis

Spring

如果用spring的情況下,由于redis沒有受spring的管理,
則我們需要自己先寫一個redis的配置類,寫好方法把redistemplate 作為bean return出來受 spring管理,
(很老的方法了,但現在企業也在用)或者 直接寫xml進行注冊,然后放入web.xml中

	<context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/classes/applicationContext.xml,/WEB-INF/classes/config/spring/xjtec_helps/spring_xjtec_helps_taskhelp_starts.xml,/WEB-INF/classes/spring-session.xml</param-value></context-param>

Springboot 或cloud

引入maven redis的starter,然后spring把基礎工作都給你寫好了,你只需要引入,按照規則前綴,在配置中寫好配置值,想用的時候 @autowired進來用就完事了

配置類

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.springframework.boot.autoconfigure.data.redis;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;@Configuration(proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
//所有的配置信息通過RedisProperties.class得到,這里引入過來
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {public RedisAutoConfiguration() {}@Bean@ConditionalOnMissingBean(name = {"redisTemplate"})@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> template = new RedisTemplate();template.setConnectionFactory(redisConnectionFactory);return template;}@Bean@ConditionalOnMissingBean@ConditionalOnSingleCandidate(RedisConnectionFactory.class)public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {return new StringRedisTemplate(redisConnectionFactory);}
}//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.springframework.boot.autoconfigure.data.redis;import java.time.Duration;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties(
//配置文件中,按照這個前綴寫就可以了prefix = "spring.redis"
)
public class RedisProperties {private int database = 0;private String url;private String host = "localhost";private String password;private int port = 6379;private boolean ssl;private Duration timeout;private String clientName;private Sentinel sentinel;private Cluster cluster;private final Jedis jedis = new Jedis();private final Lettuce lettuce = new Lettuce();public static class Pool {private int maxIdle = 8;private int minIdle = 0;private int maxActive = 8;private Duration maxWait = Duration.ofMillis(-1L);private Duration timeBetweenEvictionRuns;public Pool() {}public int getMaxIdle() {return this.maxIdle;}public void setMaxIdle(int maxIdle) {this.maxIdle = maxIdle;}public int getMinIdle() {return this.minIdle;}public void setMinIdle(int minIdle) {this.minIdle = minIdle;}public int getMaxActive() {return this.maxActive;}public void setMaxActive(int maxActive) {this.maxActive = maxActive;}public Duration getMaxWait() {return this.maxWait;}public void setMaxWait(Duration maxWait) {this.maxWait = maxWait;}public Duration getTimeBetweenEvictionRuns() {return this.timeBetweenEvictionRuns;}public void setTimeBetweenEvictionRuns(Duration timeBetweenEvictionRuns) {this.timeBetweenEvictionRuns = timeBetweenEvictionRuns;}}
}

則會自動引入

三級目錄的展示,通過緩存一點一點優化

最基本的緩存的加入

緩存中有則直接用,無則進入數據庫查詢并存放

  @Overridepublic Map<String, List<Catalog2VO>> getCatalogJson() {//給緩存中放json字符串,拿出的json字符串,還用逆轉為能用的對象類型 【序列化與反序列化】//1.加入緩存邏輯String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");if(StringUtils.isEmpty(catalogJSON)){//2.緩存中沒有,查詢數據庫Map<String, List<Catalog2VO>> catalogJsonFromDb = getCatalogJsonFromDb();//3.查到的數據再放入緩存,將對象轉為json放在緩存中String s = JSON.toJSONString(catalogJsonFromDb);redisTemplate.opsForValue().set("catalogJSON",s);}//轉為我們指定的對象Map<String,List<Catalog2VO>> result = JSON.parseObject(catalogJSON,new TypeReference<Map<String,List<Catalog2VO>>>(){});return result;}

為了解決擊穿的問題,優化代碼

緩存中所遇到的三種問題

這塊代碼只能在單項目(this)中能控制到1人進到數據庫查找值,分布式時則控制不住(分了幾臺服務器就有幾個人能進),但也比所有有人進入到數據庫查找好

鎖中的代碼,一定要由三部分,
1.查緩存,又則返回
2.查數據庫
3.再入緩存

    //首先嘗試緩存拿值@Overridepublic Map<String, List<Catalog2VO>> getCatalogJson() {//給緩存中放json字符串,拿出的json字符串,還用逆轉為能用的對象類型 【序列化與反序列化】//1.加入緩存邏輯String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");if(StringUtils.isEmpty(catalogJSON)){//2.緩存中沒有,查詢數據庫Map<String, List<Catalog2VO>> catalogJsonFromDb = getCatalogJsonFromDb();return catalogJsonFromDb;}//轉為我們指定的對象Map<String,List<Catalog2VO>> result = JSON.parseObject(catalogJSON,new TypeReference<Map<String,List<Catalog2VO>>>(){});return result;}//從數據庫查詢并封裝分類數據public Map<String, List<Catalog2VO>> getCatalogJsonFromDb() {//1.synchronized(this): SpringBoot所有的組件在容器中都是單列的,則此時this可以進行鎖住,但分布式時無法鎖住synchronized (this){//得到鎖以后,我們應該再去緩存中查找一次,以防有人已經查到了String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");if(!StringUtils.isEmpty(catalogJSON)){//進鎖后,有可能別人已經提前進來并且找到了,則直接緩存拿到返回即可Map<String,List<Catalog2VO>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>(){});return result;}//1.將數據庫的多次查詢變為一次List<CategoryEntity> selectList = baseMapper.selectList(null);List<CategoryEntity> level1Categorys = getParent_cid(selectList,0L);Map<String,List<Catalog2VO>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k->k.getCatId().toString(),v->{List<CategoryEntity> categoryEntities = getParent_cid(selectList,v.getCatId());List<Catalog2VO> catalog2VOS = null;if(categoryEntities!=null){catalog2VOS = categoryEntities.stream().map(l2->{Catalog2VO catalog2VO = new Catalog2VO(v.getCatId().toString(),null,l2.getCatId().toString(),l2.getName());//1、找當前二級分類的三級分類封裝成voList<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());if(level3Catelog!=null){List<Catalog2VO.Catalog3Vo> collect = level3Catelog.stream().map(l3->{Catalog2VO.Catalog3Vo catalog3Vo = new Catalog2VO.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());return catalog3Vo;}).collect(Collectors.toList());catalog2VO.setCatalog3List(collect);}return catalog2VO;}).collect(Collectors.toList());}return catalog2VOS;}));//3.查到的數據再放入緩存,將對象轉為json放在緩存中String s = JSON.toJSONString(parent_cid);redisTemplate.opsForValue().set("catalogJSON",s,1, TimeUnit.DAYS);return parent_cid;}}

本地嘗試分布式

賦值多臺服務器但注意端口分開
在這里插入圖片描述
在這里插入圖片描述
然后nginx訪問過來時,由網關幫我們賦值均衡
在這里插入圖片描述
根據主機名進行斷言攔截到請求,然后轉到uri

穿透

訪問的數據,數據庫不存在,則也無法放入緩存中,則不斷訪問數據庫
設置null值返回

雪崩

key大批量同時過期
設置過期時間時加入隨機值

擊穿

熱點key,過期后,同時又大量的值進行訪問到數據庫了
給熱點key加鎖,不能讓所有人都去數據庫進行查詢

分布式鎖

小提示

docker中引進的redis,通過命令 docker exec -it redis redis-cli
進入到redis的操作界面

同過redis的setnx進行一次簡易的分布式鎖

    public Map<String, List<Catalog2VO>> getCatalogJsonFromDbWithRedisLock() {//1、占分布式鎖。去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111");//這里的操作就是redis的setnx操作if(lock){//加鎖成功..執行業務Map<String, List<Catalog2VO>> dataFromDb = getDataFromDb();redisTemplate.delete("lock");//操作完成后,釋放鎖return dataFromDb;}else{//加鎖失敗。。則重試return getCatalogJsonFromDbWithRedisLock();//自旋的方式,不斷重新嘗試}}

簡易鎖的缺點
解鎖之前,出異常了,則會導致死鎖
補救:加鎖時一定要帶上過期時間
eg. set lock 111 EX 300 NX(redis命令)

加鎖和過期時間的設置要進行原子操作

  public Map<String, List<Catalog2VO>> getCatalogJsonFromDbWithRedisLock() {//1、占分布式鎖。去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111",300, TimeUnit.SECONDS);//這里的操作就是redis的setnx操作if(lock){//加鎖成功..執行業務Map<String, List<Catalog2VO>> dataFromDb = getDataFromDb();redisTemplate.delete("lock");//操作完成后,釋放鎖return dataFromDb;}else{//加鎖失敗。。則重試return getCatalogJsonFromDbWithRedisLock();//自旋的方式,不斷重新嘗試}}

刪鎖時也要進行原子操作,并且要避免不要刪除掉別人的加的鎖,所有鎖的值要是唯一的,刪除要進行原子操作時,運行lua腳本

  public Map<String, List<Catalog2VO>> getCatalogJsonFromDbWithRedisLock() {String UUID = java.util.UUID.randomUUID().toString();//1、占分布式鎖。去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",UUID,300, TimeUnit.SECONDS);//這里的操作就是redis的setnx操作if(lock){//加鎖成功..執行業務Map<String, List<Catalog2VO>> dataFromDb = getDataFromDb();String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";redisTemplate.execute(new DefaultRedisScript<Integer>(script,Integer.class),Arrays.asList("lock"), UUID);return dataFromDb;}else{//加鎖失敗。。則重試return getCatalogJsonFromDbWithRedisLock();//自旋的方式,不斷重新嘗試}}

最終版本

public Map<String, List<Catalog2VO>> getCatalogJsonFromDbWithRedisLock() {String UUID = java.util.UUID.randomUUID().toString();//1、占分布式鎖。去redis占坑Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",UUID,300, TimeUnit.SECONDS);//這里的操作就是redis的setnx操作Map<String, List<Catalog2VO>> dataFromDb = new HashMap<>();if(lock){try{//加鎖成功..執行業務dataFromDb = getDataFromDb();}finally {String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";redisTemplate.execute(new DefaultRedisScript<Integer>(script,Integer.class),Arrays.asList("lock"), UUID);}return dataFromDb;}else{//加鎖失敗。。則重試return getCatalogJsonFromDbWithRedisLock();//自旋的方式,不斷重新嘗試}}

分布式鎖框架 redisson

引用springboot的redissonstarter則基本不用自己再寫代碼,只需配置文件寫好響應的屬性即可

首次使用則我們自己進行配置一下,則只單獨引入redisson的配置

寫好配置類(放入到ioc容器中,后面則可直接使用)

單個redis的情況,集群則寫入多個ip

@Configuration()
public class MyRedissonConfig {@Bean(destroyMethod="shutdown")public RedissonClient redisson() throws IOException{//創建配置Config config = new Config();config.useSingleServer().setAddress("redis://192.168.29.103:6379");//根據conf創建使用redisson所必須要的 RedissonClient示例RedissonClient redissonClient = Redisson.create(config);return redissonClient;}
}

redisson的鎖都是可重入鎖
可重入鎖:
可重入鎖是指一個線程可以重復獲取它已經持有的鎖。當一個線程已經持有某個鎖時,它可以再次獲取該鎖而不會被阻塞,這種特性稱為"可重入性"(Reentrancy)。

重入計數:每次獲取鎖計數器加1,釋放時減1,計數器為0時鎖才真正釋放
避免死鎖:允許同一線程多次獲取同一把鎖
公平性選擇:ReentrantLock可以配置為公平鎖或非公平鎖
中斷響應:ReentrantLock支持在等待鎖時響應中斷

redisson的api用法與lock的api用法相同

redisson的基礎應用

  @GetMapping("/hello")public String hello(){//1.獲取一把鎖,只要鎖的名字一樣,就是同一把鎖RLock lock = redisson.getLock("my-lock");//2.加鎖lock.lock();//阻塞式等待,默認加的鎖30s,只要這邊把鎖放了,其他人可以獲得到//看門狗,業務時間長時,自動給鎖續期//業務完成,(獲取不到線程id)則不再續期,不手動解鎖的話,30s后自動解鎖//2.1 可以自己指定過期時間,但指定時間后,不會觸發開門狗,則不會自動續期,我們指定了時間后,就會去執行 固定的腳本//lock.lock(10,TimeUnit.SECONDS);//2.2lock.tryLock(100,10,TimeUnit.SECONDS); 可以設定線程等待鎖的時間,超過則放棄//實際應用時,則還是需要指定時間,不需要續期,只要指定的時間長一點即可(超過業務時間)try {System.out.println("加鎖成功,執行業務..."+Thread.currentThread().getId());Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {lock.unlock();}}
讀寫鎖(也是可重入鎖,當前線程可對鎖多次操作)
//通過讀寫鎖,能保證讀的數據一定是最新的, 寫鎖是一個排他鎖,讀鎖是一個共享鎖//寫鎖沒釋放時,其他人既不能寫也不能讀,沒有寫鎖時,則讀鎖就像不存在一樣
//正在讀的過程中,寫鎖也一定要等待讀鎖釋放@GetMapping("/write")@ResponseBodypublic String writeValue(){//讀寫鎖RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");String s = "";RLock rLock = lock.writeLock();try{//1、操作數據庫 則加寫鎖 , 讀數據加讀鎖rLock.lock();s = UUID.randomUUID().toString();Thread.sleep(3000);stringRedisTemplate.opsForValue().set("writeValue",s);} catch (InterruptedException e) {e.printStackTrace();}finally {rLock.unlock();}return s;}@GetMapping("/read")@ResponseBodypublic String readValue(){RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");String s = "";RLock rLock = lock.readLock();rLock.lock();try{s = stringRedisTemplate.opsForValue().get("writeValue");} catch (Exception e) {e.printStackTrace();}finally {rLock.unlock();}return s;}
信號量(主要用于限流操作,可以設置線程上線)
 @GetMapping("/park")@ResponseBodypublic String park() throws InterruptedException{RSemaphore park = redisson.getSemaphore("park");//park.acquire();//直接獲取一個信號量,如果此時信號量沒有了,則會卡住//則通常用下面的方法,得到boolean返回值boolean b = park.tryAcquire();if(b){//執行業務}else{//返回提示return "fail";}return "ok=>"+b;}@GetMapping("/go")@ResponseBodypublic String go() throws InterruptedException{RSemaphore park = redisson.getSemaphore("park");park.release();return "ok";}
閉鎖
 /*** 等待5個班級都沒人后,才能鎖大門*/@GetMapping("/lockDoor")@ResponseBodypublic String lockDoor() throws InterruptedException{RCountDownLatch door = redisson.getCountDownLatch("door");door.trySetCount(5);//設置好鎖需要達到的數量door.await();//等待閉鎖的完成//到達數量后則可以放行return "放假了...";}@GetMapping("/gogogo/{id}")public String gogogo(@PathVariable("id") Long id){RCountDownLatch door = redisson.getCountDownLatch("door");door.countDown();//觸發一次,則減少一個return id+"班的人都走了";}

緩存一致性

   /**(根據自己的業務仔細判斷)* 緩存里面的數據如何和數據庫保持一致* 緩存數據一致性* 1)、雙寫模式  操作數據庫的時候 ,重新緩存保存* 2)、失效模式  操作數據庫的時候,直接刪除相應緩存* 但這兩個操作如果不加鎖的話,還是會導致臟數據* 加鎖后性能很低,要加也要加讀寫鎖* 總結:* 我們能放入緩存的數據本就不應該是實時性,一致性要求超高的數據,所以一般設定好過期時間都是能正常使用的* 我們不應該過度設計,增加系統的復雜性* 遇到頻繁修改、實時要求高,那就不要緩存、不加鎖、自己查數據庫就好了* 阿里有一個cannal* @return*/

SpringCache

不同的CacheManager 都對緩存進行了進一次的分區,例如對于redis的CacheManager,雖然緩存都是存在redis得同一個庫,但到這邊還是進行了分區處理

整合SpringCache簡化緩存開發
  1. 引入依賴
    spring-boot-starter-cache
    spring-boot-starter-data-redis //我們用redis進行的緩存

2)寫配置
(1)自動配置了那些
CacheAutoConfiguration會導入 RedisCacheConfiguration;
自動配置好了緩存管理器RedisCacheManager
(2)配置使用了redis作為緩存
spring.cache.type = redis
3)測試使用緩存
@Cacheable 觸發將數據保存到緩存的操作
@CacheEvict 觸發將數據從緩存刪除的操作
@CachePut 不影響方法的執行進行緩存的更新
@Cacheing 組合以上多個操作
@CacheConfig 在類別共享緩存的相同配置

3)測試使用緩存
主類加注解 啟動緩存 @EnbaleCaching
然后進行相應的注解進行相應的緩存操作

   // 每一個需要緩存的數據我們都來指定要放到那個名字的緩存【緩存的分區(按照業務類型分)】@Cacheable({"category"})//代表當前方法的結果需要緩存,如果緩存中有,則不調用此方法@Overridepublic List<CategoryEntity> getLevel1Categorys() {System.out.println("調用了getLevel1Categorys...");// 查詢父id=0return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));}

自定義調整緩存配置

 /*** 啟動Cacheable注釋能進行緩存的使用 是有默認值的* key自動生產名字* 緩存的值默認使用jdk序列化* 默認ttl時間 -1* * 則這三樣內容我們肯定要進行自定義的* 注解中定義了 key值 通過表達式或者字符串的形式定義key值* 緩存時間則需要在配置文件中進行修改* 為了更好的遷移性 在緩存信息保存為json形式,調整格式時,就需要寫類調整配置類了* @return*/// 每一個需要緩存的數據我們都來指定要放到那個名字的緩存【緩存的分區(按照業務類型分)】@Cacheable(value = {"category"},key = "'level1Categorys'")//代表當前方法的結果需要緩存,如果緩存中有,則不調用此方法@Overridepublic List<CategoryEntity> getLevel1Categorys() {System.out.println("調用了getLevel1Categorys...");// 查詢父id=0return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));}
自定義配置類
package com.atguigu.gulimall.product.config;import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** 通過注解 @EnableConfigurationProperties 可以綁定配置類* 但為什么我們要通過注解的形式拿到該類(CacheProperties)和該類的值呢,因為CacheProperties并不是受spring容器管理,則通過這種形式導入*/
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
//把啟動注解放在我們的自己自定義緩存配置上面
@EnableCaching
//自定義緩存配置
public class MyCacheConfig {//在類CacheProperties的源碼我們可以看到/*** @ConfigurationProperties(prefix = "spring.cache") 是通過這個前綴和自己的屬性進行值匹配的** 然后為了讓這個類生效 我們就加了頂部的 @EnableConfigurationProperties(CacheProperties.class)*///@Autowired 可以通過屬性的方式得到//CacheProperties cache//這里是該redis的相關配置,要該其他的,導入其他的配置類在按這要寫就可以了@Bean                                         //放在參數上面 也是可以直接導入值的RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){//拿到最原始的實例,然后進行修改覆蓋即可RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();//設置key的序列化方式config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));//主要就是把默認jdk序列化的形式 轉為jsonconfig = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));CacheProperties.Redis redis = cacheProperties.getRedis();//將配置文件中的所以配置都生效if(redis.getTimeToLive()!=null){config = config.entryTtl(redis.getTimeToLive());}if(redis.getKeyPrefix()!=null){config = config.prefixKeysWith(redis.getKeyPrefix());}if(!redis.isCacheNullValues()){config = config.disableCachingNullValues();}if(!redis.isUseKeyPrefix()){config = config.disableKeyPrefix();}return config;}}
再看下我們的配置文件中進行了那些配置
spring.cache.type=redis
spring.cache.redis.time-to-live=360000
#指定我們的緩存前綴,如果沒有就用緩存的名字作為前綴
spring.cache.redis.key-prefix=CACHE_
#但需要手動開啟
spring.cache.redis.use-key-prefix=false
#防止緩存擊穿 是否緩存空值
spring.cache.redis.cache-null-values=true

數據更新時,緩存注釋的操作

    /*** 級聯更新所有關聯的數據* @param category*/                                                      //注意key為固定字符串時一定要加單引號,不然會被當作動態的表達式//刪除單個指定key緩存 @CacheEvict(value = "category", key = "'getLevel1Categorys'")//寫的方法上加入該注解@CacheEvict 進行的失效模式,刪除之前的緩存//刪除多個指定key緩存 則要用到注解 @Caching 可以進行組合操作@Caching(evict = {@CacheEvict(value = "category", key = "'getLevel1Categorys'"),@CacheEvict(value = "category", key = "'getCatalogJson'")})//直接進行分區刪除緩存 更為方便@CacheEvict(value = "category",allEntries = true)//如果數據返回值時 用注解CachePut 則會把數據寫入緩存中@CachePut@Transactional@Overridepublic void updateCascade(CategoryEntity category) {this.updateById(category);categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/913476.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/913476.shtml
英文地址,請注明出處:http://en.pswp.cn/news/913476.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

大語言模型全方位解析:從基礎認知到RESTful API應用

文章目錄 前言一、初見大模型1.1 大語言模型基本知識了解&#xff08;一&#xff09;日常可能用到的大語言模型&#xff08;二&#xff09;大模型的作用&#xff08;三&#xff09;核心價值 1.2 大模型與人工智能關系1.3 大語言模型的“前世今生”與發展1.3.1 大語言模型的發展…

網安系列【11】之目錄穿越與文件包含漏洞詳解

文章目錄 前言一 目錄穿越漏洞1.1 什么是目錄穿越&#xff1f;1.2 目錄穿越的原理1.3 目錄穿越的常見形式1.3.1 基本形式1.3.2 編碼繞過1.3.3 絕對路徑攻擊 1.4 實戰案例解析1.4.1 案例1&#xff1a;簡單的目錄穿越1.4.2 案例2&#xff1a;編碼繞過 1.5 目錄穿越的危害 二、文件…

uri-url-HttpServletRequest

1. 使用HttpServletRequest UrlPathHelper 解析 出 url路徑 org.springframework.web.util.UrlPathHelper 是 Spring 框架中用于處理 HTTP 請求路徑的一個工具類&#xff0c;它幫助解析和處理與請求路徑相關的細節。特別是 getLookupPathForRequest(HttpServletRequest request…

Ubuntu22.04安裝p4顯卡 nvidia-utils-570-server 570.133.20驅動CUDA Version: 12.8

Ubuntu22.04安裝p4顯卡 nvidia-utils-570-server 570.133.20驅動CUDA Version: 12.8專業顯卡就是專業顯卡&#xff0c;盡管p4已經掉到了白菜價&#xff0c;官方的支持卻一直都保持&#xff0c;比如它可以裝上cuda12.8,這真的出乎我意料。NVIDIA Tesla P4顯卡的主要情況Pascal架…

工業日志AI大模型智能分析系統-前端實現

目錄 主要架構 前端項目結構 1. 核心實現代碼 1.1 API服務封裝 (src/api/log.ts) 1.2 TS類型定義 (src/types/api.ts) 1.3 Pinia狀態管理 (src/stores/logStore.ts) 1.4 日志分析頁面 (src/views/LogAnalysis.vue) 1.5 日志詳情組件 (src/components/LogDetail.vue) 2…

C++內存泄漏排查

引言 C內存泄漏問題的普遍性與危害內存泄漏排查大賽的背景與目標文章結構和主要內容概述 內存泄漏的基本概念 內存泄漏的定義與類型&#xff08;顯式、隱式、循環引用等&#xff09;C中常見的內存泄漏場景&#xff08;指針管理不當、資源未釋放等&#xff09;內存泄漏對程序性能…

20250706-4-Docker 快速入門(上)-常用容器管理命令_筆記

一、常用管理命令1. 選項&#xfeff;&#xfeff;1&#xff09;ls&#xfeff;功能&#xff1a;列出容器常用參數&#xff1a;-a&#xff1a;查看所有容器包含退出的-q&#xff1a;列出所有容器ID-l&#xff1a;列出最新創建的容器狀態使用技巧&#xff1a;容器很多時使用dock…

基于 Camunda BPM 的工作流引擎示例項目

項目介紹 這是一個基于 Camunda BPM 的工作流引擎示例項目&#xff0c;包含完整的后臺接口和前端頁面&#xff0c;實現了流程的設計、部署、執行等核心功能。 技術棧 后端 Spring Boot 2.7.9Camunda BPM 7.18.0MySQL 8.0JDK 1.8 前端 Vue 3Element PlusBpmn.jsVite 功能…

Day06_刷題niuke20250707

試卷01&#xff1a; 單選題 C 1. 在C中,一個程序無論由多少個源程序文件組成,其中有且僅有一個主函數main().說法是否正確&#xff1f; A 正確 B 錯誤 正確答案&#xff1a;A 官方解析&#xff1a; 在C程序設計中,一個完整的程序確實有且僅有一個main函數作為程序的入口點,這…

洛谷 P5788 【模板】單調棧

題目背景模板題&#xff0c;無背景。2019.12.12 更新數據&#xff0c;放寬時限&#xff0c;現在不再卡常了。題目描述給出項數為 n 的整數數列 a1…n?。定義函數 f(i) 代表數列中第 i 個元素之后第一個大于 ai? 的元素的下標&#xff0c;即 f(i)mini<j≤n,aj?>ai??{…

linux系統運行時_安全的_備份_還原_方法rsync

1.問題與需求 問題: 新部署的機器設備(主控RK3588), 沒有經過燒錄定制鏡像, 研發部署, 直接組裝發送到客戶現場需要通過frpc遠程部署: 安裝ros2 python包 docker鏡像 環境配置 自啟動配置 SN設備信息寫自動部署腳本, 實現一鍵部署升級無奈物聯網卡做了白名單限制, apt 和…

18套精美族譜Excel模板,助力家族文化傳承!

【資源分享】18套精美族譜Excel模板&#xff0c;助力家族文化傳承&#xff01; &#x1f3af; 本文分享一套完整的家族譜系資源&#xff0c;包含18個精心設計的Excel模板&#xff0c;從基礎模板到專業圖表&#xff0c;滿足各類家族的族譜制作需求。 一、為什么要制作族譜&…

MySQL Galera Cluster企業級部署

一、MySQL Galera Cluster簡介 主要特點 同步復制&#xff1a; 所有的寫操作&#xff08;包括插入、更新、刪除&#xff09;在集群中的所有節點上都是同步的。這意味著每個節點上的數據是完全一致的。 多主節點&#xff1a; 集群中的每個節點都是主節點。所有節點都可以處理讀…

HTTP 重定向

什么是 HTTP 重定向&#xff1f; HTTP 重定向&#xff08;HTTP Redirect&#xff09; 是服務器向客戶端&#xff08;通常是瀏覽器&#xff09;發出的指令&#xff0c;告訴客戶端某個請求的資源已被移到新的位置。重定向通常通過發送一個特殊的 HTTP 狀態碼&#xff08;例如 3x…

本地加載非在線jar包設置

項目中存在私有jar包&#xff0c;提示在線獲取不到&#xff0c;需要先獲取到完整的jar包在打進maven中再在項目中進行maven依賴引入 mvn install:install-file -DfileD:\tools\maven\apache-maven-3.5.2\local_repository2\org\ahjk\SixCloudCommon\1.0\SixCloudCommon-1.0-SN…

Codeforces Round 979 (Div. 2)

A c[1]-b[1]0&#xff0c;之后每個c[1]-b[1]最大都是maxa-mina&#xff0c;最大和最小放前兩個 B ans2^(a1)-2^s-1&#xff0c;1一個最小 C 我們可以把式子化為(....)||(....)||(....)括號里沒有||&#xff0c;如果括號全是1那么A贏&#xff0c;A盡量選擇把1選在一起 D …

UI前端大數據處理性能瓶頸突破:分布式計算框架的應用

hello寶子們...我們是艾斯視覺擅長ui設計、前端開發、數字孿生、大數據、三維建模、三維動畫10年經驗!希望我的分享能幫助到您!如需幫助可以評論關注私信我們一起探討!致敬感謝感恩!一、引言&#xff1a;前端大數據處理的性能困境與破局之路在數據爆炸增長的時代&#xff0c;UI…

病蟲害數據集

數據是泰迪杯主辦方提供的已經標記好的數據&#xff0c;4k畫質的圖片&#xff0c;總大小8個G 鏈接&#xff1a;https://pan.baidu.com/s/1fvmNHGrLvflEovjfCjDLOw?pwd6666 提取碼&#xff1a;6666 蟲害包括&#xff1a; 八點灰燈蛾 褐飛虱屬 白背飛虱 二化螟 蟋蟀 黃足…

JAVA基礎:關于JDK環境變量設置的若干相關細節及注意事項

一、JDK下載安裝 網址&#xff1a;https://www.oracle.com/java/technologies/downloads/ 以 win11 為例&#xff0c;根據網址下載安裝包后&#xff0c;點擊安裝&#xff0c;注意設置安裝路徑 二、基礎常識 1.Java三大使用平臺 Java SE(Java Standard Edition): 標準版&…

C++高頻知識點(四)

文章目錄 16. 虛基類要解決什么問題&#xff1f;17. C中如何進行類型轉換操作&#xff1f;列舉并解釋四種類型轉換方式。18. 什么是函數重載&#xff1f;如何進行函數重載&#xff1f;19. 解釋C中的友元函數和友元類&#xff0c;并解釋其使用場景。友元函數友元類 20. 請解釋C中…