Bootstrap

Redis(三)

1. java连接redis

java提高连接redis的方式jedis. 我们需要遵循jedis协议。

引入依赖

<!--引入java连接redis的驱动-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>4.3.1</version>
        </dependency>

2 java连接redis集群模式

 public static void main(String[] args) {
        Set<HostAndPort> nodes=new HashSet<>();
        nodes.add(new HostAndPort("192.168.111.188",7001));
        nodes.add(new HostAndPort("192.168.111.188",7002));
        nodes.add(new HostAndPort("192.168.111.188",7003));
        nodes.add(new HostAndPort("192.168.111.188",7004));
        nodes.add(new HostAndPort("192.168.111.188",7005));
        nodes.add(new HostAndPort("192.168.111.188",7006));
        JedisCluster jedisCluster=new JedisCluster(nodes);
        jedisCluster.set("k5","666");
        System.out.println(jedisCluster.get("k5"));
    }

适合ssm项目。

3. springboot整合redis

starter启动依赖。---包含自动装配类---完成相应的装配功能。

引入依赖

  <!--引入了redis整合springboot 的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

修改配置文件

#redis 配置
spring.redis.host=192.168.100.104
spring.redis.port=6379
spring.redis.database=1
spring.redis.jedis.pool.max-active=100
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=10000ms

使用

springboot整合redis时封装了两个工具类:StringRedisTemplate和RedisTemplate.

StringRedisTemplate它是RedisTemplate的子类。StringRedisTemplate里面只能存放字符串的内容。

1. StringRedisTemplate

package com.zql;

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.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Spring Boot Redis 应用的测试类,用于验证 Redis 的集成和操作。
 */
@SpringBootTest
class SpringBootRedisApplicationTests {
    // 自动注入 StringRedisTemplate,用于进行 Redis 的字符串操作
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 测试 Redis Hash 的操作,包括添加、获取和删除数据。
     */
    //关于Hash的操作
    @Test
    void testHash(){
        // 获取 Hash 操作实例
        HashOperations<String, String, Object> ops = stringRedisTemplate.opsForHash();
        // 向 Hash 中添加单个字段值
        //存放数据 hset(k,f,v)
        ops.put("user","name","zql");
        // 向 Hash 中添加多个字段值
        Map<String, Object> map=new HashMap<>();
        map.put("name","zql");
        map.put("age","18");
        map.put("sex","男");
        ops.putAll("user",map);
        // 获取 Hash 中的单个字段值
        //获取指定的元素
        Object name = ops.get("user", "name");
        // 获取 Hash 中的所有字段值
        Map<String, Object> user = ops.entries("user");
        // 获取 Hash 中的所有字段名
        Set<String> keys = ops.keys("user");
        // 获取 Hash 中的所有字段值
        List<Object> values = ops.values("user");
    }
    /**
     * 测试 Redis 字符串操作,包括设置、获取和递增操作。
     */
    //关于String字符串的操作
    @Test
    void testString(){
        // 获取字符串操作实例
        //得到操作字符串的类对象
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        // 设置字符串值
        //存放数据---set(k,v)
        ops.set("k1","v1");
        ops.set("k2","2");
        // 为字符串设置过期时间并设置值
        //存放数据--settex(k,second,v)
        ops.set("k3","v3",30, TimeUnit.SECONDS);
        // 如果键不存在,则设置键值并设置过期时间
        Boolean aBoolean = ops.setIfAbsent("k4", "v4", 30, TimeUnit.SECONDS);
        // 获取字符串值
        String k1 = ops.get("k1");
        // 对字符串进行递增操作
        Long k2 = ops.increment("k2", 10);
    }
    /**
     * 测试 Redis 关于键的操作,包括查找、删除、检查存在性和设置过期时间。
     */
    //关于key的操作
    @Test
    void testKeys() {
        // 查找所有匹配的键
        Set<String> keys = stringRedisTemplate.keys("*");
        // 删除指定的键
        Boolean k1 = stringRedisTemplate.delete("k1");
        // 检查键是否存在
        Boolean k11 = stringRedisTemplate.hasKey("k1");
        // 为键设置过期时间
        Boolean k12 = stringRedisTemplate.expire("k4", 30, TimeUnit.SECONDS);
    }

}

2. RedisTemplate

它属于StringRedisTemplate的父类,它的泛型默认都是Object。它可以直接存储任意类型的key和value.

