何小豆儿在此 发表于 2024-12-14 15:13:28

springboot3整合redis

来源于https://www.bilibili.com/video/BV1UC41187PR/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=865f32e12aef524afb83863069b036aa
一、整合redis

1.创建项目文件

https://i-blog.csdnimg.cn/direct/540cd7fb5e364c07810abc35b3d6daf4.png
2.添加依赖

<dependencies>
      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
      </dependency>

      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
      </dependency>

      <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
      </dependency>

      <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
      </dependency>

      <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
      </dependency>

      <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.5</version>
      </dependency>
      <!-- mybatis-plus会自动调用mybatis,但mybatis-spring版本和springboot3+版本的spring会不匹配,所以要升版本-->
      <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>3.0.3</version>
      </dependency>
    </dependencies> 3.配置文件

后缀名改为yml
配置redis、mysql数据源、日记
server:
port: 9999

spring:
data:
    redis:
      port: 6379
      host: localhost

datasource:
    username: root
    password: *****
    url: jdbc:mysql:///testdb
   
logging:
level:
    com.qqcn: debug
4.创建redis配置类

@EnableCaching该注解为启动缓存管理
redisTemplate对象用来操作redis
redisCacheManager缓存管理器
此中setKey(Serializer)序列化 存到redis中的数据更加直观
package com.qqcn.config;

import org.springframework.cache.annotation.EnableCaching;
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.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
      RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
      redisTemplate.setConnectionFactory(factory);
      redisTemplate.setKeySerializer(new StringRedisSerializer());
      redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
      redisTemplate.setHashKeySerializer(new StringRedisSerializer());
      redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
      return redisTemplate;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate) {// 明确指定参数化类型
      if (redisTemplate == null || redisTemplate.getConnectionFactory() == null) {
            // 处理错误情况,例如抛出异常或采取默认行为
            throw new RuntimeException("RedisTemplate or its connection factory is null");
      }
      RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());

      RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
      return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
    }
}
二、RedisTemplate操作-String

创建测试类演示
https://i-blog.csdnimg.cn/direct/6aee27b6bf5d4bc5a6a71df2060ea786.png

1.如果现在有一个登录的token想要存在redis里面

设置了有效期
package com.qqcn;

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.RedisTemplate;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@SpringBootTest
public class RedisTest {

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    @Test
    public void testString(){
      String key = "user:token:0001";
//      实际当中可以用jwt
      redisTemplate.opsForValue().set(key, UUID.randomUUID().toString(),30, TimeUnit.MINUTES);
      System.out.println(redisTemplate.opsForValue().get(key));
    }
} 启动redis
https://i-blog.csdnimg.cn/direct/e30daf3abdbc4c0cbc7ca315f38595ec.png
启动乐成
https://i-blog.csdnimg.cn/direct/19fdbeeef4aa4aeaa34900081406cc44.png
token已存入
https://i-blog.csdnimg.cn/direct/7581d1d724944337911a14528c014e1b.png

2.如果我们要对一篇文章进行访问数的统计

每检察一次都进行计数
@Test
    public void testString2(){
      String key = "article:A00001:viewsCount";
      redisTemplate.opsForValue().increment(key);
      System.out.println(redisTemplate.opsForValue().get(key));
    } 第一次执行是1,第二次就是2了
https://i-blog.csdnimg.cn/direct/cae3debadef1423abca9fe54bd868ded.png
https://i-blog.csdnimg.cn/direct/6fcfbf4bc57e46b79cfd1b3440ab9bb3.png

3.如果想要存储一个对象

如果一个对象无需修改而是仅查询作用,那么存String也是一个很好的选择
在配置类中做了一个json的序列化处理,所以存储对象进去最终会被转换成json字符串
@Test
    public void testString3(){
      Map<String,Object> user = new HashMap<>();
      user.put("id","0001");
      user.put("name","张三丰");
      user.put("age",28);
      String key = "user:0001";
      redisTemplate.opsForValue().set(key,user);
      System.out.println(redisTemplate.opsForValue().get(key));
    } 可以看到被存储为json格式
