一、整合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中的缓存也没有了