1.spring对于redis封装了两个类
StringRedisTemplate和RedisTemplate,StringRedisTemplate是RedisTemplate的子类,StringRedisTemplate它只能存储字符串类型,无法存储对象类型。要想用StringRedisTemplate存储对象必须把对象转为json字符串。
2.StringRedisTemplate
2.1引入相关的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.2使用StringRedisTemplate
StringRedisTemplate把对每种数据类型的操作,单独封装了相应的内部类。
@SpringBootTest
class LessonspringbootRedisApplicationTests {
@Autowired
private StringRedisTemplate redisTemplate;
@Test
public void test01(){
HashOperations<String, Object, Object> forHash = redisTemplate.opsForHash();
forHash.put("k1","name","张三");
Map<String,String> map = new HashMap<>();
map.put("name","张三");
map.put("age","18");
forHash.putAll("k2",map);
Object o = forHash.get("k1", "name");
System.out.println(o);
Set<Object> k1 = forHash.keys("k1");
System.out.println(k1);
List<Object> k12 = forHash.values("k1");
System.out.println(k12);
Map<Object, Object> k11 = forHash.entries("k1");
System.out.println(k11);
}
@Test
void contextLoads() {
//删除指定的key
//redisTemplate.delete();
//查看所有的key
//redisTemplate.keys();
//是否存在指定的key
//redisTemplate.hasKey();
//对字符串数据类型的操作ValueOperations
ValueOperations<String, String> operations = redisTemplate.opsForValue();
//存储字符串类型--->key value long unit setex()
operations.set("k1","king",30, TimeUnit.SECONDS);
//等价于setnx() 存入成功为true 存入失败为false
Boolean aBoolean = operations.setIfAbsent("K1", "林先生", 30, TimeUnit.SECONDS);
System.out.println(aBoolean);
operations.append("k1","有钱");
String k1 = operations.get("k1");
System.out.println(k1);
}
}
@SpringBootTest
public class Test02 {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void test02(){
HashOperations forHash = redisTemplate.opsForHash();
forHash.put("k1","name",new User(1,"李四",20));
}
@Test
public void test01(){
//当你存储的value类型为对象类型使用redisTemplate
//当存储的value类型为字符串,StringRedisTemplate
ValueOperations forValue = redisTemplate.opsForValue();
//必须指定序列化方式
/*redisTemplate.setKeySerializer(new StringRedisSerializer());
//redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));*/
//redis中key和value都成乱码了
//key和value都没有指定序列化方式,默认采用jdk的序列化方式
forValue.set("k3","张三");
//value默认采用jdk,类必须实现序列化接口 指定序列化后就不用实现接口了
forValue.set("k2",new User(1,"李四",20));
}
}
2.3写一个配置类,为RedisTemplate指定好序列化
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
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);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化 filed value
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(redisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
}
3.redis的使用场景
数据储存在redis内存中,查询速度会有很大的提高,会少量的向数据发送请求,这样给数据库分摊了很多的压力。
3.2redis作为缓存,没在内存中查到数据,去查询数据库,再存到redis中
@Service
public class DeptService {
@Autowired
private DeptMapper deptMapper;
@Autowired
private RedisTemplate redisTemplate;
//使用查询注解:cacheNames表示缓存的名称,key:唯一标识---dept::key
//先从缓存中查看key为(cacheNames::key)是否存在,如果存在不会执行方法体,如果不存在则执行方法体并把方法的返回值存入缓存中
@Cacheable(cacheNames = {"dept"},key = "#id")
public Dept findById(Integer id){
/*ValueOperations forValue = redisTemplate.opsForValue();
//查询缓存
Object o = forValue.get("dept::" + id);
//缓存命中
if (o!=null){
return (Dept) o;
}*/
Dept dept = deptMapper.selectById(id);
/*//存入缓存
if (dept!=null){
forValue.set("dept::"+id,dept,2, TimeUnit.HOURS);
}*/
return dept;
}
@CacheEvict(cacheNames = {"dept"},key = "#id")
public int delete(Integer id){
redisTemplate.delete("dept::"+id);
int i = deptMapper.deleteById(id);
return i;
}
public Dept insert(Dept dept){
int insert = deptMapper.insert(dept);
return dept;
}
//这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新
@CachePut(cacheNames = {"dept"},key = "#dept.id")
public Dept update(Dept dept){
/*ValueOperations forValue = redisTemplate.opsForValue();
forValue.set("dept::"+dept.getId(),dept,2, TimeUnit.HOURS);*/
int i = deptMapper.updateById(dept);
return dept;
}
}
查看的缓存: 前部分代码相同@before通知,后部分代码也相同后置通知,使用注解 。
@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;
}
在启动类上使用开启缓存注解
使用注解
4.redis的解决分布式锁的bug
可以使用:redission依赖,redission解决redis超时问题的原理。
为持有锁的线程开启一个守护线程,守护线程会每隔10秒检查当前线程是否还持有锁,如果持有则延迟生存时间。
引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.4</vers
</dependency>
//获取redisson对象并交于spring容器管理
@Bean
public Redisson redisson(){
Config config =new Config();
config.useSingleServer().
setAddress("redis://localhost:6379").
//redis默认有16个数据库
setDatabase(0);
return (Redisson) Redisson.create(config);
}
@Override
public String decreaseStock(Integer productId) {
//获取锁对象
RLock lock = redisson.getLock("aaa::" + productId);
try {
lock.lock(30, TimeUnit.SECONDS);
//查看该商品的库存数量
Integer stock = productStockDao.findStockByProductId(productId);
if (stock > 0) {
//修改库存每次-1
productStockDao.updateStockByProductId(productId);
System.out.println("扣减成功!剩余库存数:" + (stock - 1));
return "success";
} else {
System.out.println("扣减失败!库存不足!");
return "fail";
}
} finally {
lock.unlock();
}