您现在的位置是:首页 >学无止境 >项目实战-redis网站首页学无止境

项目实战-redis

你的意义luuuu 2024-06-02 00:00:02
简介项目实战-redis

springboot集成mybatis

步骤

1、添加Redis依赖项:在项目的pom.xml文件中添加以下依赖项

<!-- spring data redis 依赖 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- commons-pool2 对象池依赖 -->
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-pool2</artifactId>
</dependency>

2、配置Redis连接属性:在application.yml中添加以下属性:# Redis配置

注意,如果你的redis有密码,还要配置密码!!!

spring:
  redis:
    timeout: 10000ms                        # 连接超时时间
    host: 192.168.10.100                   # Redis服务器地址
    port: 6379                              # Redis服务器端口
    database: 0                             # 选择哪个库,默认0库
    lettuce:
      pool:
        max-active: 1024                    # 最大连接数,默认 8
        max-wait: 10000ms                   # 最大连接阻塞等待时间,单位毫秒,默认 -1
        max-idle: 200                       # 最大空闲连接,默认 8
        min-idle: 5                          # 最小空闲连接,默认 0

# 商品分类列表 Key
goods.category.list.key: goods:category:list:goodsCategoryList

为什么要配置redis-key?
提高代码的可维护性和可配置性,方便应用程序的管理和部署。
将Redis Key的名称配置在yml(或其他配置文件)中,有以下几个好处:

  1. 集中管理:将Redis Key的名称存储在一个配置文件中,可以方便地对Key进行统一的管理和维护。在需要修改Key名称时,只需要修改配置文件中的内容即可,而不需要在应用程序中修改多处代码。
  2. 易于维护:通过配置文件中的Redis Key名称,开发人员可以直观地了解每个Key的含义和作用,从而更容易地进行代码维护和开发。而且,通过配置文件,可以快速地了解应用程序中使用了哪些Redis Key。
  3. 可配置性:将Redis Key的名称配置在一个文件中,可以方便地进行动态配置。这样,在应用程序启动时,可以根据不同的配置文件加载不同的Redis Key,从而实现灵活的应用程序部署和管理。

命名规则:
这个Redis Key是用于存储商品分类列表数据的,格式为goods:category:list:goodsCategoryList
命名空间:对象类型:数据类型:数据名称
Redis Key的设计通常遵循以下几个原则:
5. 命名空间:通过给Key添加一个命名空间,可以避免不同模块或不同功能使用相同的Key造成冲突。在这里,goods就是一个命名空间,表示这个Key是与商品相关的数据。
6. 对象类型:通常,在Key中包含对象类型可以让开发人员快速地了解这个Key存储的是什么类型的数据。在这里,category表示这个Key存储的是商品分类数据。
7. 层级结构:为了避免Key的名称过于冗长,可以考虑将Key分层。在这里,list表示这个Key存储的是列表类型的数据,而goodsCategoryList是实际的Key的名称。通过这种设计方式,可以使Key的名称更加清晰明了,易于开发人员的理解和维护。同时,也方便了对数据进行管理和查询。

3、 创建RedisTemplate Bean:在Spring配置类中创建RedisTemplate Bean,以便在应用程序中使用RedisTemplate。

/**
 * Redis配置类
 *
 * @author zhoubin
 * @since 1.0.0
 */
@Configuration
public class RedisConfig {
	@Bean
	public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory){
		RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
		//为string类型key设置序列器
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		//为string类型value设置序列器
		redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
		//为hash类型key设置序列器
		redisTemplate.setHashKeySerializer(new StringRedisSerializer());
		//为hash类型value设置序列器
		redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
		redisTemplate.setConnectionFactory(redisConnectionFactory);
		return redisTemplate;
	}
}

这个配置类的作用是为了创建一个RedisTemplate对象并进行相关配置,具体的作用有以下几点:1. 序列化器配置:配置RedisTemplate对象的序列化器,这样在进行Redis数据操作时,可以保证数据的正确序列化和反序列化。这里使用了两种序列化器,StringRedisSerializerGenericJackson2JsonRedisSerializer,分别用于处理Redis Key和Value的序列化。2. 连接工厂配置:设置要使用的Redis客户端连接工厂。这里使用了LettuceConnectionFactory类型的对象,表示使用Lettuce客户端来进行Redis数据操作。3. Bean注解配置:通过@Bean注解声明一个Bean,使该对象可以在应用程序中被使用。
这样编写的好处在于,RedisTemplate是Spring提供的Redis客户端模板,能够方便地进行Redis数据操作。而该配置类提供了一种便捷的方式来创建RedisTemplate实例并进行相关配置,可以使得在应用程序的其他地方直接使用该实例来进行Redis数据操作,简化了代码的编写。同时,通过注入redisConnectionFactory,可以方便地在其他地方进行配置和替换该连接工厂,提高了代码的扩展性。