package com.zql;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import com.zql.entity.User;
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.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@SpringBootTest
class SpringBootRedisApplicationTest02 {
@Autowired
private RedisTemplate redisTemplate;
@Test
    public void test01() {
    //指定key的序列化方式
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    //指定value的序列化方式
    redisTemplate.setValueSerializer(new FastJsonRedisSerializer<>(Object.class));

    ValueOperations valueOperations = redisTemplate.opsForValue();
    valueOperations.set("name","zhangsan");
    System.out.println(valueOperations.get("name"));
    valueOperations.set
            ("k6",new User("zql",15));
    JSONObject k6=(JSONObject) valueOperations.get("k6");

    HashOperations forHash = redisTemplate.opsForHash();
    forHash.put("u","n","zhangsan");
    forHash.put("u","张三","张三杀回");
}
}

我们可以自己写个工具类

package com.zql.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @program: spring-boot01
 * @description:
 * @author: 
 * @create: 2024-07-23 19:47
 **/
@Configuration
public class RedisConfig {

    /**
     * 配置Redis模板
     *
     * @param factory Redis连接工厂,用于创建Redis连接。
     * @return RedisTemplate实例,配置了键值的序列化方式。
     */
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String,Object> template=new RedisTemplate<>();//创建redisTemplate对象
        RedisSerializer<String> redisSerializer=new StringRedisSerializer();//字符串序列化器
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer<>(Object.class);//json序列化器

        // 配置ObjectMapper,用于序列化和反序列化Java对象为JSON。
        ObjectMapper om=new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 配置Redis模板的序列化方式。
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化方式
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //hash的key序列化方式
        template.setHashKeySerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(redisSerializer);
        return template;
    }
}

4. 集群模式

在配置文件里面配置集群的配置

#集群模式
spring.redis.cluster.nodes=192.168.111.188:7006,192.168.111.188:7001,192.168.111.188:7002,192.168.111.188:7003,192.168.111.188:7004,192.168.111.188:7005
 

5. 短信业务

项目结构

controller

package com.zql.controller;

import com.zql.SendMsgUtil;
import com.zql.vo.LoginVo;
import com.zql.vo.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.concurrent.TimeUnit;

/**
 * @program: spring-boot01
 * @description:
 * @author: 
 * @create: 2024-07-24 18:35
 **/
@RestController
@RequestMapping("/msg")
public class MsgController {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping("send")
    public R send(String phone) throws Exception {
        //1.校验手机号是否存在 --连接数据库
        if (phone.equals("13938300924")) {
            if (redisTemplate.hasKey("code::" + phone)) {
                return new R(500, "验证码已发送", null);
            }
            //2. 发送验证码
            String code = SendMsgUtil.sendCode(phone);
            //3.将验证码存入redis
            redisTemplate.opsForValue().set("code::" + phone, code, 5, TimeUnit.MINUTES);
            return new R(200, "发送成功", null);
        }
        return new R(500, "手机号未注册", null);
    }

    @PostMapping("login")
    public R login(@RequestBody LoginVo loginVo) {
        //1.校验验证码
        String code = redisTemplate.opsForValue().get("code::" + loginVo.getPhone());
        String phone = loginVo.getPhone();
        if (StringUtils.hasText(loginVo.getCode()) && loginVo.getCode().equals(code)) {
            //2.登录成功
            if (phone.equals("13938300924")) {
               // redisTemplate.delete("code::" + phone);
                return new R(200, "登录成功", null);
            } else {
                return new R(500, "手机号未注册", null);
            }
        }
            return new R(500, "验证码错误", null);

    }
}

entity

package com.zql.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @program: spring-boot01
 * @description:
 * @author: 
 * @create: 2024-07-23 19:18
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private String name;
    private int age;
}

vo

package com.zql.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @program: spring-boot01
 * @description:
 * @author: 
 * @create: 2024-07-24 18:46
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginVo {
    private String phone;
    private String code;
}
package com.zql.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @program: spring-boot01
 * @description:
 * @author: 
 * @create: 2024-07-24 18:40
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class R {
    private Integer code;
    private String msg;
    private Object data;
}

发送短信的配置类 里面带****的使用自己的配置就行

package com.zql;

import cn.hutool.captcha.generator.RandomGenerator;
import com.aliyun.teaopenapi.Client;
import org.springframework.beans.factory.annotation.Value;

/**
 * @Author: 
 * @Description:
 * @Date: Create in 17:30 2024/7/23
 */