https://i-blog.csdnimg.cn/direct/30bd8163f4eb450d96237c5b1776de43.png
https://i-blog.csdnimg.cn/direct/60e22f0ea0734448a426d6b129d5d837.png


三、RedisTemplate操作-Hash

好比说在做一个电商类项目的时间,会有一个购物车对象
Hash是kv键值类型,值类型类似map结构,更得当用来生存对象
如果用opsForHash().put(Object key, Object hashkey, Object value)此中hashkey表示右边的k,value表示右边的v,如果一个一个存比力繁琐
所以我们这里用opsForHash().putAll(Object key,Map m)
https://i-blog.csdnimg.cn/direct/2522006eb2334666b564e7e4a71106a0.png
测试取出购物车有哪些商品
@Test
    public void testHash(){
      String key = "user:0001:cart";
      Map<String, Object> shoppingCart = new HashMap<>();
      shoppingCart.put("cartId", "123456789");
      shoppingCart.put("userId", "987654321");
      List<Map<String, Object>> items = List.of(
                Map.of("itemId", "1", "itemName", "手机", "price", 999.99, "quantity", 1),
                Map.of("itemId", "2", "itemName", "笔记本电脑", "price", 1499.99, "quantity",
                        2),
                Map.of("itemId", "3", "itemName", "耳机", "price", 49.99, "quantity", 3)
      );
      shoppingCart.put("items", items);
      shoppingCart.put("totalAmount", 3149.92);
      shoppingCart.put("creationTime", "2046-03-07T10:00:00");
      shoppingCart.put("lastUpdateTime", "2046-03-07T12:30:00");
      shoppingCart.put("status", "未结账");
      redisTemplate.opsForHash().putAll(key, shoppingCart);
      System.out.println(redisTemplate.opsForHash().get(key, "items"));
    } https://i-blog.csdnimg.cn/direct/6c9891914b2b47f1aeb11bf8daa2ec71.png


四、RedisTemplate操作-Set

是数据库中一种无序的、不重复的数据结构,用于存储一组唯一的元素
好比想去存储某个用户的粉丝有哪些人
redis中统计黑白常的快的
添加了三个粉丝add进去
@Test
    public void testSet(){
      String key = "author:0001:fans";
      redisTemplate.opsForSet().add(key,"张三","李四","王五");
      System.out.println("粉丝量:" + redisTemplate.opsForSet().size(key));
    } 结果为粉丝量为3
https://i-blog.csdnimg.cn/direct/dcf0c1ca7d5c4c6eaf3ea6dd15965188.png
https://i-blog.csdnimg.cn/direct/5128a14b67a44ad0a9fe784f52e800b1.png


五、RedisTemplate操作-ZSet

是一个有序且唯一的集合,每个元素都与一个浮点数分数干系联,使得集合中的元素可以根据分数进行排序,默认按照分数进行升序排序。
若要降序查询:
redisTemplate.opsForZSet().reverseRange(key,0,-1)
添加成员的时间有一个分数值,通过分数值进行排序处理
好比我们想存储某个用户的挚友列表
除了添加用户的信息,还可以添加时间,可以根据时间获取挚友的列表
@Test
    public void testZSet(){
      String key = "user:0001:friends";
      //取添加好友的时间的毫秒值
      redisTemplate.opsForZSet().add(key,"张三",System.currentTimeMillis());
      redisTemplate.opsForZSet().add(key,"李四",System.currentTimeMillis());
      redisTemplate.opsForZSet().add(key,"王五",System.currentTimeMillis());

      //倒序
      Set<Object> set = redisTemplate.opsForZSet().reverseRange(key, 0, -1);
      System.out.println(set);
    } 最后存入的在最前面(按时间的降序来处理)
https://i-blog.csdnimg.cn/direct/36b8bbfa7da44e5999c51624068e5a88.png


六、RedisTemplate操作-List

一种简单的字符串列表,按照插入顺序排序
在redis中可以把List想象成通道,两边都是开放的,可以左进右出,也可以右进左出,或者左进左出也可以
可以用List实现队列的功能,好比现有一个订单的哀求处理,在买商品的时间很大概因为并发的题目引发别的题目,可以利用队列让订单做一个排队
存入数据:
redisTemplate.opsForList().leftPush(key,order1);
获取并移除数据:
redisTemplate.opsForList().rightPop(key)
@Test
    public void testList(){
      String key = "order:queue";

      //订单1
      Map<String, Object> order1 = new HashMap<>();
      order1.put("orderId", "1001");
      order1.put("userId", "2001");
      order1.put("status", "已完成");
      order1.put("amount", 500.75);
      order1.put("creationTime", "2024-08-09T09:30:00");
      order1.put("lastUpdateTime", "2024-08-9T10:45:00");
      order1.put("paymentMethod", "在线支付");
      order1.put("shoppingMethod", "自提");
      order1.put("remarks", "尽快处理");

      //订单2
      Map<String, Object> order2 = new HashMap<>();
      order2.put("orderId", "1002");
      order2.put("userId", "2002");
      order2.put("status", "待处理");
      order2.put("amount", 280.99);
      order2.put("creationTime", "2024-08-09T11:00:00");
      order2.put("lastUpdateTime", "2024-08-09T11:00:00");
      order2.put("paymentMethod", "货到付款");
      order2.put("shoppingMethod", "快递配送");
      order2.put("remarks", "注意保鲜");

      // A程序接收订单请求并将其加入队列
      redisTemplate.opsForList().leftPush(key,order1);
      redisTemplate.opsForList().leftPush(key,order2);
      // B程序从订单队列中获取订单数据并处理
      System.out.println("处理订单:" + redisTemplate.opsForList().rightPop(key));
    } 从右边取出的数据:订单1https://i-blog.csdnimg.cn/direct/18c8a4648604430b9eba5961d4eee72c.png
还剩订单2
https://i-blog.csdnimg.cn/direct/ae14eab4862747b6b8a6bffe25052a57.png


七、@RedisHash注解

除了用RedisTemplate来操作hash数据,还提供了@RedisHash注解
用于将java对象直接映射到redis的hash数据当中
java对象的存储和检索变得更加简单
关键步调:
1.@RedisHash注解标注在实体类上
2.创建接口并继续CrudRepository

1.创建一个实体类

@Id表示id为主键
https://i-blog.csdnimg.cn/direct/89650058f1354ef1a9a55c7f955ba1f3.png
package com.qqcn.entity;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

@RedisHash
@Data
public class user {
    @Id
    private Integer id;
    private String name;
    private Integer age;
    private String phone;
}

2.创建一个接口

想要用此注解还必要创建一个接口
继续CrudRepository(增删改查)设定泛型第一个是操作的实体,第二个是主键的类型
定义完了接口之后就具备了对对象的基于redis的增删改查的本事
https://i-blog.csdnimg.cn/direct/569f27b73d004c2182b261e9e4b2751e.png
package com.qqcn.repository;

import com.qqcn.entity.User;
import org.springframework.data.repository.CrudRepository;

public interface UserRedisRepository extends CrudRepository<User,Integer> {
}

3.测试类进行测试

注入UserRedisRepository,进行测试类测试
@Autowired
    private UserRedisRepository userRedisRepository;

    @Test
    public void testRedisHash(){
      User user = new User();
      user.setId(100);
      user.setName("张三丰");
      user.setAge(18);
      user.setPhone("18899998888");

      //保存到redis里面
      userRedisRepository.save(user);

      //读取
      Optional<User> redisUser = userRedisRepository.findById(100);
      System.out.println(redisUser);

      //更新
      user.setPhone("18899998866");
      //看数据是否存在,存在就修改,不存在就保存
      userRedisRepository.save(user);

      //再打印一次
      Optional<User> redisUser2 = userRedisRepository.findById(100);
      System.out.println(redisUser2);

      //删除
      //userRedisRepository.deleteById(100);

      //查看id是否存在
      boolean exists = userRedisRepository.existsById(100);
      System.out.println(exists);
    }
测试结果
https://i-blog.csdnimg.cn/direct/e175cab454a1412d9feab0b989f0ff9f.png
因为没有指定key,key默认是 路径:id
https://i-blog.csdnimg.cn/direct/09e2b701908c4ee6beaace805a331d62.png


八、缓存管理注解

redis最常见的用途就是用作缓存
而springboot的缓存管理功能旨在帮助开发人员轻松地在应用步伐中利用缓存,以进步性能和响应速度,它提供了一套注解和配置,使得开发人员可以在方法级别上进行缓存控制,并且支持多种缓存存储提供步伐
https://i-blog.csdnimg.cn/direct/dbe4d6a23c6a48d5a4c13774b96c4f22.png
@Cacheable一样平常被用于查询方法,先在redis看看有无数据,如果有就直接在缓存里面拿,如果没有就查询数据库,并且会将查询到的数据放入缓存
@CachePut主要被用于更新缓存,一样平常大概用在新增和修改的方法上面
@CacheEvict主要用于扫除缓存,一样平常会用在删除方法的上面

1.预备表

CREATE TABLE product (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    price DECIMAL(10, 2) NOT NULL,
    stock INT NOT NULL
); INSERT INTO product (name, description, price, stock) VALUES
('iPhone 15', '最新的iPhone型号', 8999.99, 100),
('三星Galaxy S24', '旗舰安卓手机', 7899.99, 150),
('MacBook Pro', '专业人士的强大笔记本电脑', 15999.99, 50),
('iPad Air', '性能强劲的便携式平板电脑', 5599.99, 200),
('索尼PlayStation 6', '下一代游戏机', 4499.99, 75); https://i-blog.csdnimg.cn/direct/a60a5e0841ef40a8b552408cfb46837f.png
https://i-blog.csdnimg.cn/direct/78b2c133a1ea4d75912ae3a87d060ce3.png

2.根据表创建实体类

package com.qqcn.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName
public class Product {
    @TableId
    private Integer id;
    private String name;
    private String description;
    private Double price;
    private Integer stock;
}

3.创建一个mapper接口

到数据库当中查数据,必须要有数据库操作的存在
前面创建项目的时间是添加了mybatis-plus的依赖
所以创建一个mapper来简化数据库的操作
在mybatis-plus里面想要实现增删改查必要在接口上面继续BaseMapper,泛型里面指定实体是谁
package com.qqcn.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qqcn.entity.Product;

public interface ProductMapper extends BaseMapper<Product> {
}
注意:要在启动类上加一个注解@MapperScan扫描一下
https://i-blog.csdnimg.cn/direct/9741bf85c00d4406a6dfefc2c1f735ca.png

4.创建一个service

在此创建增删改查的几个方法
package com.qqcn.service;

import com.qqcn.entity.Product;

public interface ProductService {
    //查询
    public Product getProductById(Integer id);
    //新增还是要写一个返回值
    //因为我们要用注解@CachePut缓存数据是只能缓存方法的返回值
    public Product addProduct(Product product);
    //修改
    public Product updateProduct(Product product);
    //删除
    public void deleteProductById(Integer id);
}

5.创建相应的实现类

package com.qqcn.service.impl;

import com.qqcn.entity.Product;
import com.qqcn.mapper.ProductMapper;
import com.qqcn.service.ProductService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

@Service
public class ProductServiceImpl implements ProductService {
    @Resource
    private ProductMapper productMapper;

    @Override
    public Product getProductById(Integer id) {
      return productMapper.selectById(id);
    }

    @Override
    public Product addProduct(Product product) {
      productMapper.insert(product);
      return product;
    }

