当前位置:   article > 正文

springboot与redis_springboot redis

springboot redis

1.springboot整合redis

springboot对redis的操作封装了两个StringRedisTemplate和RedisTemplate类,StringRedisTemplate是RedisTemplate的子类,StringRedisTemplate它只能存储字符串类型,无法存储对象类型。要想用StringRedisTemplate存储对象必须把对象转为json字符串。

1.1.StringRedisTemplate

(1) 引入相关的依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>

(2)注入StringRedisTemplate该类对象

  1. @Autowired
  2. private StringRedisTemplate redisTemplate;

(3)使用StringRedisTemplate

该类把对每种数据类型的操作,单独封了相应的内部类。

对String类型的数据操作

  1. //对String类型的操作
  2. @Test
  3. void test01() {
  4. ValueOperations<String, String> forValue = stringRedisTemplate.opsForValue();
  5. //时间存储 时间结束后销毁
  6. forValue.set("k1","李四",30l, TimeUnit.SECONDS);
  7. //获取k1的value值
  8. String s = forValue.get("k1");
  9. System.out.println(s);
  10. //若存在,不存入,不存在则存入 返回布尔值
  11. Boolean aBoolean = forValue.setIfAbsent("k1", "张三", 30l, TimeUnit.SECONDS);
  12. System.out.println(aBoolean);
  13. //追加
  14. Integer i = forValue.append("k1", "是个人");
  15. System.out.println(i);
  16. }

对Hash类型的数据操作

  1. //对Hash类型的操作
  2. @Test
  3. void test02(){
  4. HashOperations<String, Object, Object> forHash = stringRedisTemplate.opsForHash();
  5. forHash.put("k1","name","张三");
  6. //必须都是字符串类型,虽然上面泛型是Object 但使用的是spring的序列化 Integer 无法转为String
  7. forHash.put("k1","age","18");
  8. Map<String,String> map=new HashMap<>();
  9. map.put("name","李四");
  10. map.put("age","25");
  11. forHash.putAll("k2",map);
  12. Object o = forHash.get("k1", "name");
  13. System.out.println(o);
  14. Set<Object> s= forHash.keys("k1");
  15. System.out.println(s);
  16. List<Object> l = forHash.values("k1");
  17. System.out.println(l);
  18. //获取k1对于的所有的field和value
  19. Map<Object, Object> k12 = forHash.entries("k1");
  20. System.out.println(k12);
  21. }

注意:

1.2.RedisTemplate

使用RedisTemplate 必须要指定序列化方式,默认使用jdk序列化方式。但会引起乱码,而且占用内存大。

  1. @Autowired
  2. private RedisTemplate redisTemplate;
  3. @Test
  4. void test01(){
  5. //指定key的序列化方式
  6. redisTemplate.setKeySerializer(new StringRedisSerializer());
  7. //指定value的序列化方式 GenericJackson2JsonRedisSerializer()/Jackson2JsonRedisSerializer<Object>(Object.class)
  8. redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
  9. ValueOperations forValue = redisTemplate.opsForValue();
  10. forValue.set("k1","张三",60l,TimeUnit.SECONDS);
  11. //value默认采用jdk,类必须实现序列化接口
  12. forValue.set("k2",new User(1,"李四","123456"));
  13. }

上面的RedisTemplate需要每次都指定key value以及field的序列化方式,可以创建一个配置类,为RedisTemplate指定好序列化。以后再用就无需指定。

  1. @Configuration
  2. public class RedisConfig {
  3. @Bean
  4. public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
  5. RedisTemplate<String, Object> template = new RedisTemplate<>();
  6. RedisSerializer<String> redisSerializer = new StringRedisSerializer();
  7. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  8. ObjectMapper om = new ObjectMapper();
  9. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  10. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  11. jackson2JsonRedisSerializer.setObjectMapper(om);
  12. template.setConnectionFactory(factory);
  13. //key序列化方式
  14. template.setKeySerializer(redisSerializer);
  15. //value序列化
  16. template.setValueSerializer(jackson2JsonRedisSerializer);
  17. //value hashmap序列化 filed value
  18. template.setHashValueSerializer(jackson2JsonRedisSerializer);
  19. template.setHashKeySerializer(redisSerializer);
  20. return template;
  21. }
  22. }

2.redis的使用场景

2.1.作为缓存

(1)数据存储在内存中,数据查询速度快。可以分摊数据库压力。

(2)什么样的数据适合放入缓存

查询频率比较高,修改频率比较低。

安全系数低的数据

(3)使用redis作为缓存

  1. @Autowired
  2. private UserMapper userMapper;
  3. @Autowired
  4. private RedisTemplate redisTemplate;
  5. //根据id查询
  6. public User findById(Integer id){
  7. ValueOperations forValue = redisTemplate.opsForValue();
  8. //查询缓存
  9. Object o = forValue.get("user::" + id);
  10. //缓存命中
  11. if(o!=null){
  12. return (User) o;
  13. }
  14. User user = userMapper.selectById(id);
  15. if(user!=null){
  16. //存入缓存
  17. forValue.set("user::"+id,user,2, TimeUnit.HOURS);
  18. }
  19. return user;
  20. }
  21. //根据id删除
  22. public int delete(Integer id){
  23. //先删除缓存再删除数据库中的数据
  24. redisTemplate.delete("user::"+id);
  25. int i = userMapper.deleteById(id);
  26. return i;
  27. }
  28. //添加
  29. public User insert(User user){
  30. int i = userMapper.insert(user);
  31. return user;
  32. }
  33. //根据id修改
  34. //先删掉缓存,再修改数据库
  35. public User update(User user){
  36. Integer id = user.getId();
  37. redisTemplate.delete("user::"+id);
  38. int i = userMapper.updateById(user);
  39. return user;
  40. }