public class SendMsgUtil {
    /**
     * 使用AK&SK初始化账号Client
     * @return Client
     * @throws Exception
     */
    @Value("${aliyun.msg.accessKeyId}")
    private static String accessKeyId;
    public static Client createClient() throws Exception {
        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
                // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。
                .setAccessKeyId("**************")

                // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
                .setAccessKeySecret("****************");
        // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
        config.endpoint = "dysmsapi.aliyuncs.com";
        return new com.aliyun.teaopenapi.Client(config);
    }

    /**
     * API 相关
     * @return OpenApi.Params
     */
    public static com.aliyun.teaopenapi.models.Params createApiInfo() throws Exception {
        com.aliyun.teaopenapi.models.Params params = new com.aliyun.teaopenapi.models.Params()
                // 接口名称
                .setAction("SendSms")
                // 接口版本
                .setVersion("2017-05-25")
                // 接口协议
                .setProtocol("HTTPS")
                // 接口 HTTP 方法
                .setMethod("POST")
                .setAuthType("AK")
                .setStyle("RPC")
                // 接口 PATH
                .setPathname("/")
                // 接口请求体内容格式
                .setReqBodyType("json")
                // 接口响应体内容格式
                .setBodyType("json");
        return params;
    }

    public static String sendCode(String phone) throws Exception {
        com.aliyun.teaopenapi.Client client = createClient();
        com.aliyun.teaopenapi.models.Params params = createApiInfo();
        // query params
        java.util.Map<String, Object> queries = new java.util.HashMap<>();
        queries.put("PhoneNumbers", phone);
        queries.put("SignName", "智友");
        queries.put("TemplateCode", "SMS_173342144"); //您正在申请手机注册,验证码为:${code},5分钟内有效!
        RandomGenerator randomGenerator=new RandomGenerator("0123456789",6);
        String code= randomGenerator.generate();
        queries.put("TemplateParam", "{\"code\":\""+code+"\"}");
        // runtime options
        com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
        com.aliyun.teaopenapi.models.OpenApiRequest request = new com.aliyun.teaopenapi.models.OpenApiRequest()
                .setQuery(com.aliyun.openapiutil.Client.query(queries));
        // 复制代码运行请自行打印 API 的返回值
        // 返回值为 Map 类型,可从 Map 中获得三类数据:响应体 body、响应头 headers、HTTP 返回的状态码 statusCode。
        client.callApi(params, request, runtime);
        return code;
    }
}

测试

package com.zql;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * @program: spring-boot01
 * @description:
 * @author: 
 * @create: 2024-07-23 20:40
 **/
@SpringBootTest

class Test01 {
    @Test
    public void test01()throws Exception{
        System.out.println(SendMsgUtil.sendCode("*********"));
    }
}

5.1 注册登录功能

6. redis的使用场景-热点数据缓存

6.1 什么是缓存?

为了把一些经常访问的数据,放入缓存中以减少对数据库的访问频率。从而减少数据库的压力,提高程序的性能。【内存中存储】

6.2 缓存的原理

6.3 什么样的数据适合放入缓存中

1. 查询频率高且修改频率低
2. 数据安全性低

6.4 哪个组件可以作为缓存

  1. redis组件

  2. memory组件

  3. ehcache组件

6.5 java使用redis如何实现缓存功能

配置文件

# 应用服务 WEB 访问端口
server.port=8080

#数据库
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/zql?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456


#映射文件路径
mybatis-plus.mapper-locations=classpath*:mapper/*.xml

#配置日志--sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

#redis 配置
spring.redis.host=192.168.100.104
spring.redis.port=6379
spring.redis.database=1
spring.redis.jedis.pool.max-active=100
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=10000ms

项目结构

config

package com.zql.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

/**
 * @program: spring-boot01
 * @description:
 * @author: 赵庆龙
 * @create: 2024-07-23 19:47
 **/
@Configuration
public class RedisConfig {
    @Bean//把该方法返回的类对象交与spring容器管理
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String,Object> template=new RedisTemplate<>();//创建redisTemplate对象
        RedisSerializer<String> redisSerializer=new StringRedisSerializer();//字符串序列化器
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer<>(Object.class);//json序列化器
        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);
        //hash的key序列化方式
        template.setHashKeySerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(redisSerializer);
        return template;
    }
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> 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;
    }

}

controller

package com.zql.controller;

import com.zql.entity.Dept;
import com.zql.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @program: spring-boot01
 * @description:
 * @author: 
 * @create: 2024-07-25 09:16
 **/
