集成Redis
Jedis :jedis就是集成了redis的一些命令操作,封装了redis的java客户端。提供了连接池管理。一般不直接使用jedis,而是在其上在封装一层,作为业务的使用。
- 添加Jedis依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.3</version>
</dependency>
- 添加Fastjson依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.38</version>
</dependency>
- 添加redis的配置项
#redis 配置服务器等信息
redis.host=127.0.0.1
redis.port=6379
redis.timeout=10
//redis.password=123456
redis.poolMaxTotal=1000
redis.poolMaxldle=500
redis.poolMaxWait=500
- 新建一个包redis,在里面新建一个RedisConfig类。
@Component
@ConfigurationProperties(prefix="redis")//将application.properties里面前缀redis都读取
@lombok
public class RedisConfig {
private String host;
private int port;
private int timeout;
private String password;
private int poolMaxTotal;
private int poolMaxldle;
private int poolMaxWait;
}
@ConfigurationProperties(prefix=“redis”)指定配置文件里面前缀为"redis"的配置项,与配置项里面的属性对应起来。
- 再在redis包中创建RedisPoolFactory类,RedisPoolFactory 通过配置文件,生成Jedis连接池(配置),方便在RedisService中调用。
@Service
public class RedisPoolFactory {
@Autowired
RedisConfig redisConfig;
//JedisPool的实例注入到spring容器里面
@Bean
public JedisPool JedisPoolFactory() {
JedisPoolConfig poolConfig=new JedisPoolConfig();
System.out.println("redisConfig.getPoolMaxldle():"+redisConfig.getPoolMaxldle());
System.out.println("redisConfig.getPoolMaxTotal():"+redisConfig.getPoolMaxTotal());
System.out.println("redisConfig.getPoolMaxWait():"+redisConfig.getPoolMaxWait());
System.out.println("redisConfig.getPassword():"+redisConfig.getPassword());
poolConfig.setMaxIdle(redisConfig.getPoolMaxldle());
poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal());
poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait()*1000);//s-->ms
//因为我们使用的是s(秒)来配置的,而源码使用的是ms(毫秒),所以转换一下
JedisPool jp=new JedisPool(poolConfig,redisConfig.getHost(),redisConfig.getPort(),
redisConfig.getTimeout()*1000,redisConfig.getPassword(),0);
return jp;
}
}
注意:RedisPoolFactory 通过配置文件,生成Jedis连接池(配置),方便在RedisService中调用。
- 再在redis包中创建RedisService类来提供所有关于redis的服务方法。
注意:该类中封装了常用的Redis 方法操作。
- public T get(KeyPrefix prefix,String key,Class data) 根据key取得缓存中值(根据传入的前缀)
- public boolean delete(KeyPrefix prefix,String key) 删除key
- public boolean set(KeyPrefix prefix,String key,T value) 根据key设置缓存中值
- public Long decr(KeyPrefix prefix,String key) 自减
- public Long incr(KeyPrefix prefix,String key) 自增
- public boolean exitsKey(KeyPrefix prefix,String key) 是否存在key
- public static T stringToBean(String s,Class clazz)
- public static String beanToString(T value)
但是其中,存入redis 的是String类型,这个时候我们的数据不一定是String类型,所以需要类型转换,将数据转换成String格式存入。
BeanToString()这个方法,就是来转化,先获取传入数据的Class类型,根据类型判断,int,long,String 类型,通过API转换直接转换成String即可,或是其他的自定义对象,则利用fastjson库将我们项目中定义的JavaBean 对象,转化为json字符串。StringToBean() 相当于上个是反方法。都需要传入一个类型,这样才知道将字符串转换为什么对象。
通用缓存Key的设计与封装
为什么要这个通用缓存Key?
当项目中的模块越来越多的时候,需要存的缓存也越来越多,比如商品Id,订单Id,用户id等,此时若是id出现重复,将给系统带来错误。那么使用KeyPrefix来更好的操作和管理缓存中对应的key。给不同模块的key带有一个前缀。
不加前缀的情况:
public <T> boolean set(String key,T value){
Jedis jedis=null;
//在JedisPool里面取得Jedis
try {
jedis=jedisPool.getResource();
//将T类型转换为String类型
String s=beanToString(value);
if(s==null) {
return false;
}
jedis.set(key, s);
return true;
}finally {
returnToPool(jedis);
}
}
解决覆盖key的办法:将前缀+一个key一起作为一个redis里面真正的key,这样不同模块之间就不会重复。
好处:不同功能或者不同模块的前缀不同,即使有同名key出现,那么前缀不同,并不会引起key冲突被其他功能覆盖的情况。
使用一个模板模式来封装:接口<---抽象类<---实现类
接口:
/**
*做缓存的前缀接口
*/
public interface KeyPrefix {
//有效期
public int expireSeconds();
//前缀
public String getPrefix();
}
BasePrefix 抽象类:简单的实现一下KeyPrefix(定义成抽象类原因:防止不小心被创建)我们不希望BasePrefix被实例化。我们只希望它被继承。不同模块的前缀类都继承他。
//定义成抽象类
public abstract class BasePrefix implements KeyPrefix{
private int expireSeconds;
private String prefix;
public BasePrefix() {
}
public BasePrefix(String prefix) {
//this(0, prefix);//默认使用0,不会过期
this.expireSeconds=0;
this.prefix=prefix;
}
public BasePrefix(int expireSeconds,String prefix) {//覆盖了默认的构造函数
this.expireSeconds=expireSeconds;
this.prefix=prefix;
}
//默认为0代表永不过期
public int expireSeconds() {
return expireSeconds;
}
//前缀为类名:+prefix
public String getPrefix() {
String className=getClass().getSimpleName();
return className+":"+prefix;
}
}
注意:该类2种不同构造方法:用于继承。一个只带前缀名,一个带前缀名和过期时间。当实现public BasePrefix(String prefix)的时候,我们将默认这个key不会失效,因为有一些场景,我们不希望key失效,但是有些场景我们需要设置key的合适的有效期。
具体实现类:用户UserKey只去继承了super(prefix),即public BasePrefix(String prefix),那么代表user的key的过期时间为不会过期。
public class UserKey extends BasePrefix{
public UserKey(String prefix) {
super(prefix);
}
public static UserKey getById=new UserKey("id");
public static UserKey getByName=new UserKey("name");
}
秒杀用户的MiaoshaUserKey ,继承了super(expireSeconds,prefix),可以设置有效期时间为2天。
public class MiaoshaUserKey extends BasePrefix{
public static final int TOKEN_EXPIRE=3600*24*2;//3600S*24*2 =2天
public MiaoshaUserKey(int expireSeconds,String prefix) {
super(expireSeconds,prefix);
}
public static MiaoshaUserKey token=new MiaoshaUserKey(TOKEN_EXPIRE,"tk");
//对象缓存一般没有有效期,永久有效
public static MiaoshaUserKey getById=new MiaoshaUserKey(0,"id");
}
具体实现类的具体使用场景:
/**
*避免key被不同类的数据覆盖
*使用Prefix前缀-->不同类别的缓存,用户、部门、
*/
@RequestMapping("/redis/set")
@ResponseBody
public Result<Boolean> redisSet() {//0代表成功
User user=new User(1,"1111");
boolean f=redisService.set(UserKey.getById,""+1,user);
return Result.success(true);
}
@RequestMapping("/redis/getbyid")
@ResponseBody
public Result<User> redisGetById() {//0代表成功
User res=redisService.get(UserKey.getById,""+1,User.class);
//redisService.get("key1",String.class);
//System.out.println("res:"+userService.tx());
return Result.success(res);
}
完善后的get和set缓存的Key值的方法:
public <T> T get(KeyPrefix prefix,String key,Class<T> data){
Jedis jedis=null;
//在JedisPool里面取得Jedis
try {
jedis=jedisPool.getResource();
//生成真正的key className+":"+prefix; BasePrefix:id1
String realKey=prefix.getPrefix()+key;
//System.out.println("jedis:"+jedis);
String sval=jedis.get(realKey);
//将String转换为Bean入后传出
T t=stringToBean(sval,data);
return t;
}finally {
returnToPool(jedis);
}
}
/**
* 设置单个、多个对象
*/
//MiaoshaUserKey.token, token, user
public <T> boolean set(KeyPrefix prefix,String key,T value){
Jedis jedis=null;
try {//在JedisPool里面取得Jedis
jedis=jedisPool.getResource();
String realKey=prefix.getPrefix()+key;
String s=beanToString(value);
if(s==null||s.length()<=0) {
return false;
}
int seconds=prefix.expireSeconds();
if(seconds<=0) {//有效期:代表不过期,这样才去设置
jedis.set(realKey, s);
}else {//没有设置过期时间,即没有设置有效期,那么自己设置。
jedis.setex(realKey, seconds,s);
}
return true;
}finally {
returnToPool(jedis);
}
}
getPrefix(通过反射再getsimpleclass得到前缀)然后再加在key的前面。当然前缀里也可以设置expire时间
关于token: