文章目录

  • 第2章 缓存解决方案
    • 学习目标
  • 1. SpringDataRedis
    • 1.1 SpringDataRedis简介
    • 1.2 SpringDataRedis快速入门
      • 1.2.1 准备工作
      • 1.2.2 值类型操作
      • 1.2.3 Set类型操作
      • 1.2.4 List类型操作
      • 1.2.5 Hash类型操作
      • 1.2.6 ZSet类型操作
      • 1.2.7 过期时间设置
  • 2. 缓存穿透、缓存击穿、缓存雪崩
    • 2.1 缓存穿透
    • 2.2 缓存击穿
    • 2.3 缓存雪崩
  • 3. 商品分类导航缓存
    • 3.1 需求分析
    • 3.2 实现思路
    • 3.3 代码实现
      • 3.3.1 通用模块整合spring data redis
      • 3.3.2 商品分类加载到缓存
      • 3.3.3 查询商品分类缓存
      • 3.3.4 更新商品分类缓存
  • 4. 广告轮播图缓存
    • 4.1 需求分析
    • 4.2 实现思路
    • 4.3 代码实现
      • 4.3.1 广告数据加载到缓存
      • 4.3.2 查询广告缓存
      • 4.3.3 更新广告缓存
  • 5. 商品详细页价格缓存
    • 5.1 需求分析
    • 5.2 实现思路
    • 5.3 代码实现
      • 5.3.1 价格数据加载到缓存
      • 5.3.2 查询价格缓存
      • 5.3.3 更新价格缓存
      • 5.3.4 删除价格缓存

第2章 缓存解决方案

学习目标

掌握SpringDataRedis 的常用操作
能够理解并说出什么是缓存穿透、缓存击穿、缓存雪崩,以及对应的解决方案
使用缓存预热的方式实现商品分类导航缓存
使用缓存预热的方式实现广告轮播图缓存
使用缓存预热的方式实现商品价格缓存
项目序列-10:https://github.com/Jonekaka/javaweb-qingcheng-10-85

1. SpringDataRedis

1.1 SpringDataRedis简介

SpringDataRedis 属于Spring Data 家族一员,用于对redis的操作进行封装的框架

Spring Data ----- Spring 的一个子项目。Spring 官方提供一套数据层综合解决方案,用
于简化数据库访问,支持NoSQL和关系数据库存储。包括Spring Data JPA 、Spring
Data Redis 、SpringDataSolr 、SpringDataElasticsearch 、Spring DataMongodb 等
框架。

1.2 SpringDataRedis快速入门

1.2.1 准备工作

(1)构建Maven工程 SpringDataRedisDemo 引入Spring相关依赖、JUnit依赖、Jedis
和SpringDataRedis依赖

<!‐‐缓存‐‐>
<dependencies><!--spring-data-redis底层需要依赖jedis--><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.0</version></dependency><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId><version>2.0.5.RELEASE</version></dependency><!--单元测试--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><!--spring和junit整合的依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.5.RELEASE</version></dependency></dependencies>

(2)在src/main/resources下创建properties文件夹,建立redis-config.properties

redis主机配置,端口,密码,数据库,连接数,最大等待时长

redis.host=127.0.0.1redis.port=6379redis.pass=redis.database=0redis.maxIdle=300redis.maxWait=3000

maxIdle :最大空闲数
maxWaitMillis: 连接时的最大等待毫秒数
(3)在src/main/resources下创建spring文件夹,创建applicationContext-redis.xml
对redis进行配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"      xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!--读取路径下redis的属性文件--><context:property-placeholder location="classpath:redis-config.properties" /><!-- redis 相关配置 --><!--从属性文件中提取属性进行配置,配置连接池--><bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">  <property name="maxIdle" value="${redis.maxIdle}" />   <property name="maxWaitMillis" value="${redis.maxWait}" />  </bean>  <!--redis连接工厂--><bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:pool-config-ref="poolConfig"/>  <!--redis最为核心的类,对redis的存取值都需要此redisTemplate,相当于这就是redis的操作对象了,当需要redis的时候就根据需要注入,调用了redis工厂,层层向上封装--><bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">  <property name="connectionFactory" ref="jedisConnectionFactory" /></bean></beans>

接下来演示redis如何增删改查数据

1.2.2 值类型操作

