1.Springboot操作redis
1.1首先创建user实体类
package com.hmq;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private Integer age;
}
接口对应自动序列化
1.2使用RedisTemplate时需要为key和value设置序列化
package com.hmq.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean //把该方法返回的类对象交于spring的IOC容器来管理
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); //
return template;
}
}
1.3测试
package com.hmq;
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.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest
class HmqRedisApplicationTests02 {
//springboot创建好该类对象 并交于IOC容器管理
@Autowired
private RedisTemplate redisTemplate;//在使用时需要指定序列化方式,如果没有指定,则采用JDK序列化方式,而jdk序列化方式要求类对象必须实现序列化接口
@Test
public void test02(){
ValueOperations valueOperations = redisTemplate.opsForValue();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());//手动序列化,不需要给user实体类添加接口如果这里指定序列化方式,对象类可以不用实现序列化接口
valueOperations.set("k1","v1");//存储都是字符串类型,看到存放的数据乱码,对yey进行序列化采用的是默认的jdk序列化方式
System.out.println(valueOperations.get("k1"));
valueOperations.set("k3",new User("张三丰",108));
Object user = valueOperations.get("k3");
System.out.println(user);
}
@Test
public void test03(){
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("k11","v11");
valueOperations.set("k12",new User("刘文强",24));
}
}
2.springboot能否使用redis集群模式
#redis的配置信息--单机
#spring.redis.host=192.168.245.223
#spring.redis.port=6379
# nginx代理redis集群
spring.redis.cluster.nodes=192.168.245.223:7001,192.168.245.223:7002,192.168.245.223:7003,192.168.245.223:7004,192.168.245.223:7005,192.168.245.223:7006
3.使用redis作为缓存
缓存:---存在内存中。
作用: ---提高查询性能,减少数据库访问频率。
什么样的数据适合放入缓存: ---安全系数低的。---访问频率高 ---修改频率低 。
原理:
3.1.创建springboot项目,连接数据库
3.2.导入pom依赖
<!--导入阿里云依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--数据库依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
3.3.配置文件
pring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///qy165?serverTimezone=Asia/Shanghai
spring.redis.host=192.168.245.223
spring.redis.port=6379
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
3.4创建dept实体类
package com.ehmq.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@TableName("tb_dept")
@AllArgsConstructor
@NoArgsConstructor
public class Dept {
private Integer id;
private String name;
private String loc;
}
3.5创建dao接口的接口类deptdao
package com.ehmq.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ehmq.entity.Dept;
public interface DeptDao extends BaseMapper<Dept> {
}
3.6创建service层
package com.ehmq.service;
import com.ehmq.dao.DeptDao;
import com.ehmq.entity.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DeptService {
@Autowired
private DeptDao deptDao;
public Dept findById(Integer id){
Dept dept = deptDao.selectById(id);
return dept;
}
}
3.7给RedisSpeingbootCacheApp类添加路径
package com.ehmq;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.ehmq.dao")
public class RedisSpringbootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(RedisSpringbootCacheApplication.class, args);
}
}
3.8测试
package com.ehmq;
import com.ehmq.entity.Dept;
import com.ehmq.service.DeptService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RedisSpringbootCacheApplicationTests {
@Autowired
private DeptService deptService;
@Test
void contextLoads() {
Dept dept = deptService.findById(2);
System.out.println(dept);
}
}
实现缓存:
package com.ehmq.service;
import com.ehmq.dao.DeptDao;
import com.ehmq.entity.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Service
public class DeptService {
@Autowired
private DeptDao deptDao;
@Autowired
private RedisTemplate redisTemplate;
public Dept findById(Integer id){
ValueOperations valueOperations = redisTemplate.opsForValue();
//1.查询缓存是否存在该数据
Object o = valueOperations.get("dept:" + id);
if(o!=null && o instanceof Dept){
return (Dept) o;
}
//2.查询数据库
Dept dept = deptDao.selectById(id);
if(dept!=null) {
//把该数据放入缓冲中
valueOperations.set("dept:" + id, dept, 24, TimeUnit.HOURS);
}
return dept;
}
public Dept update(Dept dept){
ValueOperations valueOperations = redisTemplate.opsForValue();
//1.先该数据库还是先删缓冲。
deptDao.updateById(dept);
valueOperations.set("dept:"+dept.getId(),dept);
return dept;
}
public Dept insert(Dept dept){
deptDao.insert(dept);//添加
return dept;
}
public int delete(int id){
redisTemplate.delete("dept:"+id);
deptDao.deleteById(id);
return 1;
}
}
AOP---可以把一些非核心业务代码抽象----抽取为一个切面类@Aspect. 在结合一些注解完成相应的切面功能。 Spring框也能想到,Spring在3.0以后提高了缓存注解。可以帮助把功能抽取。
1)配置RedisConfig缓存配置
@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;
}
2)开启缓存注解
3)使用缓存注解
package com.ehmq.service;
import com.ehmq.dao.DeptDao;
import com.ehmq.entity.Dept;
import org.springframework.beans.factory.annotation.Autowired;
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 DeptServiceCache {
@Autowired
private DeptDao deptDao;
//使用于查询的缓存注解: 缓存的名称叫做: cacheNames::key
//先从缓存中找名字叫:cacheNames::key 如果存在,则方法不执行。如果不存在会执行方法,并把改方法的返回值作为缓存的值.
//必须开启缓存注解驱动
@Cacheable(cacheNames = "dept",key = "#id")
public Dept findById(Integer id){
Dept dept = deptDao.selectById(id);
return dept;
}
//先执行方法体,并把方法的返回结果作为缓存的值。修改缓存的值。
@CachePut(cacheNames = "dept",key="#dept.id")
public Dept update(Dept dept){
//1.先该数据库还是先删缓冲。
deptDao.updateById(dept);
return dept;
}
//
// public Dept insert(Dept dept){
// deptDao.insert(dept);
// return dept;
// }
//
//删除缓存再执行方法体
@CacheEvict(cacheNames = "dept",key = "#id")
public int delete(int id){
deptDao.deleteById(id);
return 1;
}
}
4.redis解决分布式锁
锁:保证程序的原子性操作
上面由于线程安全问题:出现了重卖以及超卖现象。
如何解决上面的问题:加锁.
synchronized或lock锁---本地JVM。
如果我们的项目是一个集群模式
上面的情况也会出现线程安全问题。
配置nginx
启动nginx
使用jemter压测时又出现了上面的现象
如何解决:---使用分布式锁。
如何实现分布式---redis可以实现或zookeeper实现
package com.ykq.distrinctlock.service.impl;
import com.ykq.distrinctlock.dao.ProductStockDao;
import com.ykq.distrinctlock.service.ProductStockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class ProductStockServiceImpl implements ProductStockService {
@Autowired
private ProductStockDao productStockDao;
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public String decreaseStock(Integer productId) {
ValueOperations<String, String> forValue = redisTemplate.opsForValue();
Boolean flag = forValue.setIfAbsent("product:" + productId, "霍梦齐", 30, TimeUnit.SECONDS);
if(flag) {//表示获取分布锁成功
try {
//查看该商品的库存数量
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 {
redisTemplate.delete("product:"+productId);//释放锁资源
}
}else{
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return decreaseStock(productId);
}
}
}
缺陷: 当程序执行时间超过锁的时间。
解决: 使用watch dog机制。----第三方框架完成redisson