为什么要进行数据的序列化
在使用Redis存储数据时,需要将数据序列化成二进制格式,以便于Redis服务器进行存储和读取。Redis只能存储二进制数据,不能直接存储Java对象,因此需要将对象序列化成二进制格式,然后再存储到Redis中。数据序列化的主要作用有:1. 压缩数据:将数据序列化成二进制格式可以大幅减少数据大小,从而节省内存和网络带宽。2. 跨平台传输:不同的平台和语言之间,二进制数据是一种通用的传输格式,比如Java对象序列化成二进制格式后,可以在Python、C++等其他语言中进行反序列化。3. 持久化存储:将数据序列化成二进制格式,可以方便地将数据存储到磁盘中,以实现数据持久化。在Redis中,可以使用不同的序列化器来序列化不同类型的数据。
其中,常用的序列化器有:1. StringRedisSerializer:序列化Redis Key的字符串类型。2. JdkSerializationRedisSerializer:使用JDK自带的ObjectInputStreamObjectOutputStream实现的序列化器,适用于Java对象的序列化。3. Jackson2JsonRedisSerializer:使用Jackson库实现的JSON格式数据的序列化和反序列化。4. GenericJackson2JsonRedisSerializer:使用Jackson库实现的JSON格式数据的泛化型序列化和反序列化,支持类型信息。综上所述,数据序列化可以大幅减少数据大小,方便跨平台传输和存储,而使用不同的序列化器,可以适应不同的数据类型和业务需求。

JsonUtils

