Redis
键值对数据库,基于内存
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--
apache通用连接池
提供了对可复用的对象(如数据库连接,线程等)的池化管理,有助于提高资源的利用率和系统的 性能
使用commons-pool2来管理与Redis数据库的连接池
-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
RedisTemplate
RedisTemplate是Spring Data Redis提供的一个强大的Redis操作模板,用于在Spring应用程序中与Redis数据库进行交互。对redis底层开发包(Jedis,JRedis,RJC)进行了一个封装,定义了五种数据结构的操作方法,异常处理,序列化,发布订阅
opsForValue():操作字符串。
opsForList():操作列表。
opsForHash():操作哈希。
opsForSet():操作集合。
opsForZSet():操作有序集合。
————————————————
Redis序列化
这个就跟它的序列化有关了。就是根据RedisTemplate将数据写入到redis中的时候会经过一个序列化操作,序列化操作后就变成了以上数据格式,这个并不影响程序,因为set写入时会有序列化操作,get获取数据时也就有着这么一个序列话操作,但是对于我们来说时很不友好的
如果不设置默认为JdkSerializationRedisSerializer
配置Redis序列化
redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);则键值对都为一样的序列化
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
//实例化RedisTemplate
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
//设置连接工厂
redisTemplate.setConnectionFactory(connectionFactory);
//指定k,v的序列化方式(json)
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//默认序列化为json格式 key和值都序列化为json格式
// redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer);
//key设置为String类型
redisTemplate.setKeySerializer(new StringRedisSerializer());
//value设置为Json格式
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
将java对象先转化为json数据在传入redis序列化
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
//实例化RedisTemplate
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
//设置连接工厂
redisTemplate.setConnectionFactory(factory);
//指定k,v的序列化方式(json)
Jackson2JsonRedisSerializer<Object> redisSerializer= new Jackson2JsonRedisSerializer<>(Object.class);
//将对象变为json进行传递
ObjectMapper mapper=new ObjectMapper();
//设置序列化的域,ANY表示所有域都序列化.PropertyAccessor.ALL:表示要配置所有的属性访问器,包括字段、getter、setter等。
// JsonAutoDetect.Visibility.ANY:表示所有的属性都是可见的,不论其修饰符是什么,都将被序列化或反序列化。
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
redisSerializer.setObjectMapper(mapper);
//key设置为String类型
redisTemplate.setKeySerializer(new StringRedisSerializer());
//value设置为Json格式
redisTemplate.setValueSerializer(redisSerializer);
//设置hash
//redisTemplate.afterPropertiesSet();:这一行用于确保所有的配置都已经完成。在 Spring 中,afterPropertiesSet
// 方法通常在 bean 的所有属性被设置之后调用,以确保 bean 处于一个有效的状态。
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(redisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
注解@EnableCaching 用于方法级别的缓存当应用使用缓存时候帮助Spirng自动配置缓存和管理缓存
注解通常与 CacheManager
一起使用,以定义缓存的配置和规则。@EnableCaching
注解可以在应用程序上下文中寻找 CacheManager
类型的 bean,并将其用于缓存管理。如果没有明确指定 CacheManager
,Spring 将尝试使用合适的默认实现。
工具类对RedisTemplate的封装
redisTemplate.expire(key, time, TimeUnit.SECONDS);
redisTemplate
: 这是 Spring Data Redis 中的一个模板类,提供了对 Redis 数据存储进行操作的方法。通常,你需要在应用程序中注入一个RedisTemplate
实例,以便与 Redis 进行交互。expire
: 这是 Redis 中的命令,用于设置指定键的过期时间。一旦设置了过期时间,键将在一定时间后被自动删除。在这里,expire
是RedisTemplate
类中的一个方法,它接受三个参数:key
: 要设置过期时间的键。time
: 过期时间的时长,以整数形式表示。在这里,它是一个时间单位的数量,将在下一个参数TimeUnit.SECONDS
中定义的时间单位内过期。TimeUnit.SECONDS
: 过期时间的时间单位。在这里,设置为秒(Seconds),表示time
参数表示的时间单位是秒。
@SuppressWarnings("unchecked")
public void del(String ... key){
if(key!=null&&key.length>0){
if(key.length==1){
redisTemplate.delete(key[0]);
}else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
String... key
是 Java 中的可变参数(Varargs)语法。它允许你在方法调用时传递任意数量的字符串参数。在方法内部,这些参数被视为一个字符串数组。具体来说,String... key
表示一个名为 key
的字符串数组,可以接受零个或多个字符串参数。这样的语法使得在调用方法时可以以更自然的方式传递多个参数,而不需要手动创建数组。例子如下
public class Example {
public static void main(String[] args) {
printKeys("key1", "key2", "key3");
printKeys(); // 也可以不传递任何参数
}
public static void printKeys(String... keys) {
System.out.println("Number of keys: " + keys.length);
for (String key : keys) {
System.out.println("Key: " + key);
}
}
}
注解:@SuppressWarnings(“unchecked”) 它用于告诉编译器忽略特定类型转换的警告信息。在这个具体的情境下,它用于抑制未经检查的转换(unchecked conversion)的警告。
举例:
普通redis存入数据:
List集合存入会成一张表
Redist缓存+Mysql
//配置全局的序列化和redis缓存时间 以便注解使用
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
可以手动在service中进行缓冲操作例子:
@Override
public DemoUser findUserById(int id) {
String key = "user_" + id;
ValueOperations<String,DemoUser> operations = redisTemplate.opsForValue();
//判断redis中是否有键为key的缓存
boolean haskey = redisTemplate.hasKey(key);
if (haskey){
DemoUser user = operations.get(key);
System.out.println("从缓存中获取的数据:"+user.getUserName());
System.out.println("----------------------------------");
return user;
}else {
DemoUser user = demoUserMapper.findUserById(id);
System.out.println("查询数据库中的数据"+ user.getUserName());
System.out.println("--------------------写入数据------------------");
//写入数据
operations.set(key,user,5, TimeUnit.HOURS);
return user;
}
}
注解的方式:
开起注解缓存
@EnableCaching
查询数据缓存@Cacheable
将返回的结果进行缓冲到指定缓冲器中,如果缓存中有数据如果相同的参数被传递,可以直接返回缓存中的结果,而不必再次执行方法体。
1.value(或 cacheNames): 指定缓存的名称,可以指定一个或多个名称。多个名称之间使用逗号分隔。缓存的名称在整个应用程序中必须是唯一的。
2.key: 指定缓存的键。可以使用SpEL表达式来构造键。默认情况下,会使用方法的参数值作为键
@Cacheable(value = "myCache", key = "#userId")
public User getUserById(Long userId) {
// ...
}
3.condition: 指定在满足条件的情况下才进行缓存。条件使用SpEL表达式
@Cacheable(value = “myCache”, condition = “#result != null”)
4.unless: 与 condition
相反,指定在满足条件的情况下不进行缓存。同样使用SpEL
清除缓存**@CacheEvict
**
@CacheEvict
注解用于从缓存中移除一个或多个缓存条目。通常在执行一些更新或删除操作后,为了保持缓存与数据的一致性,可以使用 @CacheEvict
注解清除相应的缓
1.value (cacheNames): 指定要清除的缓存的名称,可以指定一个或多个名称
2.key: 指定要清除的缓存的键。可以使用SpEL表达式来构造键。默认情况下,会使用方法的参数值作为键。
3.allEntries: 如果设置为 true
,则清除缓存中的所有条目,忽略指定的键。默认值为 false
。
4.beforeInvocation: 如果设置为 true
,则在方法执行之前清除缓存,即使方法执行出现异常。如果设置为 false
,则在方法执行成功后才清除缓存。默认值为 false
。
5.condition: 指定在满足条件的情况下才执行缓存清除操作。条件使用SpEL表达式。
缓存更新**@CachePut
**
@CachePut
注解用于将方法的返回值存储到缓存中。与 @Cacheable
注解不同,@CachePut
注解不会检查缓存中是否已经存在相同的键,而是直接将方法的结果存储到缓存中。这在更新缓存中的数据时非常有用。以下是 @CachePut
注解的主要属性和用法:
1.value (cacheNames): 指定缓存的名称,可以指定一个或多个名称。多个名称之间使用逗号分隔。缓存的名称在整个应用程序中必须是唯一的。
2.key: 指定缓存的键。可以使用SpEL表达式来构造键。默认情况下,会使用方法的参数值作为键。
3.condition: 指定在满足条件的情况下才执行缓存更新操作。条件使用SpEL表达式。
4.unless: 指定在满足条件的情况下不进行缓存更新。同样使用SpEL表达式。
@Caching
:
用于组合多个缓存注解。可以在一个方法上同时使用多个缓存注解
@Caching(
cacheable = @Cacheable(value = "myCache", key = "#userId"),
evict = @CacheEvict(value = "otherCache", key = "#result.email")
)
public User getUserById(Long userId) {
// ...
}
@CacheConfig
:
用于在类级别配置缓存的公共属性,这样在方法级别的注解中可以省略一些重复的配置。
@Service
@CacheConfig(cacheNames = "myCache", keyGenerator = "customKeyGenerator")
public class UserService {
@Cacheable(key = "#userId")
public User getUserById(Long userId) {
// ...
}
}
案例
package edcu.com.demo.service.impl;
import edcu.com.demo.dao.SmbmsUserDao;
import edcu.com.demo.entity.SmbmsUser;
import edcu.com.demo.service.SmbmsUserService;
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.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
/**
* (SmbmsUser)表服务实现类
*
* @author makejava
* @since 2023-12-15 18:16:36
*/
@Service
public class SmbmsUserServiceImpl implements SmbmsUserService {
@Resource
private SmbmsUserDao smbmsUserDao;
/**
* 通过ID查询单条数据
*
* @param id 主键
* @return 实例对象
*/
@Override
@Cacheable(value = "smbmsUserCache", key = "#id")
public SmbmsUser queryById(Long id) {
// 如果缓存中存在数据,方法不会执行,直接返回缓存的数据
// 如果缓存中不存在数据,方法会执行,并将结果放入缓存
System.out.println("从数据库中查询数据");
return this.smbmsUserDao.queryById(id);
}
/**
* 分页查询
*
* @param smbmsUser 筛选条件
* @param pageRequest 分页对象
* @return 查询结果
*/
@Override
//@Cacheable(value ="smbmsUserCachePage", key =" 'ALL_' + #pageRequest.pageNumber") page反序列问题无法解决
public Page<SmbmsUser> queryByPage(SmbmsUser smbmsUser, PageRequest pageRequest) {
long total = this.smbmsUserDao.count(smbmsUser);
return new PageImpl<>(this.smbmsUserDao.queryAllByLimit(smbmsUser, pageRequest), pageRequest, total);
}
/**
* 新增数据
*
* @param smbmsUser 实例对象
* @return 实例对象
*/
@Override
@CachePut(value = "smbmsUserCache", key = "#smbmsUser.id")
public SmbmsUser insert(SmbmsUser smbmsUser) {
this.smbmsUserDao.insert(smbmsUser);
updatePageCache();// 删除分页查询的缓存
return smbmsUser;
}
/**
* 修改数据
*
* @param smbmsUser 实例对象
* @return 实例对象
*/
@Override
@CachePut(value = "smbmsUserCache", key = "#smbmsUser.id")
public SmbmsUser update(SmbmsUser smbmsUser) {
this.smbmsUserDao.update(smbmsUser);
this.updatePageCache();// 删除分页查询的缓存
return this.queryById(smbmsUser.getId());
}
/**
* 通过主键删除数据
*
* @param id 主键
* @return 是否成功
*/
@Override
@CacheEvict(value = "smbmsUserCache", key = "#id")
public boolean deleteById(Long id) {
this.updatePageCache();// 删除分页查询的缓存
return this.smbmsUserDao.deleteById(id) > 0;
}
// 手动更新分页查询的缓存
@CacheEvict(value = "smbmsUserCachePage", allEntries = true)
public void updatePageCache() {
System.out.println("手动更新分页查询的缓存");
// 这里可以不做任何实际操作,@CacheEvict 注解会清空缓存,下次查询时会重新加载缓存
}
}
ey = “#id”)
public boolean deleteById(Long id) {
this.updatePageCache();// 删除分页查询的缓存
return this.smbmsUserDao.deleteById(id) > 0;
}
// 手动更新分页查询的缓存
@CacheEvict(value = "smbmsUserCachePage", allEntries = true)
public void updatePageCache() {
System.out.println("手动更新分页查询的缓存");
// 这里可以不做任何实际操作,@CacheEvict 注解会清空缓存,下次查询时会重新加载缓存
}
}