运行redis服务器,windows上也能运行
一个服务器,一个客户端,客户端可以连接其他机器进行操作

/*** @ClassName redis值测试* Description TODO**/
/*标识这是一个单元测试类*/
@RunWith(SpringJUnit4ClassRunner.class)
/*指定redis配置文件*/
@ContextConfiguration(locations = "classpath:applicationContext-redis.xml")
public class TestValue {/*将redis操作对象注入进来*/@Autowiredprivate RedisTemplate redisTemplate;/*存值,取值,删值*//*** @Description 存值,key,value* @Param []* @return void**/@Testpublic void setValue() {redisTemplate.boundValueOps("name").set("lili");}/*** @Description 取值* @Param []* @return void**/@Testpublic void getValue() {String name = (String) redisTemplate.boundValueOps("name").get();System.out.println(name);}@Testpublic void del() {redisTemplate.delete("name");}
}

测试存值

取值

1.2.3 Set类型操作

当存入的是集合,且没有顺序要求,可以使用set
添加顺序到输出顺序不一致

/*** @ClassName redis值测试* Description TODO**/
/*标识这是一个单元测试类*/
@RunWith(SpringJUnit4ClassRunner.class)
/*指定redis配置文件*/
@ContextConfiguration(locations = "classpath:applicationContext-redis.xml")
public class TestSet {/*将redis操作对象注入进来*/@Autowiredprivate RedisTemplate redisTemplate;/*存值,取值,删值*//*** @Description set,key,value* @Param []* @return void**/@Testpublic void setSet() {redisTemplate.boundSetOps("nameSet").add("心剑");redisTemplate.boundSetOps("nameSet").add("魔剑");redisTemplate.boundSetOps("nameSet").add("生死棋");}/*** @Description 取值* @Param []* @return void**/@Testpublic void getSet() {Set names = redisTemplate.boundSetOps("nameSet").members();System.out.println(names);}@Testpublic void del() {redisTemplate.delete("nameSet");}/*** @Description 删除其中一个* @Param []* @return void**/@Testpublic void delValue() {redisTemplate.boundSetOps("nameSet").remove("魔剑");}
}

1.2.4 List类型操作

可以保留原来的顺序
(1)右压栈 后添加的对象排在后边

(2)左压栈 后添加的对象排在前边

(3)根据索引查询元素

(4)移除指定个数的值

/*** @ClassName redis值测试* Description TODO**/
/*标识这是一个单元测试类*/
@RunWith(SpringJUnit4ClassRunner.class)
/*指定redis配置文件*/
@ContextConfiguration(locations = "classpath:applicationContext-redis.xml")
public class TestList {/*将redis操作对象注入进来*/@Autowiredprivate RedisTemplate<String, Integer> redisTemplate;/*** @return void* @Description 右压栈* @Param []**/@Testpublic void rightStack() {redisTemplate.boundListOps("nameList").rightPush(1);redisTemplate.boundListOps("nameList").rightPush(2);redisTemplate.boundListOps("nameList").rightPush(3);}@Testpublic void getRightStack() {/*range(0, 10)代表查询范围,0,-1代表查询所有,开始索引,查询条数*/List<Integer> nameList = redisTemplate.boundListOps("nameList").range(0, 10);System.out.println(nameList);}/*** @return void* @Description 左压栈* @Param []**/@Testpublic void leftStack() {redisTemplate.boundListOps("nameList").leftPush(1);redisTemplate.boundListOps("nameList").leftPush(2);redisTemplate.boundListOps("nameList").leftPush(3);}@Testpublic void getLeftStack() {/*range(0, 10)代表查询范围,0,-1代表查询所有,开始索引,查询条数*/List<Integer> nameList = redisTemplate.boundListOps("nameList").range(0, 10);System.out.println(nameList);}/*查询具体的某个索引的值*/@Testpublic void getIndexValue() {Integer nameList = redisTemplate.boundListOps("nameList").index(0);System.out.println(nameList);}/*移除某个索引的值,(移除的个数,值),个数<=>值,如果有三个值,移除4个,则三个都移除不报错,移除两个还有一个*/public void delValue() {/*代表移除2个1*/redisTemplate.boundListOps("nameList").remove(2, 1);}}

1.2.5 Hash类型操作

(1)存入值
(2)提取所有的KEY
(3)提取所有的值
(4)根据KEY提取值
(5)根据KEY移除值

