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.创建项目文件

2.添加依赖

XML 复制代码
<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数据源、日志

XML 复制代码
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中的数据更加直观

java 复制代码
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

创建测试类演示

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

设置了有效期

java 复制代码
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

启动成功

token已存入

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

每查看一次都进行计数

java 复制代码
@Test
    public void testString2(){
        String key = "article:A00001:viewsCount";
        redisTemplate.opsForValue().increment(key);
        System.out.println(redisTemplate.opsForValue().get(key));
    }

第一次执行是1,第二次就是2了

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

如果一个对象无需修改而是仅查询作用,那么存String也是一个很好的选择

在配置类中做了一个json的序列化处理,所以存储对象进去最终会被转换成json字符串

java 复制代码
@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格式

三、RedisTemplate操作-Hash

比如说在做一个电商类项目的时候,会有一个购物车对象

Hash是kv键值类型,值类型类似map结构,更适合用来保存对象

如果用opsForHash().put(Object key, Object hashkey, Object value)其中hashkey表示右边的k,value表示右边的v,如果一个一个存比较繁琐

所以我们这里用opsForHash().putAll(Object key,Map m)

测试取出购物车有哪些商品

java 复制代码
@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"));
    }

四、RedisTemplate操作-Set

是数据库中一种无序的、不重复的数据结构,用于存储一组唯一的元素

比如想去存储某个用户的粉丝有哪些人

redis中统计是非常的快的

添加了三个粉丝add进去

java 复制代码
@Test
    public void testSet(){
        String key = "author:0001:fans";
        redisTemplate.opsForSet().add(key,"张三","李四","王五");
        System.out.println("粉丝量:" + redisTemplate.opsForSet().size(key));
    }

结果为粉丝量为3

五、RedisTemplate操作-ZSet

是一个有序且唯一的集合,每个元素都与一个浮点数分数相关联,使得集合中的元素可以根据分数进行排序,默认按照分数进行升序排序。

若要降序查询:

redisTemplate.opsForZSet().reverseRange(key,0,-1)

添加成员的时候有一个分数值,通过分数值进行排序处理

比如我们想存储某个用户的好友列表

除了添加用户的信息,还可以添加时间,可以根据时间获取好友的列表

java 复制代码
@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);
    }

最后存入的在最前面(按时间的降序来处理)

六、RedisTemplate操作-List

一种简单的字符串列表,按照插入顺序排序

在redis中可以把List想象成通道,两边都是开放的,可以左进右出,也可以右进左出,或者左进左出也可以

可以用List实现队列的功能 ,比如现有一个订单的请求处理,在买商品的时候很可能因为并发的问题引发别的问题,可以利用队列让订单做一个排队

存入数据:

redisTemplate.opsForList().leftPush(key,order1);

获取并移除数据:

redisTemplate.opsForList().rightPop(key)

java 复制代码
@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));
    }

从右边取出的数据:订单1

还剩订单2

七、@RedisHash注解

除了用RedisTemplate来操作hash数据,还提供了@RedisHash注解

用于将java对象直接映射到redis的hash数据当中

java对象的存储和检索变得更加简单

关键步骤:

1.@RedisHash注解标注在实体类上

2.创建接口并继承CrudRepository

1.创建一个实体类

@Id表示id为主键

java 复制代码
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的增删改查的能力

java 复制代码
package com.qqcn.repository;

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

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

3.测试类进行测试

注入UserRedisRepository,进行测试类测试

java 复制代码
@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);
    }

测试结果

因为没有指定key,key默认是 路径:id

八、缓存管理注解

redis最常见的用途就是用作缓存

而springboot的缓存管理功能旨在帮助开发人员轻松地在应用程序中使用缓存,以提高性能和响应速度,它提供了一套注解和配置,使得开发人员可以在方法级别上进行缓存控制,并且支持多种缓存存储提供程序

@Cacheable一般被用于查询方法,先在redis看看有无数据,如果有就直接在缓存里面拿,如果没有就查询数据库,并且会将查询到的数据放入缓存

@CachePut主要被用于更新缓存,一般可能用在新增和修改的方法上面

@CacheEvict主要用于清除缓存,一般会用在删除方法的上面

1.准备表

sql 复制代码
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
);
sql 复制代码
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);

2.根据表创建实体类

java 复制代码
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,泛型里面指定实体是谁

java 复制代码
package com.qqcn.mapper;

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

public interface ProductMapper extends BaseMapper<Product> {
}

注意:要在启动类上加一个注解@MapperScan扫描一下

4.创建一个service

在此创建增删改查的几个方法

java 复制代码
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.创建相应的实现类

java 复制代码
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压力

java 复制代码
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)测试查询
java 复制代码
@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文件 所以我们需要把密码用双引号引起来

java 复制代码
spring:
  data:
    redis:
      port: 6379
      host: localhost

  datasource:
    username: root
    password: "*****"
    url: jdbc:mysql://localhost:3306/testdb

更改之后测试成功,测试结果如下

redis中已缓存

重新测第二次

没有sql语句,因为我们可以在缓存中读取了

(2)测试更新(和新增同理,这里只演示更新)
java 复制代码
@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日志,查询走的都是缓存

redis已同步更改

(3)测试删除

删完的同时,缓存中的数据也会不存在

java 复制代码
@Test
    public void testDelete(){
        productService.deleteProductById(1);
    }

测试结果,会打印一个删除的sql日志

mysql数据表中该数据被删除

redis中的缓存也没有了

相关推荐
2402_857589369 分钟前
Spring Boot编程训练系统:实战开发技巧
数据库·spring boot·性能优化
zy01010112 分钟前
使用 IDEA 创建 Java 项目(二)
java·人工智能·intellij-idea
luckilyil13 分钟前
Spring MVC 与 JSP 数据传输
java·spring·mvc
向阳121814 分钟前
JVM入门教程:从概念到实践
java·jvm
飞升不如收破烂~16 分钟前
Spring Initializr 和 Maven 是创建 Spring 项目时可以使用的两种不同的工具
java·spring·maven
液态不合群17 分钟前
Spring AI 再更新:如何借助全局参数实现智能数据库操作与个性化待办管理
数据库·人工智能·spring
2401_8574396920 分钟前
Spring Boot编程训练系统:深入设计与实现
java·spring boot·后端
2401_8576363922 分钟前
电商系统设计与实现:Spring Boot框架
数据库·spring boot·后端
码蜂窝编程官方24 分钟前
【含开题报告+文档+PPT+源码】基于Spring Boot智能综合交通出行管理平台的设计与实现
java·vue.js·spring boot·后端·spring
晚睡早起₍˄·͈༝·͈˄*₎◞ ̑̑39 分钟前
SpringBoot(三)
java·spring boot·后端