@RestController
@RequestMapping("/dept")
public class DeptController {
    @Autowired
    private DeptService deptService;
    @GetMapping("/getById/{id}")
    public Dept getById(@PathVariable  Integer id) {
        Dept dept = deptService.getById(id);
        return dept;

    }
    @PostMapping("/insert")
    public Dept insert(@RequestBody Dept dept) {
        return deptService.insert(dept);
    }
    @PutMapping("/update")
    public Dept update(@RequestBody Dept dept) {
        return deptService.update(dept);
    }
    @DeleteMapping("/delete/{id}")
    public int delete(@PathVariable Integer id) {
        return deptService.delete(id);
    }
}

dao

package com.zql.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zql.entity.Dept;

/**
 * @program: spring-boot01
 * @description:
 * @author:
 * @create: 2024-07-24 20:00
 **/
public interface DeptDao extends BaseMapper<Dept> {
}

entity

package com.zql.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @program: spring-boot01
 * @description:
 * @author: 
 * @create: 2024-07-24 19:58
 **/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_dept")
public class Dept {
    @TableId(type = IdType.AUTO)
    private Integer did;
    private String dname;
    private String loc;

}

service

package com.zql.service;

import com.zql.entity.Dept;

/**
 * @program: spring-boot01
 * @description:
 * @author: 
 * @create: 2024-07-24 20:02
 **/
public interface DeptService {

    public Dept getById(Integer id);
    public Dept insert(Dept dept);
    public Dept update(Dept dept);
    public int delete(Integer id);

}
package com.zql.service.impl;

import com.zql.dao.DeptDao;
import com.zql.entity.Dept;
import com.zql.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

/**
 * @program: spring-boot01
 * @description:
 * @author: 
 * @create: 2024-07-24 20:07
 **/
@Service
public class DeptServiceImpl01 implements DeptService {

    @Autowired
    private DeptDao deptDao;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @Override
    public Dept getById(Integer id) {
        //1.查询redis缓存是否命中
        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        Object o=ops.get("dept::"+id);
        //表示缓存命中
        if (o!=null){
            return (Dept) o;
        }
        //查询数据库
        Dept dept = deptDao.selectById(id);
        if (dept!=null){
            ops.set("dept::"+id,dept);
        }
        return dept;
    }

    @Override
    public Dept insert(Dept dept) {
        int insert = deptDao.insert(dept);
        return dept;
    }

    @Override
    public Dept update(Dept dept) {
     // 更新数据库
        int i = deptDao.updateById(dept);
        if (i>0){
            //删除缓存
            redisTemplate.opsForValue().set("dept::"+dept.getDid(),dept);
        }
        return dept;
    }

    @Override
    public int delete(Integer id) {
        int i = deptDao.deleteById(id);
        if (i>0){
            //删除缓存
            redisTemplate.delete("dept::"+id);
        }
        return i;
    }
}

发现: 业务层代码除了要维护核心业务功能外,额外还要维护缓存的代码。

如何解决: 使用AOP面向切面编程。

spring框架也能想到。---aop切面来解决。

6.6 使用缓存注解完成缓存功能

package com.zql.service.impl;

import com.zql.dao.DeptDao;
import com.zql.entity.Dept;
import com.zql.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

/**
 * @program: spring-boot01
 * @description:
 * @author: 
 * @create: 2024-07-24 20:07
 **/
@Service
public class DeptServiceImpl implements DeptService {

    @Autowired
    private DeptDao deptDao;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    //Cacheable:表示查询时使用的注解。 cacheNames:缓存的名称  key:缓存的唯一表示值
    //   1. 查询缓存中是否存在名称为cacheNames::key的值
    //2.如果存在则方法不会执行
    //   3. 如果不存在则执行方法体并把方法的返回结果放入缓存中cacheNames::key
  @Cacheable(cacheNames = "dept",key = "#id")
    @Override
    public Dept getById(Integer id) {
        //1.查询redis缓存是否命中
        //查询数据库
        Dept dept = deptDao.selectById(id);

        return dept;
    }


    @Override
    public Dept insert(Dept dept) {
        int insert = deptDao.insert(dept);
        return dept;
    }
    //CachePut:表示修改时使用的注解.
    // 1. 先执行方法体
    // 2. 把方法的返回结果放入缓存中
@CachePut(cacheNames = "dept",key = "#dept.did")
    @Override
    public Dept update(Dept dept) {
     // 更新数据库
         int i = deptDao.updateById(dept);

        return dept;
    }
    //CacheEvict:表示删除时使用的注解
    // 1. 先执行方法体
    // 2. 把缓存中名称为cacheNames::key的值删除

