spring與redis整合
目錄
一、jedis對5種java資料型別的儲存方式
一個快取資訊包含三個,name快取名稱,key快取的key值,value快取的value值。jedis常用操作如下:
public class JedisTest {
private static final Jedis jedis = new Jedis("127.0.0.1", 6379);
@Test
public void test_string() {
jedis.set("name", "zhangsan");
}
@Test
public void test_hash() {
//存放的類似map型別的
jedis.hset("url", "google", "www.google.com");
jedis.hset("url", "taobao", "www.taobao.com");
jedis.hset("url", "baidu", "www.baidu.com");
Map<String, String> userInfo = new HashMap<>();
userInfo.put("name", "zhangsan");
userInfo.put("age", "35");
userInfo.put("sex", "man");
jedis.hmset("userInfo", userInfo);
String name = jedis.hget("userInfo", "name");
//取多個返回值
List<String> urlList = jedis.hmget("url", "google", "taobao", "baidu");
//取hash所有key值
Map<String, String> userInfoMap = jedis.hgetAll("userInfo");
}
@Test
public void test_list() {
jedis.lpush("charList", "abc");//lpush,在list首部新增元素
jedis.rpush("charList", "def");//rpush,在listw尾部新增元素
//擷取list
List<String> charList = jedis.lrange("charList", 0, 1);
jedis.lpop("charList");//在list首部刪除元素
jedis.rpop("charList");//在list尾部刪除元素
}
@Test
public void test_set() {
jedis.sadd("setMem", "s1");
jedis.sadd("setMem", "s2");
Set<String> sets = jedis.smembers("setMem");
}
@Test
public void test_sort_set() {
jedis.zadd("sortSetMem", 1, "s1");
jedis.zadd("sortSetMem", 2, "s1");
Set<String> sets = jedis.zrange("sortSetMem", 0, 1);
Set<String> revesortSet = jedis.zrevrange("sortSetMem", 0, 1);//反向取
}
}
二、關於redis的一點介紹
具體見redis簡介
三、相關程式碼
1、CacheDefinition
在@Cacheable中沒有屬性描述快取有效時間,以及當有個多個相關快取(比如產品的快取)的關聯性屬性,所以提供兩個屬性,duration和tag。tag屬性的作用是將多個快取關聯,可以一起刪除這些快取(利用redis的hash資料型別)。
public class CacheDefinition {
private String name;//快取名稱
private int duration;//快取有效時間
private String tag;//快取標籤
public String getName() {
return name;
}
public CacheDefinition setName(String name) {
this.name = name;
return this;
}
public int getDuration() {
return duration;
}
public CacheDefinition setDuration(int duration) {
this.duration = duration;
return this;
}
public String getTag() {
return tag;
}
public CacheDefinition setTag(String tag) {
this.tag = tag;
return this;
}
}
2、CacheMapping
定義該註解,用於表示快取時間和標籤。
/**
* 定義一個快取,支援類級別和方法級別,如果同時存在,類級別覆蓋方法級別配置
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface CacheMapping {
// 快取時間(秒)
public int duration() default 60;
// 快取標籤,用於清理快取
public String tag() default "";
}
3、Storage
定義該介面,提供一些常用的快取方法。
public interface Storage {
Object get(Object key);
void put(Object key, Object value, int seconds);//seconds,過期時間,單位秒
void evict(Object key);
void replace(Object key, Object value, int seconds);
void destroy();
boolean available();
}
4、RedisStorage
定義了關於Cache的一些資訊,比如連線資訊(redisStorage)、快取名稱、tag、有效時間等資訊;
該類實現了上面的Storage介面,提供了基本的快取增刪改查操作。這裡注意可以key、value等值都進行序列化了,方便儲存和傳輸。然後對於redis的hash型別,採用hget/hset方法,第一個引數為快取名稱,第二個引數為快取key;
另外Jedis只提供了儲存string型別的方法,所以我們可以使用可以儲存位元組陣列的BinaryJedisCommands方法。
public class RedisStorage<ResourceType extends BinaryJedisCommands> implements Storage {
//序列化預設為jdk序列化,也可以選擇為hessian2/kryo
private Serializer<Object> serializer = new DefaultSerializer();
private Deserializer<Object> deserializer = new DefaultDeserializer();
private Logger log = Logger.getLogger(RedisStorage.class);
private final Pool<ResourceType> pool;
private final List<HostAndPort> hostAndPorts;
public RedisStorage(Pool<ResourceType> pool, List<HostAndPort> hostAndPorts) {
this.pool = checkNotNull(pool);
this.hostAndPorts = checkNotNull(hostAndPorts);
}
//下面方法為Storage介面的方法實現
@Override
public Object get(Object key) {
ResourceType resource = getResource();
byte[] bytes = resource.get(key.toString().getBytes());
if (bytes == null || bytes.length == 0) {
return null;
}
DeserializingConverter dc = new DeserializingConverter(deserializer);
return dc.convert(bytes);//反序列化成物件
}
@Override
public void put(Object key, Object value, int seconds) {
ResourceType resource = getResource();
SerializingConverter sc = new SerializingConverter(serializer);
byte[] bytes = sc.convert(value);
resource.setex(key.toString().getBytes(), seconds, bytes);
}
@Override
public void evict(Object key) {
ResourceType resource = getResource();
if (resource.exists(key.toString().getBytes())) {
resource.del(key.toString().getBytes());
}
}
@Override
public void replace(Object key, Object value, int seconds) {
put(key, value, seconds);
}
@Override
public void destroy() {
if (!pool.isClosed()) {
pool.close();
}
}
//返回增加的條數(如果key存在update,update返回0)
public Long hset(final Object key, final Object field, final Object value, int seconds) {
ResourceType resource = getResource();
SerializingConverter sc = new SerializingConverter(serializer);
byte[] bytes = sc.convert(value);
long result = resource.hset(key.toString().getBytes(), field.toString().getBytes(), bytes);
resource.expire(key.toString().getBytes(), seconds);
return result;
}
public Set<Object> hkeys(String tag) {
ResourceType resource = getResource();
Set<byte[]> bytes = resource.hkeys(tag.getBytes());
Set<Object> stringSet = new HashSet<>();
for (byte[] bytes1 : bytes) {
stringSet.add(new String(bytes1));
}
return stringSet;
}
public Object hget(Object tag, Object key) {
ResourceType resource = getResource();
byte[] bytes = resource.hget(tag.toString().getBytes(), key.toString().getBytes());
if (bytes == null || bytes.length == 0) {
return null;
}
DeserializingConverter dc = new DeserializingConverter(deserializer);
return dc.convert(bytes);//反序列化成物件
}
//標籤類快取,利用hash資料型別(類似map),可以儲存某一類的快取
//hash型別: key(field,value)
public void putCacheToTag(Object tag, Object key, Object value, int duration) {
if (StringUtils.isEmpty(tag)) {
return;
}
Long resultCode = this.hset(tag, key, value, duration);
if (null == resultCode) {
throw new IllegalArgumentException("Fail to put cache to tag");
}
}
//根據標籤刪除相關快取
//如果可以通過key刪除,該方法可以不要
public void evictTag(String tag) {
if (StringUtils.isEmpty(tag)) {
return;
}
this.evict(tag);
Set<Object> keys = this.hkeys(tag);
log.info("Removed cache tag " + tag);
for (Object k : keys) {
log.info("Removed cache,tag is " + tag + ",key is " + k.toString());
}
}
//檢測是否可用
@Override
public boolean available() {
ResourceType resource = getResource();
byte[] bytes = resource.echo("test".getBytes());
if (bytes == null || bytes.length == 0) {
return false;
}
return Arrays.equals(bytes, "test".getBytes());
}
private static <T> T checkNotNull(T reference) {
if (reference == null) {
throw new NullPointerException();
}
return reference;
}
public Pool<ResourceType> getPool() {
return pool;
}
private ResourceType getResource() {
return pool.getResource();
}
}
5、RedisStorageFactory
建立redisStoage,redis叢集資訊可以使用類JedisShardInfo。
/**
* 構建jedis連線
* 單個連線 JedisPool
* 叢集連線 ShardedJedisPool
*/
public class RedisStorageFactory {
public RedisStorage<? extends BinaryJedisCommands> newStorage(JedisPoolConfig poolConfig, List<JedisShardInfo> shardInfos) {
List<HostAndPort> hostAndPortList = getHostAndPortByShardInfos(shardInfos);
if (shardInfos.size() == 1) {
return new RedisStorage<>(new JedisPool(poolConfig, shardInfos.get(0).getHost(), shardInfos.get(0).getPort(), shardInfos.get(0).getSoTimeout(), shardInfos.get(0).getPassword()), hostAndPortList);
}
ShardedJedisPool shardedJedisPool = new ShardedJedisPool(poolConfig, shardInfos);
return new RedisStorage<>(shardedJedisPool, hostAndPortList);
}
private List<HostAndPort> getHostAndPortByShardInfos(List<JedisShardInfo> shardInfos) {
List<HostAndPort> hostAndPorts = new ArrayList<>();
for (JedisShardInfo shardInfo : shardInfos) {
hostAndPorts.add(new HostAndPort(shardInfo.getHost(), shardInfo.getPort()));
}
return hostAndPorts;
}
}
6、RedisCache
spring Cache介面的實現類,裡面的實現方法可以呼叫storage的方法。
public class RedisCache<ResourceType extends BinaryJedisCommands> implements Cache {
private String name;
private String tag;
private int duration;
private TimeUnit timeUnit;
private RedisStorage<ResourceType> redisStorage;
public RedisCache() {
}
public RedisCache(RedisStorage<ResourceType> redisStorage, String name, int duration, TimeUnit timeUnit) {
this.redisStorage = redisStorage;
this.name = name;
this.duration = duration;
this.timeUnit = timeUnit;
}
public RedisCache(RedisStorage<ResourceType> redisStorage, String name, String tag, int duration, TimeUnit timeUnit) {
this.redisStorage = redisStorage;
this.name = name;
this.duration = duration;
this.timeUnit = timeUnit;
this.tag = tag;
}
@Override
public String getName() {
return name;
}
@Override
public Object getNativeCache() {
return redisStorage.getPool();
}
@Override
public ValueWrapper get(Object key) {
Object actObject = null;
if (StringUtils.isEmpty(tag)) {
actObject = redisStorage.get(key);
} else {
actObject = redisStorage.hget(tag, key);
}
return actObject == null ? null : new SimpleValueWrapper(actObject);
}
@Override
public <T> T get(Object key, Class<T> aClass) {
ValueWrapper valueWrapper = this.get(key);
if (valueWrapper == null) {
return null;
}
return aClass.cast(valueWrapper.get());
}
@Override
public void put(Object key, Object value) {
if (StringUtils.isEmpty(tag)) {
redisStorage.put(key, value, (int) timeUnit.toSeconds(duration));
return;
}
redisStorage.putCacheToTag(tag, key, value, duration);
}
@Override
public void evict(Object key) {
redisStorage.evict(key);//如果該方法不行,試試evictTag,key為tag
}
@Override
public void clear() {
redisStorage.destroy();
}
}
7、RedisServersManager
該類主要是對redis的服務進行管理。
public class RedisServersManager {
private Logger log = Logger.getLogger(RedisServersManager.class);
//jedis連線分片資訊
private List<JedisShardInfo> shards = new ArrayList<JedisShardInfo>();
protected volatile RedisStorage<? extends BinaryJedisCommands> redisStorage;
protected final static Object REDIS_STORAGE_LOCK = new Object();
protected RedisStorageFactory factory = new RedisStorageFactory();
protected JedisPoolConfig config = new JedisPoolConfig();
/**
* 根據servers生成jedis分片連線池
*/
public void setServers(String servers) {
String[] serversArrays = servers.trim().split(",");
for (String server : serversArrays) {
JedisShardInfo jedisShardInfo = shardInfo(server);
this.shards.add(jedisShardInfo);
log.info("Add a node to redis shard info,node info:" + jedisShardInfo);
}
}
private JedisShardInfo shardInfo(String server) {
String[] texts = server.split("\\^");
JedisShardInfo shard = new JedisShardInfo(texts[0], Integer.parseInt(texts[1]), 1000);
if (texts.length == 3) {
shard.setPassword(texts[2]);
}
return shard;
}
public RedisStorage<? extends BinaryJedisCommands> newStorage() {
if (null == redisStorage) {
synchronized (REDIS_STORAGE_LOCK) {
if (null == redisStorage) {
redisStorage = factory.newStorage(config, shards);
log.info("Successfully created a redis storage.");
}
}
}
return redisStorage;
}
}
8、SpringDataCacheManager
實現CacheManager介面,對cache進行管理。通過ApplicationContextAware介面獲取到applicationContext,通過InitializingBean介面在初始化該類的時候把所有有快取註解資訊的放入到集合中。
public class SpringDataCacheManager extends RedisServersManager implements ApplicationContextAware, InitializingBean, CacheManager {
private ApplicationContext applicationContext;
private final Map<String, CacheDefinition> caches = new ConcurrentReferenceHashMap<>();
private int defaultDuration = 60;//預設快取60秒
private String servers = "127.0.0.1^7100^password,127.0.0.1^6379^password";
//InitializingBean的實現方法
@Override
public void afterPropertiesSet() throws Exception {
setServers(servers);
parseFromContext(applicationContext);
}
private void parseFromContext(final ApplicationContext context) {
String[] beanNames = context.getBeanNamesForType(Object.class);
for (final String beanName : beanNames) {
final Class<?> classType = context.getType(beanName);
//查詢該類是否有service和repository註解
Service service = findAnnotation(classType, Service.class);
Repository repository = findAnnotation(classType, Repository.class);
if (service == null && repository == null) {
continue;
}
ReflectionUtils.doWithMethods(classType, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) {
ReflectionUtils.makeAccessible(method);
CacheMapping cacheMapping = findCacheMapping(method, classType);//先找類級別,沒有再找當前方法級別
Cacheable cacheable = findAnnotation(method, Cacheable.class);
String[] cacheNames = getCacheNamesWithCacheable(cacheable);
for (String cacheName : cacheNames) {
CacheDefinition cacheDefinition = new CacheDefinition()
.setName(cacheName)
.setDuration(cacheMapping.duration())
.setTag(cacheMapping.tag());
caches.put(cacheName, cacheDefinition);
}
}
}, new ReflectionUtils.MethodFilter() {
public boolean matches(Method method) {
return !method.isSynthetic() && findAnnotation(method, Cacheable.class) != null;
}
});
if (context.getParent() != null) {
parseFromContext(context.getParent());
}
}
}
private CacheMapping findCacheMapping(Method method, Class<?> handlerType) {
CacheMapping cacheMapping = findAnnotation(handlerType, CacheMapping.class);
if (cacheMapping == null) {//如果類註解沒有,再從方法註解查詢
cacheMapping = findAnnotation(method, CacheMapping.class);
}
if (cacheMapping == null) {
throw new IllegalStateException("No cache mapping (@CacheMapping) could be detected on '" +
method.toString() + "'. Make sure to set the value parameter on the annotation or " +
"declare a @CacheMapping at the class-level with the default cache cacheMapping to use.");
}
return cacheMapping;
}
private String[] getCacheNamesWithCacheable(Cacheable cacheable) {
return cacheable.value();
}
//CacheManager的實現方法,該方法每次呼叫cache的get方法之前會呼叫一次
@Override
public Cache getCache(String name) {
CacheDefinition cacheDefinition = caches.get(name);
if (null != cacheDefinition) {
return new RedisCache<>(newStorage(), name, cacheDefinition.getTag(), cacheDefinition.getDuration(), TimeUnit.SECONDS);
}
return new RedisCache<>(newStorage(), name, defaultDuration, TimeUnit.SECONDS);
}
@Override
public Collection<String> getCacheNames() {
return caches.keySet();
}
//ApplicationContextAware
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
9、Service類
@Service
public class PersonService2 {
@Cacheable(value = "getPersonByName", key = "'PersonService.getPersonByName'+#name")
@CacheMapping(duration = 20, tag = "PERSON")
public Person getPersonByName(String name) {
// 方法內部實現不考慮快取邏輯,直接實現業務
System.out.println("呼叫了Service方法");
return getFromDB(name);
}
private Person getFromDB(String name) {
System.out.println("從資料庫查詢了");
return new Person();
}
}
10、測試類
public class PersonCacheTest {
private PersonService2 personService2;
@Before
public void setUp() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans/application_redis_cache.xml");
personService2 = context.getBean("personService2", PersonService2.class);
}
@Test
public void testGetPersonByName() {
System.out.println("第一次查詢………………");
personService2.getPersonByName("張三");
System.out.println("第二次查詢………………");
personService2.getPersonByName("李四");
System.out.println("第三次查詢………………");
personService2.getPersonByName("張三");
}
}
輸出
第一次查詢………………
呼叫了Service方法
從資料庫查詢了
第二次查詢………………
呼叫了Service方法
從資料庫查詢了
第三次查詢………………
第三次沒有從資料庫查詢了。
四、總結
1、BinaryJedisCommands介面可以儲存位元組陣列,儲存的key/value建議先轉化成位元組陣列然後再序列化;
2、單服務jedis使用到連線池介面有JedisPoolConfig/JedisPool,叢集的對應有JedisPoolConfig/JedisShardInfo/ShardedJedisPool;
3、實現spring Cache需要實現Cache介面和CacheManage介面(cache介面提供增刪改查,cacheManage介面對cache進行管理)。
相關文章
- spring+redis的整合,使用spring-data-redis來整合SpringRedis
- Spring Boot整合RedisSpring BootRedis
- Spring Boot 整合redisSpring BootRedis
- Spring-Boot整合RedisSpringbootRedis
- spring boot(三)整合 redisSpring BootRedis
- Spring Boot 專案整合RedisSpring BootRedis
- Spring MVC整合redis的配置SpringMVCRedis
- Spring Boot 如何快速整合 Redis 哨兵?Spring BootRedis
- Spring Boot系列筆記--整合RedisSpring Boot筆記Redis
- Spring Boot整合Redis實戰操作Spring BootRedis
- spring:spring與mybatis的整合SpringMyBatis
- Spring與ActiveMQ整合SpringMQ
- Mybatis與Spring整合MyBatisSpring
- Spring Boot整合Redis叢集(Cluster模式)Spring BootRedis模式
- 一文讀懂Spring整合RedisSpringRedis
- Spring security(四)-spring boot +spring security簡訊認證+redis整合Spring BootRedis
- ElasticSearch與Spring Boot整合ElasticsearchSpring Boot
- Spring AI與大模型Ollama如何整合整合?SpringAI大模型
- [分散式][Redis]Redis分散式框架搭建與整合分散式Redis框架
- Spring與Web環境整合SpringWeb
- Spring Cache與Ehcache 3整合Spring
- Struts2【與Spring整合】Spring
- Solr與Spring Boot整合 - ViithiisysSolrSpring Boot
- Spring Cache + Caffeine的整合與使用Spring
- Spring同時整合JPA與MybatisSpringMyBatis
- mybatis與spring整合ssm01MyBatisSpringSSM
- Shiro(環境搭建與Spring整合)Spring
- Spring Boot(十三):整合Redis哨兵,叢集模式實踐Spring BootRedis模式
- 手寫一個Redis和Spring整合的外掛RedisSpring
- spring-boot-route(十二)整合redis做為快取SpringbootRedis快取
- spring boot使用Jedis整合Redis實現快取(AOP)Spring BootRedis快取
- Spring Boot 3中將JWT與Spring Security 6整合Spring BootJWT
- Spring Boot 與 R2DBC 整合Spring Boot
- Spring 對Apache Kafka的支援與整合SpringApacheKafka
- Spring Security (三):與Vue.js整合SpringVue.js
- Spring Boot(十三)RabbitMQ安裝與整合Spring BootMQ
- Mybatis與Spring整合時都做了什麼?MyBatisSpring
- RabbitMQ(三):RabbitMQ與Spring Boot簡單整合MQSpring Boot