diff --git a/kkmall-product/pom.xml b/kkmall-product/pom.xml index 85725b5..eb2b97e 100644 --- a/kkmall-product/pom.xml +++ b/kkmall-product/pom.xml @@ -47,6 +47,17 @@ org.springframework.boot spring-boot-starter-data-redis + + + org.redisson + redisson + 3.12.5 + + + + org.springframework.boot + spring-boot-starter-cache + org.springframework.boot spring-boot-devtools diff --git a/kkmall-product/src/main/java/name/lkk/kkmall/product/config/MallCacheConfig.java b/kkmall-product/src/main/java/name/lkk/kkmall/product/config/MallCacheConfig.java new file mode 100644 index 0000000..2218f09 --- /dev/null +++ b/kkmall-product/src/main/java/name/lkk/kkmall/product/config/MallCacheConfig.java @@ -0,0 +1,57 @@ +package name.lkk.kkmall.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; + +/** + * @author: kirklin + * @date: 2021/6/24 3:32 下午 + * @description:Spring Cache配置 + */ +@Configuration +@EnableCaching +@EnableConfigurationProperties(CacheProperties.class) +public class MallCacheConfig { + /** + * 配置文件中 TTL设置没用上 + * + * 原来: + * @ConfigurationProperties(prefix = "spring.cache") + * public class CacheProperties + * + * 现在要让这个配置文件生效 : @EnableConfigurationProperties(CacheProperties.class) + * + */ + @Bean + RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){ + + RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); + + // 设置kv的序列化机制 + 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; + } +} diff --git a/kkmall-product/src/main/java/name/lkk/kkmall/product/config/MallRedissonConfig.java b/kkmall-product/src/main/java/name/lkk/kkmall/product/config/MallRedissonConfig.java new file mode 100644 index 0000000..371dfd7 --- /dev/null +++ b/kkmall-product/src/main/java/name/lkk/kkmall/product/config/MallRedissonConfig.java @@ -0,0 +1,29 @@ +package name.lkk.kkmall.product.config; + +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author: kirklin + * @date: 2021/6/23 8:43 下午 + * @description: + */ +@Configuration +public class MallRedissonConfig { + + @Value("${spring.redis.host}") + private String redisHost; + @Value("${spring.redis.port}") + private String redisPort; + + @Bean(destroyMethod = "shutdown") + public RedissonClient redisson() { + Config config = new Config(); + config.useSingleServer().setAddress("redis://"+redisHost+":"+redisPort); + return Redisson.create(config); + } +} diff --git a/kkmall-product/src/main/java/name/lkk/kkmall/product/service/impl/CategoryServiceImpl.java b/kkmall-product/src/main/java/name/lkk/kkmall/product/service/impl/CategoryServiceImpl.java index e6a8a18..cebe060 100644 --- a/kkmall-product/src/main/java/name/lkk/kkmall/product/service/impl/CategoryServiceImpl.java +++ b/kkmall-product/src/main/java/name/lkk/kkmall/product/service/impl/CategoryServiceImpl.java @@ -14,7 +14,11 @@ import name.lkk.kkmall.product.service.CategoryBrandRelationService; import name.lkk.kkmall.product.service.CategoryService; import name.lkk.kkmall.product.vo.Catalog3Vo; import name.lkk.kkmall.product.vo.Catelog2Vo; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.data.redis.core.script.DefaultRedisScript; @@ -36,6 +40,9 @@ public class CategoryServiceImpl extends ServiceImpl params) { IPage page = this.page( @@ -83,7 +90,9 @@ public class CategoryServiceImpl extends ServiceImpl paths = new ArrayList<>(); @@ -92,7 +101,37 @@ public class CategoryServiceImpl extends ServiceImpl findParentPath(Long catlogId, List paths) { + // 1、收集当前节点id + paths.add(catlogId); + CategoryEntity byId = this.getById(catlogId); + if (byId.getParentCid() != 0) { + findParentPath(byId.getParentCid(), paths); + } + return paths; + } + /** + * 级联更新所有数据 [分区名默认是就是缓存的前缀] SpringCache: 不加锁 + * + * @CacheEvict: 缓存失效模式 --- 页面一修改 然后就清除这两个缓存 + * key = "'getLevel1Categorys'" : 记得加单引号 [子解析字符串] + * + * @Caching: 同时进行多种缓存操作 + * + * @CacheEvict(value = {"category"}, allEntries = true) : 删除这个分区所有数据 + * + * @CachePut: 这次查询操作写入缓存 + */ +// @Caching(evict = { +// @CacheEvict(value = {"category"}, key = "'getLevel1Categorys'"), +// @CacheEvict(value = {"category"}, key = "'getCatelogJson'") +// }) + @CacheEvict(value = {"category"}, allEntries = true) +// @CachePut @Transactional @Override public void updateCascade(CategoryEntity category) { @@ -100,6 +139,21 @@ public class CategoryServiceImpl extends ServiceImpl getLevel1Categories() { return baseMapper.selectList(new QueryWrapper().eq("cat_level", 1)); @@ -113,12 +167,43 @@ public class CategoryServiceImpl extends ServiceImpl item.getParentCid().equals(parentCid)).collect(Collectors.toList()); } - /** - * 从缓存中查询CatelogJson - * @return - */ + + + @Cacheable(value = {"category"}, key = "#root.methodName") @Override public Map> getCatelogJson() { + List entityList = baseMapper.selectList(null); + // 查询所有一级分类 + List level1 = getCategoryEntities(entityList, 0L); + Map> parent_cid = level1.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> { + // 拿到每一个一级分类 然后查询他们的二级分类 + List entities = getCategoryEntities(entityList, v.getCatId()); + List catelog2Vos = null; + if (entities != null) { + catelog2Vos = entities.stream().map(l2 -> { + Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), l2.getName(), l2.getCatId().toString(), null); + // 找当前二级分类的三级分类 + List level3 = getCategoryEntities(entityList, l2.getCatId()); + // 三级分类有数据的情况下 + if (level3 != null) { + List catalog3Vos = level3.stream().map(l3 -> new Catalog3Vo(l3.getCatId().toString(), l3.getName(), l2.getCatId().toString())).collect(Collectors.toList()); + catelog2Vo.setCatalog3List(catalog3Vos); + } + return catelog2Vo; + }).collect(Collectors.toList()); + } + return catelog2Vos; + })); + return parent_cid; + } + + + /** + * 自定义实现,从缓存中查询CatelogJson + * @return + */ +// @Override + public Map> getCatelogJsonManual() { /** * 1.空结果缓存 解决缓存穿透 @@ -140,6 +225,27 @@ public class CategoryServiceImpl extends ServiceImpl> getCatelogJsonFromDBWithRedissonLock() { + + // 这里只要锁的名字一样那锁就是一样的 + // 关于锁的粒度 具体缓存的是某个数据 例如: 11-号商品 product-11-lock + RLock lock = redissonClient.getLock("CatelogJson-lock"); + lock.lock(); + + Map> data; + try { + data = getCatelogJsonFormDB(); + } finally { + lock.unlock(); + } + return data; + } + /** * 分布式锁 * @@ -262,16 +368,5 @@ public class CategoryServiceImpl extends ServiceImpl findParentPath(Long catlogId, List paths) { - // 1、收集当前节点id - paths.add(catlogId); - CategoryEntity byId = this.getById(catlogId); - if (byId.getParentCid() != 0) { - findParentPath(byId.getParentCid(), paths); - } - return paths; - } + } \ No newline at end of file diff --git a/kkmall-product/src/main/java/name/lkk/kkmall/product/web/IndexController.java b/kkmall-product/src/main/java/name/lkk/kkmall/product/web/IndexController.java index 33adbef..8e89652 100644 --- a/kkmall-product/src/main/java/name/lkk/kkmall/product/web/IndexController.java +++ b/kkmall-product/src/main/java/name/lkk/kkmall/product/web/IndexController.java @@ -3,15 +3,19 @@ package name.lkk.kkmall.product.web; import name.lkk.kkmall.product.entity.CategoryEntity; import name.lkk.kkmall.product.service.CategoryService; import name.lkk.kkmall.product.vo.Catelog2Vo; +import org.redisson.api.*; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.util.List; import java.util.Map; +import java.util.UUID; /** * @author: kirklin @@ -24,12 +28,24 @@ public class IndexController { @Autowired private CategoryService categoryService; - @RequestMapping("/hello") - @ResponseBody - public String hello(){ - return "Hello"; - } + @Autowired + private RedissonClient redissonClient; + @Autowired + private StringRedisTemplate stringRedisTemplate; + + +// @RequestMapping("/hello") +// @ResponseBody +// public String hello(){ +// return "Hello"; +// } + + /** + * 首页转发 + * @param model + * @return + */ @RequestMapping({"/", "index", "/index.html"}) public String indexPage(Model model) { // 获取一级分类所有缓存 @@ -49,4 +65,122 @@ public class IndexController { Map> map = categoryService.getCatelogJson(); return map; } + + + /** + * RLock锁有看门狗机制 会自动帮我们续期,默认三秒自动过期 + * lock.lock(10,TimeUnit.SECONDS); + * 锁的时间一定要大于业务的时间 否则会出现死锁的情况 + * + * 如果我们传递了锁的超时时间就给redis发送超时脚本 默认超时时间就是我们指定的 + * 如果我们未指定,就使用 30 * 1000 [LockWatchdogTimeout] + * 只要占锁成功 就会启动一个定时任务 任务就是重新给锁设置过期时间 + * 这个时间还是 [LockWatchdogTimeout] 的时间 1/3 看门狗的时间续期一次 续成满时间 + */ + @ResponseBody + @RequestMapping("/index/hello") + public String hello() { + RLock lock = redissonClient.getLock("my-lock"); + // 阻塞式等待 + lock.lock(); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + return "hello"; + } + //============================================信号量========================================= + /** + * 尝试获取车位 [信号量] + * 信号量:也可以用作限流 + */ + @ResponseBody + @GetMapping("/index/park") + public String park() { + + RSemaphore park = redissonClient.getSemaphore("park"); + boolean acquire = park.tryAcquire(); + return "获取车位 =>" + acquire; + } + + /** + * 尝试获取车位 + */ + @ResponseBody + @GetMapping("/index/go/park") + public String goPark() { + + RSemaphore park = redissonClient.getSemaphore("park"); + park.release(); + return "ok => 车位+1"; + } +//============================================读写锁========================================= + /** + * 读写锁-读锁 + */ + @GetMapping("/index/read") + @ResponseBody + public String readValue() { + RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock"); + RLock rLock = lock.readLock(); + String s = ""; + rLock.lock(); + try { + s = stringRedisTemplate.opsForValue().get("writeValue"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + rLock.unlock(); + } + return s; + } + /** + * 读写锁-写锁 + */ + @GetMapping("/index/write") + @ResponseBody + public String writeValue() { + RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock"); + RLock rLock = lock.writeLock(); + String s = ""; + try { + rLock.lock(); + s = UUID.randomUUID().toString(); + Thread.sleep(3000); + stringRedisTemplate.opsForValue().set("writeValue", s); + } catch (Exception e) { + e.printStackTrace(); + } finally { + rLock.unlock(); + } + return s; + } +//============================================闭锁========================================= + /** + * 闭锁 只有设定的人全通过才关门 + */ + @ResponseBody + @GetMapping("/index/lockDoor") + public String lockDoor() throws InterruptedException { + RCountDownLatch door = redissonClient.getCountDownLatch("door"); + // 设置这里有5个人 + door.trySetCount(5); + door.await(); + + return "5个人全部通过了..."; + } + + @ResponseBody + @GetMapping("/index/go/{id}") + public String go(@PathVariable("id") Long id) throws InterruptedException { + + RCountDownLatch door = redissonClient.getCountDownLatch("door"); + // 每访问一次相当于出去一个人 + door.countDown(); + return id + "走了"; + } + } diff --git a/kkmall-product/src/main/resources/application.yml b/kkmall-product/src/main/resources/application.yml index 7ada551..dbbcbac 100644 --- a/kkmall-product/src/main/resources/application.yml +++ b/kkmall-product/src/main/resources/application.yml @@ -1,8 +1,9 @@ +ipAddr: localhost spring: datasource: #MySQL配置 driverClassName: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3306/kkmall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai + url: jdbc:mysql://${ipAddr}:3306/kkmall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 66CcFf!! cloud: @@ -18,11 +19,26 @@ spring: static-path-pattern: /static/** thymeleaf: cache: true + suffix: .html + prefix: classpath:/templates/ + redis: - host: localhost + host: ${ipAddr} port: 6379 password: linkekun + # 设置缓存类型 + cache: + type: redis + # 设置存活时间 + redis: + time-to-live: 3600000 + # 如果指定了前缀就用我们指定的 如果没有就用缓存的名字作为前缀 + # key-prefix: CACHE_ + # 是否缓存空值,解决缓存穿透问题 + cache-null-values: true +# cache-names: + mybatis-plus: mapper-locations: classpath:/mapper/**/*.xml global-config: diff --git a/kkmall-product/src/main/resources/templates/index.html b/kkmall-product/src/main/resources/templates/index.html index 55cda4d..51f9da3 100644 --- a/kkmall-product/src/main/resources/templates/index.html +++ b/kkmall-product/src/main/resources/templates/index.html @@ -93,20 +93,20 @@ @@ -122,7 +122,7 @@
@@ -580,13 +580,13 @@