    @CacheEvict(cacheNames = "dept",key = "#id")
    @Override
    public int delete(Integer id) {
        int i = deptDao.deleteById(id);
        return i;
    }
}

开启缓存注解

7. redis的使用场景--分布式锁.

模拟高并发:---jmeter压测工具

通过压测发现库存超卖和重卖了。---解决办法使用锁

syn和lock锁。

package com.ykq.service;

import com.ykq.dao.StockDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @program: distinct-lock
 * @description:
 * @author: 
 * @create: 2023-08-31 16:49
 **/
@Service
public class StockService01 {

    @Autowired
    private StockDao stockDao;


    //如果在单线程下---该功能没有任何问题。
    //如果在多线程的情况---jmeter压测工具---发现出现线程安全问题了。==使用锁synchronized ()或lock锁。
    //发现使用锁之前没有问题了。但是如果该项目是一个集群。--发现在集群的情况下本地锁【只对当前工程有效】无效了。
    //解决方案就是集群工程共用一把锁就行。---可以使用redis来解决问题。
    public String decrement(Integer productid) {
        //根据id查询商品的库存
        synchronized (this) {
            int num = stockDao.findById(productid);
            if (num > 0) {
                //修改库存
                stockDao.update(productid);
                System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
                return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
            } else {
                System.out.println("商品编号为:" + productid + "的商品库存不足。");
                return "商品编号为:" + productid + "的商品库存不足。";
            }
        }
    }
}

上面使用syn和lock虽然解决了并发问题,但是我们未来项目部署时可能要部署集群模式。

nginx代理集群

通过压测发现本地锁 无效了。使用redis解决分布式锁文件

package com.ykq.service;

import com.ykq.dao.StockDao;
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 java.util.concurrent.TimeUnit;

/**
 * @program: distinct-lock
 * @description:
 * @author: 
 * @create: 2023-08-31 16:49
 **/
@Service
public class StockService {

    @Autowired
    private StockDao stockDao;
    @Autowired
    private StringRedisTemplate redisTemplate;
    //
    public String decrement(Integer productid) {
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
        //1.获取共享锁资源
        Boolean flag = opsForValue.setIfAbsent("product::" + productid, "1111", 30, TimeUnit.SECONDS);
        //表示获取锁成功
        if(flag) {
            try {
                //根据id查询商品的库存
                int num = stockDao.findById(productid);
                if (num > 0) {
                    //修改库存
                    stockDao.update(productid);
                    System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
                    return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
                } else {
                    System.out.println("商品编号为:" + productid + "的商品库存不足。");
                    return "商品编号为:" + productid + "的商品库存不足。";
                }
            }finally {
                //释放锁资源
                redisTemplate.delete("product::"+productid);
            }
        }else{
            //休眠100毫秒 在继续抢锁
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return decrement(productid);
        }
    }
}

redis超时问题[业务代码执行时间超过了上锁时间]. 第三方redisson

引入redisson依赖
      <!--①依赖-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.24.3</version>
        </dependency>

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redisson(){
        Config config = new Config();
//        //连接的为redis集群
//        config.useClusterServers()
//                // use "rediss://" for SSL connection
//                .addNodeAddress("redis://127.0.0.1:7181","","","")
//        ;
        //连接单机
        config.useSingleServer().setAddress("redis://192.168.111.188:6379");
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}
package com.ykq.service;

import com.ykq.dao.StockDao;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
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 java.util.concurrent.TimeUnit;


@Service
public class StockService {

    @Autowired
    private StockDao stockDao;
    @Autowired
    private RedissonClient redisson;

    //
    public String decrement(Integer productid) {
        RLock lock = redisson.getLock("product::" + productid);
        lock.lock();
        try {
            //根据id查询商品的库存: 提前预热到redis缓存中
            int num = stockDao.findById(productid);
            if (num > 0) {
                //修改库存---incr---定时器[redis  数据库同步]
                stockDao.update(productid);
                System.out.println("商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个");
                return "商品编号为:" + productid + "的商品库存剩余:" + (num - 1) + "个";
            } else {
                System.out.println("商品编号为:" + productid + "的商品库存不足。");
                return "商品编号为:" + productid + "的商品库存不足。";
            }
        }finally {
            lock.unlock();
        }
    }
}

;