public class JsonUtil {
    private static ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 将对象转换成json字符串
     *
     * @param obj
     * @return
     */
    public static String object2JsonStr(Object obj) {
        try {
            return objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            //打印异常信息
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将字符串转换为对象
     *
     * @param <T> 泛型
     */
    public static <T> T jsonStr2Object(String jsonStr, Class<T> clazz) {
        try {
            return objectMapper.readValue(jsonStr.getBytes("UTF-8"), clazz);
        } catch (JsonParseException e) {
            e.printStackTrace();
        } catch (JsonMappingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将json数据转换成pojo对象list
     * <p>Title: jsonToList</p>
     * <p>Description: </p>
     *
     * @param jsonStr
     * @param beanType
     * @return
     */
    public static <T> List<T> jsonToList(String jsonStr, Class<T> beanType) {
        JavaType javaType = objectMapper.getTypeFactory().constructParametricType(List.class, beanType);
        try {
            List<T> list = objectMapper.readValue(jsonStr, javaType);
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

4、使用RedisTemplate:使用@Autowired注释将RedisTemplate注入到您的服务类中

@Service("GoodsCategoryService")
public class GoodsCategoryServiceImpl implements GoodsCategoryService {

	@Resource
	private GoodsCategoryMapper goodsCategoryMapper;
	@Autowired
	RedisTemplate<String,String> redisTemplate;
	@Value("${goods.category.list.key}")
	private String goodsCategoryListKey;
	/**
	 * 分类查询商品分类,顶级分类,二级分类,三级分类
	 * @return
	 */
	@Override
	public List<GoodsCategoryVo> selectCategoryListForView() {
	
//opsForValue()获取redis中操作字符串的对象
		ValueOperations<String, String> valueOpreations = redisTemplate.opsForValue();
		//查询Redis缓存是否有数据,有数据直接返回,没有数据去数据库查询
		String gcvListJson = valueOpreations.get(goodsCategoryListKey);
		if (!StringUtils.isEmpty(gcvListJson)){
			return JsonUtil.jsonToList(gcvListJson,GoodsCategoryVo.class);
		}

		//========================JDK8新特性======================
		//创建查询对象
		GoodsCategoryExample example = new GoodsCategoryExample();
		//查询所有商品分类
		List<GoodsCategory> list = goodsCategoryMapper.selectByExample(example);
		//将GoodsCategory对象转成GoodsCategoryVo对象
		List<GoodsCategoryVo> gcvList = list.stream().map(e -> {
			GoodsCategoryVo gcv = new GoodsCategoryVo();
			BeanUtils.copyProperties(e, gcv);
			return gcv;
		}).collect(Collectors.toList());
		//将List<GoodsCategoryVo>转成Map<parentId,List<GoodsCategoryVo>>
		//按parentId分组,key就是parentId,值就是parentId对应的List<GoodsCategoryVo>
		Map<Short, List<GoodsCategoryVo>> map =
				gcvList.stream().collect(Collectors.groupingBy(GoodsCategoryVo::getParentId));
		//循环,给children赋值
		gcvList.forEach(e->e.setChildren(map.get(e.getId())));
		//拦截器,返回level为1的list,也就是顶级分类
		List<GoodsCategoryVo> gcvList01 = gcvList.stream().filter(e -> 1 == e.getLevel()).collect(Collectors.toList());
		//放入Redis缓存
		valueOpreations.set(goodsCategoryListKey, JsonUtil.object2JsonStr(gcvList01));
		//========================JDK8新特性=======================
		return gcvList01;
	}

有动态变化的属性在redis缓存中分页查询商品列表

	/**
	 * 商品列表-分页查询
	 * @param goods
	 * @param pageNum
	 * @param pageSize
	 * @return
	 */
	@Override
	public BaseResult selectGoodsListByPage(Goods goods, Integer pageNum, Integer pageSize) {
		/**
		 * 商品列表RedisKey
		 *  1.无条件查询
		 *      goods:pageNum_:PageSize_:catId_:brandId_:goodsName_:
		 *  2.条件查询
		 *      goods:pageNum_:PageSize_:catId_123:brandId_:goodsName_:
		 *      goods:pageNum_:PageSize_:catId_:brandId_123:goodsName_:
		 *      goods:pageNum_:PageSize_:catId_:brandId_:goodsName_华为:
		 *      goods:pageNum_:PageSize_:catId_123:brandId_123:goodsName_:
		 *      goods:pageNum_:PageSize_:catId_123:brandId_:goodsName_华为:
		 *      goods:pageNum_:PageSize_:catId_:brandId_123:goodsName_华为:
		 *      goods:pageNum_:PageSize_:catId_123:brandId_123:goodsName_华为:
		 */
		//定义RedisKey数组
		String[] goodsKeyArr = new String[]{
				"goods:pageNum_"+pageNum+":PageSize_"+pageSize+":",
				"catId_:",
				"brandId_:",
				"goodsName_:"
		};
		//构建分页对象
		PageHelper.startPage(pageNum,pageSize);
		//创建查询对象
		GoodsExample example = new GoodsExample();
		GoodsExample.Criteria criteria = example.createCriteria();
		//设置查询条件
		//分类参数
		if (null!=goods.getCatId()&&0!=goods.getCatId()){
			criteria.andCatIdEqualTo(goods.getCatId());
			goodsKeyArr[1] = "catId_"+goods.getCatId()+":";
		}
		//品牌参数
		if (null!=goods.getBrandId()&&0!=goods.getBrandId()){
			criteria.andBrandIdEqualTo(goods.getBrandId());
			goodsKeyArr[2] = "brandtId_"+goods.getBrandId()+":";
		}
		//关键词
		if (!StringUtils.isEmpty(goods.getGoodsName())){
			criteria.andGoodsNameLike("%"+goods.getGoodsName()+"%");
			goodsKeyArr[3] = "goodsName_"+goods.getGoodsName()+":";
		}
		//拼接完整的RedisKey
		String goodsListKey = Arrays.stream(goodsKeyArr).collect(Collectors.joining());
		ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
		//查询缓存,如果缓存中存在数据,直接返回
		String pageInfoGoodsJson = valueOperations.get(goodsListKey);
		if (!StringUtils.isEmpty(pageInfoGoodsJson)){
			return BaseResult.success(JsonUtil.jsonStr2Object(pageInfoGoodsJson,PageInfo.class));
		}

		//==============错误代码==================
		// String listGoodsJson = valueOperations.get(goodsListKey);
		// if (!StringUtils.isEmpty(listGoodsJson)){
		// 	List<Goods> goodsList = JsonUtil.jsonToList(listGoodsJson, Goods.class);
		// 	PageInfo<Goods> pageInfo = new PageInfo<>(goodsList);
		// 	return BaseResult.success(pageInfo);
		// }
		//==============错误代码==================

		//判断查询结果是否为空,不为空放入分页对象
		List<Goods> list = goodsMapper.selectByExample(example);
		if (!CollectionUtils.isEmpty(list)){
			PageInfo<Goods> pageInfo = new PageInfo<>(list);
			//放入缓存
			valueOperations.set(goodsListKey,JsonUtil.object2JsonStr(pageInfo));
			//==============错误代码==================
			// valueOperations.set(goodsListKey,JsonUtil.object2JsonStr(list));
			//==============错误代码==================
			return BaseResult.success(pageInfo);
		}else {
			//如果没有数据,将空数据放入缓存,设置失效时间60s
			valueOperations.set(goodsListKey,JsonUtil.object2JsonStr(new PageInfo<>(new ArrayList<Goods>())),60, TimeUnit.SECONDS);
		}
		return BaseResult.error();
	}
}

首先需要知道有哪些产品是动态的,哪些产品是会实时改变的。
已知商品分类id,品牌id,商品名称,分页页码,每页显示条数是固定的
因此我们可以使用一个数组来存储信息,当发生改变时,改变数组的内容。将本次请求中所有动态数据放入数据中,得到一个最终的goodsListKey,存入redis缓存中,如果以前查询过而且没有过期,那么从redis缓存中读取数据,如果没有查询过,则到数据库中查询。
如果查询出的数据为空,那么为了防止缓存雪崩的问题,那么我们采用返回一个空数据,并设置他的失效时间。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。