springboot与redis

1.springboot整合redis

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

springboot与redis_第1张图片

1.1.StringRedisTemplate

(1) 引入相关的依赖

        
            org.springframework.boot
            spring-boot-starter-data-redis
        

(2)注入StringRedisTemplate该类对象

 @Autowired
 private StringRedisTemplate redisTemplate;

(3)使用StringRedisTemplate

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

对String类型的数据操作

    //对String类型的操作
    @Test
    void test01() {

        ValueOperations forValue = stringRedisTemplate.opsForValue();
        //时间存储  时间结束后销毁
        forValue.set("k1","李四",30l, TimeUnit.SECONDS);
        //获取k1的value值
        String s = forValue.get("k1");
        System.out.println(s);

        //若存在,不存入,不存在则存入 返回布尔值
        Boolean aBoolean = forValue.setIfAbsent("k1", "张三", 30l, TimeUnit.SECONDS);
        System.out.println(aBoolean);

        //追加
        Integer i = forValue.append("k1", "是个人");
        System.out.println(i);

    }

对Hash类型的数据操作

    //对Hash类型的操作
    @Test
    void test02(){
        HashOperations forHash = stringRedisTemplate.opsForHash();
        forHash.put("k1","name","张三");
        //必须都是字符串类型,虽然上面泛型是Object 但使用的是spring的序列化 Integer 无法转为String
        forHash.put("k1","age","18");

        Map map=new HashMap<>();
        map.put("name","李四");
        map.put("age","25");
        forHash.putAll("k2",map);

        Object o = forHash.get("k1", "name");
        System.out.println(o);

        Set s= forHash.keys("k1");
        System.out.println(s);
        List l = forHash.values("k1");
        System.out.println(l);

        //获取k1对于的所有的field和value
        Map k12 = forHash.entries("k1");
        System.out.println(k12);

    } 
  

注意:springboot与redis_第2张图片

1.2.RedisTemplate

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

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void test01(){
        //指定key的序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //指定value的序列化方式 GenericJackson2JsonRedisSerializer()/Jackson2JsonRedisSerializer(Object.class)
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Object.class));

        ValueOperations forValue = redisTemplate.opsForValue();

        forValue.set("k1","张三",60l,TimeUnit.SECONDS);

        //value默认采用jdk,类必须实现序列化接口
        forValue.set("k2",new User(1,"李四","123456"));
    } 
  

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

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate template = new RedisTemplate<>();
        RedisSerializer redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化  filed value
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.setHashKeySerializer(redisSerializer);
        return template;
    }
}

2.redis的使用场景

2.1.作为缓存

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

springboot与redis_第3张图片

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

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

安全系数低的数据

(3)使用redis作为缓存

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    //根据id查询
    public User findById(Integer id){
        ValueOperations forValue = redisTemplate.opsForValue();
        //查询缓存
        Object o = forValue.get("user::" + id);
        //缓存命中
        if(o!=null){
            return (User) o;
        }
        User user = userMapper.selectById(id);
        if(user!=null){
            //存入缓存
            forValue.set("user::"+id,user,2, TimeUnit.HOURS);
        }
        return user;
    }

    //根据id删除
    public int delete(Integer id){
        //先删除缓存再删除数据库中的数据
        redisTemplate.delete("user::"+id);
        int i = userMapper.deleteById(id);
        return i;
    }

    //添加
    public User insert(User user){
        int i = userMapper.insert(user);
        return user;
    }

    //根据id修改
    //先删掉缓存,再修改数据库
    public User update(User user){
        Integer id = user.getId();
        redisTemplate.delete("user::"+id);
        int i = userMapper.updateById(user);
        return user;
    }

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

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

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

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

2.2.分布式锁

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

springboot与redis_第4张图片

springboot与redis_第5张图片

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

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

springboot与redis_第6张图片

2.使用redis作为锁springboot与redis_第7张图片

nginx(windows系统下)

配置nginx文件 nginx.conf

springboot与redis_第8张图片

开启nginx

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

 

 准备数据库文件

开启idea集群

springboot与redis_第9张图片

测试代码:

controller层

@RestController
@RequestMapping("productStock")
public class ProductStockController {
    @Autowired
    private ProductStockService productStockService;
    //减库存
    @RequestMapping("decreaseStock/{productId}")
    public String decreaseStock(@PathVariable("productId") Integer productId){
        return productStockService.decreaseStock(productId);
    }
}

service层 

@Service
public class ProductStockServiceImpl2 implements ProductStockService {
    @Autowired
    private ProductStockDao productStockDao;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public  String decreaseStock(Integer productId) {
        ValueOperations forValue = stringRedisTemplate.opsForValue();
        Boolean flag = forValue.setIfAbsent("dis::" + productId, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
        if(flag) {
            try{
                //查看该商品的库存数量
                Integer stock = productStockDao.findStockByProductId(productId);
                if (stock > 0) {
                    //修改库存每次-1
                    productStockDao.updateStockByProductId(productId);
                    System.out.println("扣减成功!剩余库存数:" + (stock - 1));
                    return "success";
                } else {
                    System.out.println("扣减失败!库存不足!");
                    return "fail";
                }
            }finally {
                stringRedisTemplate.delete("dis::" + productId);
            }
        }
        return "服务忙,请稍后..........";

    }
}

 sql语句

  

  
      update tbl_stock set num=num-1  where productId=#{productId}
  

springboot与redis_第10张图片

3.解决redis分布式锁的bug

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

springboot与redis_第11张图片

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

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

springboot与redis_第12张图片

        
            org.redisson
            redisson
            3.13.4
        
    //获取redisson对象并交于spring容器管理    
    @Bean
    public Redisson redisson(){
        Config config =new Config();
        config.useSingleServer().
                setAddress("redis://192.168.226.234:6379").
                //redis默认有16个数据库
                        setDatabase(0);
        return (Redisson) Redisson.create(config);
    }

测试代码 

@Service
public class ProductStockServiceImpl2 implements ProductStockService {
    @Autowired
    private ProductStockDao productStockDao;

    @Autowired
    private Redisson redisson;

    @Override
    public  String decreaseStock(Integer productId) {
        //获取锁对象
        RLock rlock = redisson.getLock("dis::"+productId);
            try{
                rlock.lock(30, TimeUnit.SECONDS);
                //查看该商品的库存数量
                Integer stock = productStockDao.findStockByProductId(productId);
                if (stock > 0) {
                    //修改库存每次-1
                    productStockDao.updateStockByProductId(productId);
                    System.out.println("扣减成功!剩余库存数:" + (stock - 1));
                    return "success";
                } else {
                    System.out.println("扣减失败!库存不足!");
                    return "fail";
                }
            }finally {
                rlock.unlock();
            }
        }

}

你可能感兴趣的:(redis,java,redis,spring,boot,java)