    @Override
    public Product updateProduct(Product product) {
      productMapper.updateById(product);
      return product;
    }

    @Override
    public void deleteProductById(Integer id) {
      productMapper.deleteById(id);
    }
}

6.添加缓存存储的注解

我们希望看到的是
首先查找是先从redis里面找数据,如果有的话sql就不执行,如果sql执行了的话,日记里面会体现
value指定缓存的名称,key是生存在redis中的键(必要拼接id用单引号)
设置之后增删改查的同时会作缓存同步的处理,作高频访问的时间,可以进步效率,同时降低mysql压力
package com.qqcn.service.impl;

import com.qqcn.entity.Product;
import com.qqcn.mapper.ProductMapper;
import com.qqcn.service.ProductService;
import jakarta.annotation.Resource;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class ProductServiceImpl implements ProductService {
    @Resource
    private ProductMapper productMapper;

    @Cacheable(value = "product", key = "'product:' + #id")
    @Override
    public Product getProductById(Integer id) {
      return productMapper.selectById(id);
    }

    @CachePut(value = "product", key = "'product:'+ #product.id")
    @Override
    public Product addProduct(Product product) {
      productMapper.insert(product);
      return product;
    }

    @CachePut(value = "product", key = "'product:'+ #product.id")
    @Override
    public Product updateProduct(Product product) {
      productMapper.updateById(product);
      return product;
    }

    @CacheEvict(value = "product", key = "'product:' + #id")
    @Override
    public void deleteProductById(Integer id) {
      productMapper.deleteById(id);
    }
}

7.测试类进行测试

(1)测试查询

@Autowired
    private ProductService productService;

    @Test
    public void testQuery(){
      Product product = productService.getProductById(1);
      System.out.println(product);
    } 这里在执行时发生了一个错误java.sql.SQLException: Access denied for user ‘root‘@‘localhost‘ 
堕落在于mysql的密码是数字同时用的是.yml文件 所以我们必要把密码用双引号引起来
spring:
data:
    redis:
      port: 6379
      host: localhost

datasource:
    username: root
    password: "*****"
    url: jdbc:mysql://localhost:3306/testdb
更改之后测试乐成,测试结果如下
https://i-blog.csdnimg.cn/direct/fb423a8a7ec8404b88a7baa1801b651a.png
redis中已缓存
https://i-blog.csdnimg.cn/direct/5b57eb15cd894f119b758db9a76e7e28.png
重新测第二次
没有sql语句,因为我们可以在缓存中读取了
https://i-blog.csdnimg.cn/direct/c001f52d6dd54b59b1ab44dac25d9aed.png

(2)测试更新(和新增同理,这里只演示更新)

@Test
    public void testUpdate(){
      //将刚刚查询到的名字修改一下
      Product product = productService.getProductById(1);
      product.setName("苹果15");

      //这里更新表一定会有sql日志,更新了缓存
      productService.updateProduct(product);

      //修改之后再一次查询,这里就没有sql日志了
      Product product2 = productService.getProductById(1);
      System.out.println(product2);
    } 测试结果,只有中间更新的时间有sql日记,查询走的都是缓存
https://i-blog.csdnimg.cn/direct/2638a002afc640a68bb3445e7d0c8806.png
redis已同步更改
https://i-blog.csdnimg.cn/direct/a0d18ff9fb544f82a9b8ba2242fe8274.png

(3)测试删除

删完的同时,缓存中的数据也会不存在
@Test
    public void testDelete(){
      productService.deleteProductById(1);
    } 测试结果,会打印一个删除的sql日记
https://i-blog.csdnimg.cn/direct/0d0b15cf2c634f5cb9814d11173088ee.png
mysql数据表中该数据被删除
https://i-blog.csdnimg.cn/direct/322ee4e3f8a94f6892d40652b91864f3.png
redis中的缓存也没有了
https://i-blog.csdnimg.cn/direct/fa62c8c558da47949ea2e12d0cc98c8d.png

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: springboot3整合redis