/*** @ClassName redis值测试* Description TODO**/
/*标识这是一个单元测试类*/
@RunWith(SpringJUnit4ClassRunner.class)
/*指定redis配置文件*/
@ContextConfiguration(locations = "classpath:applicationContext-redis.xml")
public class TestHash {/*将redis操作对象注入进来*/@Autowiredprivate RedisTemplate redisTemplate;@Testpublic void testHash() {redisTemplate.boundHashOps("nameHash").put("a",1);redisTemplate.boundHashOps("nameHash").put("b",2);redisTemplate.boundHashOps("nameHash").put("c",3);redisTemplate.boundHashOps("nameHash").put("d",4);}@Testpublic void getKey() {Set nameHash = redisTemplate.boundHashOps("nameHash").keys();System.out.println(nameHash);}/*根据key查询出value*/@Testpublic void getValues() {//       List nameHash = redisTemplate.boundHashOps("nameHash").values();Integer o = (Integer) redisTemplate.boundHashOps("nameHash").get("a");System.out.println(o);}@Testpublic void delKey() {redisTemplate.boundHashOps("nameHash").delete("a");}
}

1.2.6 ZSet类型操作

因为有了分值,而有了排序,并非是输入顺序,而是绑定的值顺序
绑定的值是可以动态变化的,可以理解为主播榜,谁被刷的礼物多谁就靠前
如果值是用数据库保存,将会带来很大压力,常用缓存解决

zset是set的升级版本,它在set的基础上增加了一格顺序属性,这一属性在添加元素
的同时可以指定,每次指定后,zset会自动重新按照新的值调整顺序。
可以理解为有两列的mysql表,一列存储value,一列存储分值。
假设有abc三个人直播,受到礼物
(1)存值 ,指定分值
(2)查询,由低到高
(3)查询,由高到低,
(4)增加分数
(5)查询值和分数
TypedTuple是值与分数的封装。

/*** @ClassName redis值测试* Description TODO**/
/*标识这是一个单元测试类*/
@RunWith(SpringJUnit4ClassRunner.class)
/*指定redis配置文件*/
@ContextConfiguration(locations = "classpath:applicationContext-redis.xml")
public class TestZset {/*将redis操作对象注入进来*/@Autowiredprivate RedisTemplate redisTemplate;@Testpublic void setZset() {redisTemplate.boundZSetOps("nameZset").add("a", 100);redisTemplate.boundZSetOps("nameZset").add("b", 200);redisTemplate.boundZSetOps("nameZset").add("c", 10);}/*默认从低到高排序*/@Testpublic void getValue() {Set nameZset = redisTemplate.boundZSetOps("nameZset").range(0, -1);System.out.println(nameZset);}/*设定从高到低排序*/@Testpublic void getHighValue() {Set nameZset = redisTemplate.boundZSetOps("nameZset").reverseRange(0, -1);System.out.println(nameZset);}/*对数值变更,,增加礼物*/@Testpublic void updateScore() {redisTemplate.boundZSetOps("nameZset").incrementScore("a", 1000);}/*查询出键值全部信息,为一个对象组合,因此要循环才能得到*/@Testpublic void getALlinfo() {Set<ZSetOperations.TypedTuple> nameZset = redisTemplate.boundZSetOps("nameZset").reverseRangeWithScores(0, -1);/*此处是对象地址*/System.out.println(nameZset);//rg.springframework.data.redis.core.DefaultTypedTuple@ccb70423, org.springframework.for (ZSetOperations.TypedTuple typedTuple : nameZset) {System.out.println(typedTuple.getValue());System.out.println(typedTuple.getScore());}}

1.2.7 过期时间设置

以值类型为例:存值时指定过期时间和时间单位
都有过期方法
常用语短信验证码

/*** 存值*/
@Test
public void setValue(){redisTemplate.boundValueOps("name").set("aaa");redisTemplate.boundValueOps("name").expire(10,TimeUnit.SECONDS);}

2. 缓存穿透、缓存击穿、缓存雪崩

2.1 缓存穿透

持续的穿透了缓存去找数据库,带给缓存和数据库巨大的访问压力

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。如下面这段代码就存在缓存穿透的问题。
根据id查询价格

public Integer findPrice(Long id) {//从缓存中查询Integer sku_price =  (Integer)redisTemplate.boundHashOps("sku_price").get(id);if(sku_price==null){//缓存中没有,从数据库查询Sku sku = skuMapper.selectByPrimaryKey(id);if(sku!=null){ //如果数据库有此对象sku_price = sku.getPrice();redisTemplate.boundHashOps("sku_price").put(id,sku_price);}}return sku_price;}

解决方案:
1.接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
2.从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为
key-0。这样可以防止攻击用户反复用同一个id暴力攻击。代码举例:
redisTemplate.boundHashOps(“sku_price”).put(id,0);
如果数据库也没有,就把这个id绑定到缓存里,为0
下次再访问缓存就能返回数据了

public int findPrice(Long id) {//从缓存中查询Integer sku_price =(Integer)redisTemplate.boundHashOps("sku_price").get(id);if(sku_price==null){//缓存中没有,从数据库查询Sku sku = skuMapper.selectByPrimaryKey(id);if(sku!=null){ //如果数据库有此对象sku_price = sku.getPrice();redisTemplate.boundHashOps("sku_price").put(id,sku_price);}else{redisTemplate.boundHashOps("sku_price").put(id,0);}}return sku_price;}
  1. 使用缓存预热
    缓存预热就是将数据提前加入到缓存中,当数据发生变更,再将最新的数据更新到缓存。
    将数据访问压力转移到缓存,保护数据库
    后边我们就用缓存预热的方式实现对分类导航、广告轮播图等数据的缓存。

2.2 缓存击穿

特点:和穿透比起来一般没有id;穿透是有人攻击,但是击穿是常见现象

缓存击穿是指缓存中没有但数据库中有的数据。这时由于并发用户特别多,同时读
缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压
力。
以下代码可能会产生缓存击穿:
加过期时间是为了保证数据的更新,假如设定5分钟更新一次,那么每5分钟就存在一个击穿的风险

@Autowired
private RedisTemplate redisTemplate;
public List<Map> findCategoryTree() {//从缓存中查询List<Map> categoryTree= (List<Map>)redisTemplate.boundValueOps("categoryTree").get();//缓存中没有,压力转移到数据库if(categoryTree==null){Example example=new Example(Category.class);Example.Criteria criteria = example.createCriteria();criteria.andEqualTo("isShow","1");//显示List<Category> categories =categoryMapper.selectByExample(example);categoryTree=findByParentId(categories,0);//更新到缓存redisTemplate.boundValueOps("categoryTree").set(categoryTree);
//过期时间设置 ......}return categoryTree;}

解决方案:
1.设置热点数据永远不过期。
2.缓存预热

2.3 缓存雪崩

存在大量缓存,不止同一条数据,结果数据同时到期了,压力被转移到数据库

缓存雪崩是指缓存数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2.设置热点数据永远不过期。
3.使用缓存预热
缓存预热还是非常好的解决方案

3. 商品分类导航缓存

3.1 需求分析

为了提升首页的加载速度,减轻数据库访问压力,我们将首页的商品分类导航数据加载
在缓存中。

3.2 实现思路

为了避免缓存穿透、击穿等问题,我们采用缓存预热的方式实现对分类导航数据的缓
存。
考虑到商品分类导航数据不经常变动,所以我们不设置过期时间。

3.3 代码实现

缓存几乎在所有模块中都有遇到,因此放到公共服务模块

3.3.1 通用模块整合spring data redis

(1)qingcheng_common_service引入依赖

<!‐‐缓存‐‐>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring‐data‐redis</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>

(2)qingcheng_common_service新增配置文件 redis-config.properties

redis.host=127.0.0.1redis.port=6379redis.pass=redis.database=0redis.maxIdle=300redis.maxWait=3000

maxWait:连接池中连接用完时,新的请求等待时间,毫秒
maxIdle: 最大闲置个数
(3)qingcheng_common_service新增spring配置文件applicationContext-redis.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:cache="http://www.springframework.org/schema/cache"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/cache http://www.springframework.org/schema/beans/spring-cache.xsd"><!-- redis 相关配置 --> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">  <property name="maxIdle" value="${redis.maxIdle}" />   <property name="maxWaitMillis" value="${redis.maxWait}" />  </bean>  <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:pool-config-ref="poolConfig"/>  <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">  <property name="connectionFactory" ref="jedisConnectionFactory" /></bean></beans>  

(4)qingcheng_common_service工程新增枚举
创建枚举的目的是统一各个功能所用缓存的名称,防止因为名字不同犯低级调用错误

public enum CacheKey {AD,//广告SKU_PRICE,//价格CATEGORY_TREE;//商品分类导航树
}

至此spring data redis 已经整合完成,开始实现业务需求

3.3.2 商品分类加载到缓存

为缓存预热
(1)服务接口CategoryService新增方法定义

/*** 将商品分类树放入缓存*/
public void saveCategoryTreeToRedis();

(2)CategoryServiceImpl实现此方法

@Autowiredprivate RedisTemplate redisTemplate;public void saveCategoryTreeToRedis() {System.out.println("将商品分类数据加载到缓存");Example example = new Example(Category.class);Example.Criteria criteria = example.createCriteria();criteria.andEqualTo("isShow", "1");List<Category> categories = categoryMapper.selectByExample(example);/*将查询到的数据变换成树形结构*/List<Map> categoryTree = findByParentId(categories, 0);/*将格式化后的数据放到缓存中*/redisTemplate.boundValueOps(CacheKey.CATEGORY_TREE).set(categoryTree);}

(3)qingcheng_service_goods工程新增类
希望当此模块启动的时候自动对缓存预热
implements InitializingBean后继承此方法启动后要进行的逻辑
@Component加注解后表示这是bean

@Component
public class Init implements InitializingBean {@Autowiredprivate CategoryService categoryService;public void afterPropertiesSet() throws Exception {System.out.println("缓存预热");categoryService.saveCategoryTreeToRedis();//模块启动后自动加载商品分类导航缓存}
}

实现InitializingBean接口的类会在启动时自动调用。

3.3.3 查询商品分类缓存

修改CategoryServiceImpl的findCategoryTree方法 ,直接从缓存中提取数据。
上次从数据库中查找数据,然而已经不需要了

/*直接从缓存中读取数据*/public List<Map> findCategoryTree() {return (List<Map>) redisTemplate.boundValueOps(CacheKey.CATEGORY_TREE).get();}

查看是否可用
启动zk,redis服务
运行business,goods,portal
控制台中可以看到提示数据从缓存中取出

3.3.4 更新商品分类缓存

缓存中的数据当然也需要更新
修改CategoryServiceImpl的增删改方法,在增删改后重新加载缓存

/*** 新增* @param category*/
public void add(Category category) {categoryMapper.insert(category);saveCategoryTreeToRedis();}
/*** 修改* @param category*/
public void update(Category category) {categoryMapper.updateByPrimaryKeySelective(category);saveCategoryTreeToRedis();}
/*** 删除* @param id*/
public void delete(Integer id) {//判断是否存在下级分类
//......saveCategoryTreeToRedis();}

4. 广告轮播图缓存

4.1 需求分析

为了提升首页的加载速度,减轻数据库访问压力,我们将首页的广告轮播图数据加载在
缓存中。
然而,这里需要考虑的是所有广告

4.2 实现思路

使用“缓存预热”的方式实现
广告数据不只是轮播图,我们可以使用hash来存储广告数据。
hash本身有key ,value,位置-广告
大key用来存储广告,小key用来区分广告

4.3 代码实现

4.3.1 广告数据加载到缓存

这种更新只是局部的更新
(1)AdService新增方法定义
saveAdToRedisByPosition
更新广告的时候会调用

/*** 将某个位置的广告存入缓存* @param position*/
public void saveAdToRedisByPosition(String position);
/*** 将全部广告数据存入缓存*/
public void saveAllAdToRedis();

(2)AdServiceImpl方法实现
从数据库中查询某位置的广告,并装入缓存
查询所有的广告位置遍历位置装入缓存

@Autowired
private RedisTemplate redisTemplate;
public void saveAdToRedisByPosition(String position) {//查询某位置的广告列表Example example=new Example(Ad.class);Example.Criteria criteria = example.createCriteria();criteria.andEqualTo("position",position);criteria.andLessThanOrEqualTo("startTime",new Date());//开始时间小于等于当前时间criteria.andGreaterThanOrEqualTo("endTime",new Date());//截至时间大于等于当前时间criteria.andEqualTo("status","1");List<Ad> adList = adMapper.selectByExample(example);
//装入缓存redisTemplate.boundHashOps(CacheKey.AD).put(position,adList);}
/*** 返回所有的广告位置* @return*/
private List<String> getPositionList(){List<String> positionList=new ArrayList<String>();positionList.add("index_lb");//首页广告轮播图
//。。。return positionList;}
public void saveAllAdToRedis() {//循环所有的广告位置,将每个位置的广告封装入缓存for(String position:getPositionList()){saveAdToRedisByPosition(position);}}

(3)qingcheng_service_business工程新增类
启动该模块时自动装入该时段广告数据进入缓存

@Component
public class Init implements InitializingBean {@Autowiredprivate AdService adService;public void afterPropertiesSet() throws Exception {System.out.println("‐‐‐缓存预热‐‐‐");adService.saveAllAdToRedis();}
}

4.3.2 查询广告缓存

修改AdServiceImpl的findByPosition方法

public List<Ad> findByPosition(String position) {//从缓存中查询广告列表return(List<Ad>)redisTemplate.boundHashOps(CacheKey.AD).get(position);}

goods和business都参与了首页页面

4.3.3 更新广告缓存

缓存当然要面临更新问题
修改AdServiceImpl的增删改方法

/*** 新增* @param ad*/
public void add(Ad ad) {adMapper.insert(ad);saveAdByPosition(ad.getPosition());//重新加载缓存}
/*** 修改,可能对广告位置也进行了更新* @param ad*/
public void update(Ad ad) {//获取之前的广告位置String position =adMapper.selectByPrimaryKey(ad.getId()).getPosition();//对广告更新adMapper.updateByPrimaryKeySelective(ad);//位置变化才进行更新缓存if(!position.equals(ad.getPosition())){ //如果广告位置发生变化saveAdToRedisByPosition(ad.getPosition());//更新}}
/*** 删除,在删除之前更新缓存,不然没得查* @param id*/
public void delete(Integer id) {String position = adMapper.selectByPrimaryKey(id).getPosition();saveAdByPosition(position);//重新加载缓存adMapper.deleteByPrimaryKey(id);}

5. 商品详细页价格缓存

5.1 需求分析

我们已经将商品的信息生成为静态页面,但是商品价格经常变动,如果每次价格变动后都对静态页重新生成会影响服务器性能。
所以,对于商品价格,我们采用异步调用的方式来进行客户端渲染。

5.2 实现思路

(1)商品服务启动后加载全部价格数据到缓存。使用hash存储,skuID作为小KEY
(2)从缓存查询商品价格,封装为controller,并设置可跨域调用
什么叫跨域?
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相
互请求资源,就算作“跨域”。
比如这个contoller请求那个controller

JavaScript出于安全方面的考虑,不允许跨域调用其他页面的对象。
那什么是跨域呢,简单地理解就是因为JavaScript同源策略的限制,a.com域名下的js无法操作b.com
或是c.a.com域名下的对象。
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相
互请求资源,就算作“跨域”。
现在我们要实现的查询商品价格缓存功能就存在跨域问题。后端controller在http://www.qingcheng.com ,商品详细页在http://item.qingcheng.com,
如何解决跨域问题?我们使用CORS实现跨域。
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resourcesharing)。它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了AJAX只能同源使用的限制。使用非常简单,只需要在controller类上添加一个 @CrossOrigin
注解即可
(3)修改商品详细页模板,使用ajax读取价格,并进行客户端渲染。

5.3 代码实现

5.3.1 价格数据加载到缓存

(1)SkuService接口新增方法定义

/*** 保存全部价格到缓存*/
public void saveAllPriceToRedis();

(2)SkuServiceImpl类新增方法
如果缓存已经存在就没有必要重复查询
那么之前广告和商品分类数据为什么不做判断呢?
也可以,但是数据量比较小,即使重复载入影响不大,但是价格数据量大,对于性能有较大影响

 @Autowiredprivate RedisTemplate redisTemplate;public void saveAllPriceToRedis() {/*如果缓存并没有建立*/if (!redisTemplate.hasKey(CacheKey.SKU_PRICE)) {System.out.println("加载全部价格");List<Sku> skuList = skuMapper.selectAll();for (Sku sku : skuList) {if ("1".equals(sku.getStatus())) {redisTemplate.boundHashOps(CacheKey.SKU_PRICE).put(sku.getId(), sku.getPrice());}}}else{System.out.println("缓存已经存在,价格数据未更新,不必预热");}}

(3)修改Init类

@Component
public class Init implements InitializingBean {@Autowiredprivate CategoryService categoryService;@Autowiredprivate SkuService skuService;public void afterPropertiesSet() throws Exception {System.out.println("缓存预热");categoryService.saveCategoryTreeToRedis();//模块启动后自动加载商品分类导航缓存skuService.saveAllPriceToRedis();//加载价格数据也进入缓存}
}

5.3.2 查询价格缓存

后端代码:
(1)SkuService新增方法定义

public Integer findPrice(String id)

(2)SkuServiceImpl实现findPrice方法

public Integer findPrice(String id) {//从缓存中查询return    (Integer)redisTemplate.boundHashOps(CacheKey.SKU_PRICE).get(id);}

(3)qingcheng_web_portal工程新增类

@RestController
@RequestMapping("/sku")
@CrossOrigin
public class SkuController {@Referenceprivate SkuService skuService;@GetMapping("/price")public Integer price(String id){return skuService.findPrice(id);}
}

前端代码(修改模板):
(1)将vue.js axios.js 放到html输出文件夹下的js文件夹中。

(2)修改qingcheng_web_portal工程的模板 item.html

<script src="js/vue.js"></script>
<script src="js/axios.js"></script>
<script th:inline="javascript">new Vue({el:'#app',data(){return {skuId:/*[[${sku.id}]]*/,price:0}},created(){//读取价格axios.get('http://localhost:9102/sku/price.do?id='+this.skuId).then(response=>{this.price=(response.data/100).toFixed(2);})}});
</script>

th:inline 定义js脚本可以使用变量 js脚本的变量用 /[[${ }]]/ 渲染
(3)修改qingcheng_web_portal工程的模板 item.html ,body,添加

,并将价格修改为vue表达式

{{price}}

5.3.3 更新价格缓存

(1)SkuService接口新增方法定义

/*** 保存价格到缓存* @param skuId*/
public void savePriceToRedisById(String id,Integer price);

(2)SkuServiceImpl类新增方法

public void savePriceToRedisById(String id,Integer price) {redisTemplate.boundHashOps(CacheKey.SKU_PRICE).put(id,price);}

(3)SpuServiceImpl类引入SkuService

@Autowired
private SkuService skuService;

(4)修改SpuServiceImpl类saveGoods方法,在SKU列表循环体中添加代码

skuService.savePriceToRedisById(sku.getId(),sku.getPrice());

5.3.4 删除价格缓存

删除商品时删除缓存中的价格,释放内存空间
(1)SkuService新增方法定义

/*** 根据sku id 删除商品价格缓存* @param id*/
public void deletePriceFromRedis(String id);

(2)SkuServiceImpl新增方法实现

public void deletePriceFromRedis(String id) {redisTemplate.boundHashOps(CacheKey.SKU_PRICE).delete(id);}

(3)修改SpuServiceImpl的delete方法,新增代码逻辑

//删除缓存中的价格
Map map=new HashMap();map.put("spuId",id);List<Sku> skuList = skuService.findList(map);for(Sku sku:skuList){skuService.deletePriceFromRedis(sku.getId());}

javaweb-青橙项目-10-85相关推荐

  1. 青橙项目之品牌管理前端 day2

    学习掌握目标 1.Element-Ui组件了解与使用 2.ES6与ES5语法 3.品牌管理的前端代码 4.图片上传代码,本地和阿里云存储 5.掌握阿里云oss存储 未完成 1.ElementUI简介 ...

  2. javaweb-青橙项目-6-81

    文章目录 第6章 统计分析 学习目标 1. 商品类目销售分析表 1.1 需求分析 1.2 实现思路 1.3 代码实现 1.3.1 类目统计SQL语句 1.3.2 类目统计代码实现 1.3.3 定时任务 ...

  3. 阿里达摩院青橙奖颁出:10名85后每人100万!其中有硬核粉红少女,还有人被LeCun点名支持...

    杨净 萧箫 发自 凹非寺 量子位 报道 | 公众号 QbitAI 刚刚,这10个年轻学者,获得了阿里达摩院2020年青橙奖,每人奖金100万元. 达摩院院长张建锋公布了详细名单: 他们是: 梁文华,广 ...

  4. 阿里达摩院青橙奖“硬核10人”出炉,钟南山寄语青年科学家

    9月9日,2020年阿里巴巴达摩院青橙奖获奖名单公布,梁文华等10位青年科学家获得达摩院的1000万元奖金.这群平均年龄不足32岁的科研后浪收到了中国工程院院士钟南山的鼓励:"你们处在最好的 ...

  5. 阿里达摩院青橙奖再颁发!10名大陆青年科学家各获100万,最小获奖者28岁

    雷刚 发自 凹非寺  量子位 报道 | 公众号 QbitAI 10位青年科学家,刚刚荣获第二届达摩院青橙奖. 他们将获得达摩院提供的100万元人民币奖金和全方位的研发资源支持. 9月25日,杭州云栖大 ...

  6. 青橙商城项目总结day03-04

    青橙商城项目总结 day03_模板与分类管理 设计模式:SSM;分库分表 技术栈:黑马架构师:vue:Element UI 学习目标: 1)使用"黑马架构师"完成代码生成 \1. ...

  7. 青橙商城项目总结day02

    青橙商城项目总结 day02_品牌管理前端与图片上传 技术栈:Element UI:ES6:Spring:OSS 学习目标: 1)ElementUI常用组件:https://element.eleme ...

  8. 山大85后教授任哈工大(深圳)计算机学院执行院长,曾获青橙奖

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文来源:科学网 近日,据哈尔滨工业大学(深圳)计算机科学与技术学院 ...

  9. 阿里青橙奖名单公布,23位院士、2位图灵奖得主推荐

    9月9日,2020年阿里巴巴达摩院青橙奖获奖名单公布,梁文华等10位青年科学家获得达摩院的1000万元奖金.这群平均年龄不足32岁的科研后浪收到了中国工程院院士钟南山的鼓励:"你们处在最好的 ...

  10. 韦东奕陈杲同获达摩院青橙奖,90后数学新星光彩夺目,却说「没有黄金一代」...

    鱼羊 博雯 发自 凹非寺 量子位 报道 | 公众号 QbitAI 南北两位90后数学新星,刚刚同时获颁百万奖金大奖. 就在今天公布的阿里达摩院青橙奖十人名单中,北大韦东奕和中科大陈杲的名字赫然在列. ...

最新文章

  1. 大学生学python到底有没有有-除了不会生孩子,Python还有啥不会的呢
  2. SSD: Signle Shot Detector 用于自然场景文字检测
  3. at java.net.urlclassloader.findclass_如何使用URLClassLoader加载* .class文件?
  4. 工作中的git实际使用
  5. [Leedcode][JAVA][第94/144/145题][前中后序遍历][递归][迭代][二叉树]
  6. ant+jenkins+testng+selenium集成环境搭建
  7. Java实现数组转字符串及字符串转数组的方法
  8. 活字格企业Web应用生成器V3.0发布更新,支持插件管理和多人协作开发
  9. 运维系统 联想服务器,联想运维方案.pdf
  10. html生物代码,方舟生存进化全生物代码
  11. 完美数——C++代码及思路分析 leetcode507
  12. 不必担心安卓系统被植入棱镜
  13. 链表--逆时针旋转一个链表
  14. SSM配置redis
  15. 支付宝RSA2公钥证书生成办法
  16. 黑客在开源网站植入秘密后门、恶意软件通过非常规IP逃避检测|1月25日全球网络安全热点
  17. CPU 使用率低 负载高的原因
  18. 伟大的教育家:美国数学免费教学大神
  19. 试总结计算机整机组装的方法和流程,项目9 组装计算机整机.ppt
  20. 计算机网络实验(思科)

热门文章

  1. mysql-proxy做mysql代理连接阿里云服务器
  2. android zxing-1.6-core.jar,编译 ZXing
  3. HTML5阶段性学习2——JS
  4. 光进铜退再掀热潮 布线须冷思考
  5. 面试官:ZK分布式锁实现,你了解了吗?
  6. 《巴菲特之道 (第三版)》读书笔记
  7. python中级项目下载_中级Python复习:教程,项目思想和技巧
  8. STM32 DUF 模式 下载程序
  9. 【毕业设计】时间序列的股票预测与分析系统 - python 大数据
  10. 我们能从霓虹国的AI+新药开发中学到什么?