三级菜单加缓存及增加本地锁方案(适用于单体应用模式。不适合分布式)

This commit is contained in:
Kirk Lin 2021-06-23 16:56:23 +08:00
parent a8477e603d
commit 7131d381e0
5 changed files with 122 additions and 2 deletions

View file

@ -54,7 +54,7 @@ spring:
filters:
- RewritePath=/api/(?<segment>.*), /renren-fast/$\{segment}
# 任何以mall.com结尾的域名转发到mall-product
# 任何以kkmall.com结尾的域名转发到kkmall-product
# 注意Nginx代理给网关的时候会丢失请求Host
- id: kkmall_route
uri: lb://kkmall-product

View file

@ -42,6 +42,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 使用Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>

View file

@ -1,5 +1,7 @@
package name.lkk.kkmall.product.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@ -12,10 +14,15 @@ import name.lkk.kkmall.product.service.CategoryService;
import name.lkk.kkmall.product.vo.Catalog3Vo;
import name.lkk.kkmall.product.vo.Catelog2Vo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -25,6 +32,9 @@ public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity
@Autowired
private CategoryBrandRelationService categoryBrandRelationService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public PageUtils queryPage(Map<String, Object> params) {
IPage<CategoryEntity> page = this.page(
@ -102,9 +112,47 @@ public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity
return entityList.stream().filter(item -> item.getParentCid().equals(parentCid)).collect(Collectors.toList());
}
/**
* 从缓存中查询CatelogJson
* @return
*/
@Override
public Map<String, List<Catelog2Vo>> getCatelogJson() {
/**
* 1.空结果缓存 解决缓存穿透
* 2.设置过期时间(加随机值) 解决缓存雪崩
* 3.加锁 解决缓存击穿
*/
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
Map<String, List<Catelog2Vo>> catelogJson;
// 缓存中没有数据
String catelogJSON = operations.get("catelogJSON");
if (ObjectUtils.isEmpty(catelogJSON)) {
catelogJson = getCatelogJsonFromDBWithLocalLock();
} else {
System.out.println("缓存命中。。。查询缓存");
catelogJson = JSON.parseObject(catelogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
}
return catelogJson;
}
/**
* 从数据库中查询CatelogJson
* @return
*/
public Map<String, List<Catelog2Vo>> getCatelogJsonFormDB() {
String catelogJSON = stringRedisTemplate.opsForValue().get("catelogJSON");
if (!ObjectUtils.isEmpty(catelogJSON)) {
return JSON.parseObject(catelogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
}
// 优化将查询变为一次
List<CategoryEntity> entityList = baseMapper.selectList(null);
// 查询所有一级分类
List<CategoryEntity> level1 = getCategoryEntities(entityList, 0L);
Map<String, List<Catelog2Vo>> parent_cid = level1.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
@ -126,9 +174,53 @@ public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity
}
return catelog2Vos;
}));
// 优化查询到数据库就在锁还没结束之前放入缓存
stringRedisTemplate.opsForValue().set("catelogJSON", JSON.toJSONString(parent_cid), 1, TimeUnit.DAYS);
return parent_cid;
}
/**
* redis没有数据 查询DB [本地锁解决方案]
*/
public Map<String, List<Catelog2Vo>> getCatelogJsonFromDBWithLocalLock() {
String catelogJSON = stringRedisTemplate.opsForValue().get("catelogJSON");
if (!ObjectUtils.isEmpty(catelogJSON)) {
return JSON.parseObject(catelogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
});
}
synchronized (this) {
System.out.println("缓存未命中。。。查询数据库。[本地锁解决方案]");
// 优化将查询变为一次
List<CategoryEntity> entityList = baseMapper.selectList(null);
// 查询所有一级分类
List<CategoryEntity> level1 = getCategoryEntities(entityList, 0L);
Map<String, List<Catelog2Vo>> parent_cid = level1.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
// 拿到每一个一级分类 然后查询他们的二级分类
List<CategoryEntity> entities = getCategoryEntities(entityList, v.getCatId());
List<Catelog2Vo> catelog2Vos = null;
if (entities != null) {
catelog2Vos = entities.stream().map(l2 -> {
Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), l2.getName(), l2.getCatId().toString(), null);
// 找当前二级分类的三级分类
List<CategoryEntity> level3 = getCategoryEntities(entityList, l2.getCatId());
// 三级分类有数据的情况下
if (level3 != null) {
List<Catalog3Vo> 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;
}));
// 优化查询到数据库就在锁还没结束之前放入缓存
stringRedisTemplate.opsForValue().set("catelogJSON", JSON.toJSONString(parent_cid), 1, TimeUnit.DAYS);
return parent_cid;
}
}
/**
* 递归收集所有节点
*/

View file

@ -5,7 +5,6 @@ spring:
url: jdbc:mysql://localhost:3306/kkmall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 66CcFf!!
cloud:
nacos:
discovery:
@ -19,6 +18,10 @@ spring:
static-path-pattern: /static/**
thymeleaf:
cache: true
redis:
host: localhost
port: 6379
password: linkekun
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
@ -32,3 +35,7 @@ mybatis-plus:
#log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
server:
port: 10000
logging:
level:
name.lkk.kkmall: error

View file

@ -2,9 +2,14 @@ package name.lkk.kkmall.product;
import name.lkk.kkmall.product.entity.BrandEntity;
import name.lkk.kkmall.product.service.impl.BrandServiceImpl;
import org.junit.jupiter.api.DisplayName;
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.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.util.UUID;
@SpringBootTest
class KkmallProductApplicationTests {
@ -12,6 +17,17 @@ class KkmallProductApplicationTests {
@Autowired
BrandServiceImpl brandService;
@Autowired
StringRedisTemplate stringRedisTemplate;
@Test
@DisplayName("测试StringRedisTemplate")
public void TestStringRedisTemplate(){
ValueOperations<String, String> stringStringValueOperations = stringRedisTemplate.opsForValue();
stringStringValueOperations.set("Hello","KK"+ UUID.randomUUID());
System.out.println(stringStringValueOperations.get("Hello"));
}
@Test
void contextLoads() {
// BrandEntity brandEntity = new BrandEntity();