查看的缓存: 前部分代码相同@before通知,后部分代码也相同后置通知。 我们可以AOP完成缓存代码和业务代码分离。

spring框架它应该也能想到。--使用注解即可完成。解析该注解。

(1)把缓存的配置类加入

  1. @Bean
  2. public CacheManager cacheManager(RedisConnectionFactory factory) {
  3. RedisSerializer<String> redisSerializer = new StringRedisSerializer();
  4. Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
  5. //解决查询缓存转换异常的问题
  6. ObjectMapper om = new ObjectMapper();
  7. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  8. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  9. jackson2JsonRedisSerializer.setObjectMapper(om);
  10. // 配置序列化(解决乱码的问题),过期时间600秒
  11. RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
  12. .entryTtl(Duration.ofSeconds(600)) //缓存过期10分钟 ---- 业务需求。
  13. .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))//设置key的序列化方式
  14. .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) //设置value的序列化
  15. .disableCachingNullValues();
  16. RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
  17. .cacheDefaults(config)
  18. .build();
  19. return cacheManager;
  20. }

2.2.分布式锁

使用压测工具测试高并发下带来线程安全问题

同一个库存被使用了n次。以及数据库中库存为负数。 线程安全问题导致。

1. 解决方案: 使用 synchronized 或者lock锁

2.使用redis作为锁

nginx(windows系统下)

配置nginx文件 nginx.conf

开启nginx

注意nginx包的目录必须没有中文,否则无法开启

 

 准备数据库文件

开启idea集群

测试代码:

controller层

  1. @RestController
  2. @RequestMapping("productStock")
  3. public class ProductStockController {
  4. @Autowired
  5. private ProductStockService productStockService;
  6. //减库存
  7. @RequestMapping("decreaseStock/{productId}")
  8. public String decreaseStock(@PathVariable("productId") Integer productId){
  9. return productStockService.decreaseStock(productId);
  10. }
  11. }

service层 

  1. @Service
  2. public class ProductStockServiceImpl2 implements ProductStockService {
  3. @Autowired
  4. private ProductStockDao productStockDao;
  5. @Autowired
  6. private StringRedisTemplate stringRedisTemplate;
  7. @Override
  8. public String decreaseStock(Integer productId) {
  9. ValueOperations<String,String> forValue = stringRedisTemplate.opsForValue();
  10. Boolean flag = forValue.setIfAbsent("dis::" + productId, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
  11. if(flag) {
  12. try{
  13. //查看该商品的库存数量
  14. Integer stock = productStockDao.findStockByProductId(productId);
  15. if (stock > 0) {
  16. //修改库存每次-1
  17. productStockDao.updateStockByProductId(productId);
  18. System.out.println("扣减成功!剩余库存数:" + (stock - 1));
  19. return "success";
  20. } else {
  21. System.out.println("扣减失败!库存不足!");
  22. return "fail";
  23. }
  24. }finally {
  25. stringRedisTemplate.delete("dis::" + productId);
  26. }
  27. }
  28. return "服务忙,请稍后..........";
  29. }
  30. }

 sql语句

  1. <select id="findStockByProductId" resultType="integer">
  2. select num from tbl_stock where productId=#{productId}
  3. </select>
  4. <update id="updateStockByProductId">
  5. update tbl_stock set num=num-1 where productId=#{productId}
  6. </update>

3.解决redis分布式锁的bug

 可以使用:redission依赖,redission解决redis超时问题的原理。

为持有锁的线程开启一个守护线程,守护线程会每隔10秒检查当前线程是否还持有锁,如果持有则延迟生存时间。

使用redis破解版(可以在windows中使用的),开启redis

  1. <dependency>
  2. <groupId>org.redisson</groupId>
  3. <artifactId>redisson</artifactId>
  4. <version>3.13.4</version>
  5. </dependency>
  1. //获取redisson对象并交于spring容器管理
  2. @Bean
  3. public Redisson redisson(){
  4. Config config =new Config();
  5. config.useSingleServer().
  6. setAddress("redis://192.168.226.234:6379").
  7. //redis默认有16个数据库
  8. setDatabase(0);
  9. return (Redisson) Redisson.create(config);
  10. }

测试代码 

  1. @Service
  2. public class ProductStockServiceImpl2 implements ProductStockService {
  3. @Autowired
  4. private ProductStockDao productStockDao;
  5. @Autowired
  6. private Redisson redisson;
  7. @Override
  8. public String decreaseStock(Integer productId) {
  9. //获取锁对象
  10. RLock rlock = redisson.getLock("dis::"+productId);
  11. try{
  12. rlock.lock(30, TimeUnit.SECONDS);
  13. //查看该商品的库存数量
  14. Integer stock = productStockDao.findStockByProductId(productId);
  15. if (stock > 0) {
  16. //修改库存每次-1
  17. productStockDao.updateStockByProductId(productId);
  18. System.out.println("扣减成功!剩余库存数:" + (stock - 1));
  19. return "success";
  20. } else {
  21. System.out.println("扣减失败!库存不足!");
  22. return "fail";
  23. }
  24. }finally {
  25. rlock.unlock();
  26. }
  27. }
  28. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/152113
推荐阅读
相关标签
  

闽ICP备14008679号