“Spring-data-redis 的使用实践”的版本间差异
跳到导航
跳到搜索
(未显示同一用户的19个中间版本) | |||
第5行: | 第5行: | ||
== CRUD 中应用缓存的场景 == | |||
在普通 CRUD 应用场景中,很多情况下需要同步操作缓存,推荐使用 Spring 的 spring-data-redis 开源库。 | 在普通 CRUD 应用场景中,很多情况下需要同步操作缓存,推荐使用 Spring 的 '''spring-data-redis''' 开源库。 | ||
* 注:CRUD 是指 Create(创建),Retrieve(查询),Update(更新)和 Delete(删除)。 | * 注:CRUD 是指 Create(创建),Retrieve(查询),Update(更新)和 Delete(删除)。 | ||
第37行: | 第37行: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
: 然后定义一个完成 CRUD 操作的 Service | : 然后定义一个完成 CRUD 操作的 Service 接口:'''UserService''',定义三个方法: | ||
:# saveUser完成创建(C)、更新操作(U)。 | :# saveUser完成创建(C)、更新操作(U)。 | ||
:# getUser完成查询操作(R)。 | :# getUser完成查询操作(R)。 | ||
第67行: | 第67行: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
定义完了 Service 接口之后,接下来就是定义 Service | 定义完了 Service 接口之后,接下来就是定义 Service 服务的具体实现。不过,这里聚焦的是:'''如何通过 spring-data-redis 库,使 Service 实现带缓存的功能?''' | ||
== 配置 spring-redis.xml == | == 配置 spring-redis.xml == | ||
使用 spring-data-redis 库的步骤: | 使用 spring-data-redis 库的步骤: | ||
# '''配置依赖''': | # '''配置依赖''': | ||
# | #: 在 Maven 的 pom 文件中加上 spring-data-redis 库的依赖,具体如下: | ||
#: <syntaxhighlight lang="xml" highlight=""> | #: <syntaxhighlight lang="xml" highlight=""> | ||
<dependency> | <dependency> | ||
第80行: | 第80行: | ||
</dependency> | </dependency> | ||
</syntaxhighlight> | </syntaxhighlight> | ||
# | # '''配置 spring-redis.xml''': | ||
# | #: 连接池实例、RedisTemplate模板实例;(均为 spring bean) | ||
#: | #* 这是两个 spring bean,可以配置在项目统一的 spring xml 配置文件中,也可以编写一个独立的 springredis.xml 配置文件。 | ||
#*:(以下采用第二种方式:“'''spring-redis.xml'''”) | |||
#: <syntaxhighlight lang="xml" highlight=""> | #: <syntaxhighlight lang="xml" highlight=""> | ||
<!--加载配置文件 --> | <!--加载配置文件 --> | ||
第134行: | 第135行: | ||
</bean> | </bean> | ||
<!-- | <!-- 省略其他的 spring-redis.xml 配置 --> | ||
</syntaxhighlight> | </syntaxhighlight> | ||
spring-data-redis 库在 JedisPool 提供连接池的基础上封装了自己的连接池—— '''RedisConnectionFactory''' 连接工厂;并且 spring-data-redis 封装了一个短期、非线程安全的连接类,名为 '''RedisConnection''' 连接类。 | spring-data-redis 库在 JedisPool 提供连接池的基础上封装了自己的连接池—— '''RedisConnectionFactory''' 连接工厂;并且 spring-data-redis 封装了一个短期、非线程安全的连接类,名为 <big>'''RedisConnection'''</big> 连接类。 | ||
RedisConnection 类和 Jedis 库中的 Jedis 类原理一样,提供了与 Redis 客户端命令一对一的 API 函数,用于操作远程 Redis 服务。 | RedisConnection 类和 Jedis 库中的 Jedis 类原理一样,提供了与 Redis 客户端命令一对一的 API 函数,用于操作远程 Redis 服务。 | ||
第152行: | 第146行: | ||
RedisConnection 的 API 命令操作的对象都是'''字节级别'''的 Key 键和 Value 值。为了更进一步地减少开发的工作,spring-data-redis 库在 RedisConnection 连接类的基础上,针对不同的缓存类型,设计了'''五大数据类型的命令 API 集合''',用于完成不同类型的数据缓存操作,并封装在 RedisTemplate 模板类中。 | RedisConnection 的 API 命令操作的对象都是'''字节级别'''的 Key 键和 Value 值。为了更进一步地减少开发的工作,spring-data-redis 库在 RedisConnection 连接类的基础上,针对不同的缓存类型,设计了'''五大数据类型的命令 API 集合''',用于完成不同类型的数据缓存操作,并封装在 RedisTemplate 模板类中。 | ||
== 使用 RedisTemplate 模板 API == | == 使用 RedisTemplate 模板 API == | ||
第165行: | 第158行: | ||
【'''在代码中,除了 key 相关的 Redis 操作(如:keys、hasKey)直接使用 redisTemplate 实例完成。其他的 API 命令,都是在不同类型的命令集合类上完成。'''】 | |||
RedisTemplate 提供了5个方法,取得不同类型的命令集合,具体为: | RedisTemplate 提供了5个方法,取得不同类型的命令集合,具体为: | ||
# '''redisTemplate.opsForValue()''':取得 String 类型命令API集合。 | # '''redisTemplate.opsForValue()''':取得 String 类型命令API集合。 | ||
第177行: | 第169行: | ||
【'''在实际开发中,为了尽可能地减少第三方库的“入侵”,或者为了在不同的第三方库之间进行方便的切换,一般来说,要对第三方库进行封装。'''】 | 【'''在实际开发中,为了尽可能地减少第三方库的“入侵”,或者为了在不同的第三方库之间进行方便的切换,一般来说,要对第三方库进行封装。'''】 | ||
示例: | 示例: | ||
# 将 RedisTemplate 模板类的大部分缓存操作封装成一个自己的缓存操作 Service 服务 —— '''CacheOperationService''',部分源代码节选如下: | |||
: <syntaxhighlight lang="Java" highlight="20,92,124,158,189"> | #: <syntaxhighlight lang="Java" highlight="20,92,124,158,189"> | ||
package com.crazymakercircle.redis.springJedis; | package com.crazymakercircle.redis.springJedis; | ||
//... | //... | ||
public class CacheOperationService { | public class CacheOperationService { | ||
private | private RedisTemplate redisTemplate; | ||
public void setRedisTemplate( | public void setRedisTemplate(RedisTemplate redisTemplate) { | ||
this.redisTemplate = redisTemplate; | this.redisTemplate = redisTemplate; | ||
} | } | ||
第394行: | 第385行: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
# 配置“'''spring-redis.xml'''”:将 redisTemplate 封装成通用服务(为缓存 Service 配置依赖) | |||
#: <syntaxhighlight lang="xml" highlight=""> | |||
<!-- 省略其他的spring-redis.xml配置,具体参见源代码 --> | |||
<!-- 1、redis 数据源 | |||
2、Spring-redis 连接池管理工厂 | |||
3、redis template definition | |||
--> | |||
<!-- . . . --> | |||
<!--将 redisTemplate 封装成通用服务--> | |||
<bean id="springRedisService" class="com.crazymakercircle.redis.springJedis.CacheOperationService"> | |||
<property name="redisTemplate" ref="redisTemplate"/> | |||
</bean> | |||
</syntaxhighlight> | |||
== 使用 RedisTemplate 模板 API 完成 CRUD 的实践案例 == | == 使用 RedisTemplate 模板 API 完成 CRUD 的实践案例 == | ||
封装完成了自己的 CacheOperationService 缓存管理服务之后,可以注入到 Spring 的业务 Service 中,就可以完成缓存的 CRUD 操作了。 | |||
* 使用 CacheOperationService 后,就能非常方便地进行缓存的管理,同时,在进行 POJO 的查询时,能优先使用缓存数据,省去了数据库访问的时间。 | |||
示例: | |||
# 实现业务类 '''UserServiceImplWithTemplate''' 类(实现“UserService”接口,包含“CacheOperationService”成员对象),用于完成 User 实例缓存的 CRUD。 | |||
#: <syntaxhighlight lang="Java" highlight="4,6,21,23"> | |||
package com.crazymakercircle.redis.springJedis; | |||
import com.crazymakercircle.im.common.bean.User; | |||
import com.crazymakercircle.util.Logger; | |||
public class UserServiceImplWithTemplate implements UserService { | |||
public static final String USER_UID_PREFIX = "user:uid:"; | |||
protected CacheOperationService cacheOperationService; | |||
private static final long CASHE_LONG = 60 * 4;//4分钟 | |||
public void setCacheOperationService(CacheOperationService cacheOperationService) { | |||
this.cacheOperationService = cacheOperationService; | |||
} | |||
/** | |||
* CRUD 的创建/更新 | |||
* @param user 用户 | |||
*/ | |||
@Override | |||
public User saveUser(final User user) { | |||
//保存到缓存 | |||
String key = USER_UID_PREFIX + user.getUid(); | |||
Logger.info("user :", user); | |||
cacheOperationService.set(key, user, CASHE_LONG); | |||
//保存到数据库 | |||
//...如mysql | |||
return user; | |||
} | |||
/** | |||
* CRUD 的查询 | |||
* @param id id | |||
* @return 用户 | |||
*/ | |||
@Override | |||
public User getUser(final long id) { | |||
//首先从缓存中获取 | |||
String key = USER_UID_PREFIX + id; | |||
User value = (User) cacheOperationService.get(key); | |||
if (null == value) { | |||
//如果缓存中没有,就从数据库中获取 | |||
//...如mysql | |||
//并且保存到缓存 | |||
} | |||
return value; | |||
} | |||
/** | |||
* CRUD 的删除 | |||
* @param id id | |||
*/ | |||
@Override | |||
public void deleteUser(long id) { | |||
//从缓存删除 | |||
String key = USER_UID_PREFIX + id; | |||
cacheOperationService.del(key); | |||
//从数据库删除 | |||
//...如mysql | |||
Logger.info("delete User:", id); | |||
} | |||
} | |||
</syntaxhighlight> | |||
# 配置“'''spring-redis.xml'''”:在业务 Service 类使用 CacheOperationService 缓存管理之前,还需要在配置文件中为其配置依赖: | |||
#: <syntaxhighlight lang="xml" highlight=""> | |||
<!-- 省略其他的spring-redis.xml配置,具体参见源代码 --> | |||
<!-- 1、redis 数据源 | |||
2、Spring-redis 连接池管理工厂 | |||
3、redis template definition | |||
4、将 redisTemplate 封装成通用服务 | |||
--> | |||
<!-- . . . --> | |||
<!--业务 service,依赖缓存 service--> | |||
<bean id="serviceImplWithTemplate" class="com.crazymakercircle.redis.springJedis.UserServiceImplWithTemplate"> | |||
<property name="cacheOperationService" ref="cacheOperationService"/> | |||
</bean> | |||
</syntaxhighlight> | |||
# 编写一个用例,测试一下 UserServiceImplWithTemplate,运行之后,可以从 Redis 客户端输入命令来查看缓存的数据。 | |||
#: 至此,缓存机制已经成功生效,数据访问的时间可以从数据库的百毫秒级别缩小到毫秒级别,性能提升了100倍。 | |||
#: <syntaxhighlight lang="Java" highlight=""> | |||
package com.crazymakercircle.redis.springJedis; | |||
import com.crazymakercircle.im.common.bean.User; | |||
import com.crazymakercircle.util.Logger; | |||
import org.junit.Test; | |||
import org.springframework.context.ApplicationContext; | |||
import org.springframework.context.support.ClassPathXmlApplicationContext; | |||
public class SpringRedisTester { | |||
@Test | |||
public void testServiceImplWithTemplate() { | |||
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring-redis.xml"); | |||
UserServiceuserService = (UserService) ac.getBean("serviceImplWithTemplate"); | |||
long userId = 1L; | |||
userService.deleteUser(userId); | |||
User userInredis = userService.getUser(userId); | |||
Logger.info("delete user", userInredis); | |||
User user = new User(); | |||
user.setUid("1"); | |||
user.setNickName("foo"); | |||
userService.saveUser(user); | |||
Logger.info("save user:", user); | |||
userInredis = userService.getUser(userId); | |||
Logger.info("get user", userInredis); | |||
} | |||
//....省略其他的测试用例 | |||
} | |||
</syntaxhighlight> | |||
== 使用 '''RedisCallback''' 回调完成 CRUD 的实践案例 == | |||
前面讲到,“'''RedisConnection 连接类'''”和“'''RedisTemplate模板类'''”都提供了整套 Redis 操作的 API,只不过,它们的层次不同: | |||
1、RedisConnection 连接类更加底层,它负责'''二进制层面'''的 Redis 操作,Key、Value 都是二进制字节数组。 | |||
2、RedisTemplate 模板类,在 RedisConnection 的基础上,使用在 spring-redis.xml 中配置的'''序列化、反序列化'''的工具类,完成'''上层类型'''(如:String、Object、POJO类等)的 Redis 操作。 | |||
如果不需要 RedisTemplate 配置的序列化、反序列化的工具类,或者由于其他的原因,需要直接使用 RedisConnection 去操作 Redis,怎么办呢? | |||
: 可以'''使用 RedisCallback 的 doInRedis 回调方法''',在 doInRedis 回调方法中,直接使用实参 RedisConnection 连接类实例来完成 Redis 的操作。 | |||
* 当然,完成 RedisCallback 回调业务逻辑后,还需要使用 RedisTemplate 模板实例去执行,调用的是“'''RedisTemplate.execute(RedisCallback)'''”方法。 | |||
示例: | |||
# 通过 RedisCallback 回调方法实现 CRUD 的实例代码如下:UserServiceImplInTemplate 类(实现“UserService”接口,包含“RedisTemplate”成员对象) | |||
#: <syntaxhighlight lang="Java" highlight="3,5,20-28"> | |||
package com.crazymakercircle.redis.springJedis; | |||
//... | |||
public class UserServiceImplInTemplate implements UserService { | |||
public static final String USER_UID_PREFIX = "user:uid:"; | |||
private RedisTemplate redisTemplate; | |||
private static final long CASHE_LONG = 60 * 4;//4分钟 | |||
public void setRedisTemplate(RedisTemplate redisTemplate) { | |||
this.redisTemplate = redisTemplate; | |||
} | |||
/** | |||
* CRUD 的创建/更新 | |||
* @param user 用户 | |||
*/ | |||
@Override | |||
public User saveUser(final User user) { | |||
//保存到缓存 | |||
redisTemplate.execute(new RedisCallback<User>() { | |||
@Override | |||
public User doInRedis(RedisConnection connection) throws DataAccessException { | |||
byte[] key = serializeKey(USER_UID_PREFIX + user.getUid()); | |||
connection.set(key, serializeValue(user)); | |||
connection.expire(key, CASHE_LONG); | |||
return user; | |||
} | |||
}); | |||
//保存到数据库 | |||
//...如mysql | |||
return user; | |||
} | |||
private byte[] serializeValue(User s) { | |||
return redisTemplate.getValueSerializer().serialize(s); | |||
} | |||
private byte[] serializeKey(String s) { | |||
return redisTemplate.getKeySerializer().serialize(s); | |||
} | |||
private User deSerializeValue(byte[] b) { | |||
return (User) redisTemplate.getValueSerializer().deserialize(b); | |||
} | |||
/** | |||
* CRUD 的查询 | |||
* @param id id | |||
* @return 用户 | |||
*/ | |||
@Override | |||
public User getUser(final long id) { | |||
//首先从缓存中获取 | |||
User value = (User) redisTemplate.execute(new RedisCallback<User>() { | |||
@Override | |||
public User doInRedis(RedisConnection connection) throws DataAccessException { | |||
byte[] key = serializeKey(USER_UID_PREFIX + id); | |||
if (connection.exists(key)) { | |||
byte[] value = connection.get(key); | |||
return deSerializeValue(value); | |||
} | |||
return null; | |||
} | |||
}); | |||
if (null == value) { | |||
//如果缓存中没有,从数据库中获取 | |||
//...如mysql | |||
//并且保存到缓存 | |||
} | |||
return value; | |||
} | |||
/** | |||
* CRUD 的删除 | |||
* @param id id | |||
*/ | |||
@Override | |||
public void deleteUser(long id) { | |||
//从缓存删除 | |||
redisTemplate.execute(new RedisCallback<Boolean>() { | |||
@Override | |||
public Boolean doInRedis(RedisConnection connection) throws DataAccessException { | |||
byte[] key = serializeKey(USER_UID_PREFIX + id); | |||
if (connection.exists(key)) { | |||
connection.del(key); | |||
} | |||
return true; | |||
} | |||
}); | |||
//从数据库删除 | |||
//...如mysql | |||
} | |||
} | |||
</syntaxhighlight> | |||
# 配置“'''spring-redis.xml'''”:同样的,在使用 UserServiceImplInTemplate 之前,也需要在配置文件中为其配置好依赖关系: | |||
#: <syntaxhighlight lang="xml" highlight=""> | |||
<!-- 省略其他的spring-redis.xml配置,具体参见源代码 --> | |||
<!-- 1、redis 数据源 | |||
2、Spring-redis 连接池管理工厂 | |||
3、redis template definition | |||
--> | |||
<!-- . . . --> | |||
<!--业务service,依赖缓存service--> | |||
<bean id="serviceImplInTemplate" class="com.crazymakercircle.redis.springJedis.UserServiceImplInTemplate"> | |||
<property name="redisTemplate" ref="redisTemplate"/> | |||
</bean> | |||
</syntaxhighlight> |
2021年12月27日 (一) 13:22的最新版本
关于
无论是 Jedis 还是 JedisPool,都只是完成对 Redis 操作的极为基础的 API,在不依赖任何中间件的开发环境中,可以使用它们。但是,一般的 Java 开发,都会使用了 Spring 框架,可以使用 spring-data-redis 开源库来简化 Redis 操作的代码逻辑,做到最大程度的业务聚焦。
CRUD 中应用缓存的场景
在普通 CRUD 应用场景中,很多情况下需要同步操作缓存,推荐使用 Spring 的 spring-data-redis 开源库。
- 注:CRUD 是指 Create(创建),Retrieve(查询),Update(更新)和 Delete(删除)。
- 创建缓存:
- 在创建(Create)一个 POJO 实例的时候,对 POJO 实例进行分布式缓存,一般以“缓存前缀+ID”为缓存的 Key 键,POJO 对象为缓存的 Value 值,直接缓存 POJO 的二进制字节。
- 前提是:POJO 必须可序列化,实现 java.io.Serializable 空接口。如果 POJO 不可序列化,也是可以缓存的,但是必须自己实现序列化的方式,例如使用 JSON 方式序列化。
- 查询缓存:
- 在查询(Retrieve)一个 POJO 实例的时候,首先应该根据 POJO 缓存的 Key 键,从 Redis 缓存中返回结果。
- 如果不存在,才去查询数据库,并且能够将数据库的结果缓存起来。
- 更新缓存:
- 在更新(Update)一个 POJO 实例的时候,既需要更新数据库的 POJO 数据记录,也需要更新 POJO 的缓存记录。
- 删除缓存:
- 在删除(Delete)一个 POJO 实例的时候,既需要删除数据库的 POJO 数据记录,也需要删除 POJO 的缓存记录。
为了演示 CRUD 场景下 Redis 的缓存操作
- 首先定义一个简单的 POJO 实体类:聊天系统的用户类。
- 此类拥有一些简单的属性,例如 uid 和 nickName,且这些属性都具备基本的 getter 和 setter 方法:
package com.crazymakercircle.im.common.bean; //... import java.io.Serializable; @Slf4j public class User implements Serializable { String uid; String devId; String token; String nickName; //....省略 getter setter toString等方法 }
- 然后定义一个完成 CRUD 操作的 Service 接口:UserService,定义三个方法:
- saveUser完成创建(C)、更新操作(U)。
- getUser完成查询操作(R)。
- deleteUser完成删除操作(D)。
- Service接口的代码如下:
package com.crazymakercircle.redis.springJedis; import com.crazymakercircle.im.common.bean.User; public interface UserService { /** * CRUD 的创建/更新 * @param user 用户 */ User saveUser(final User user); /** * CRUD 的查询 * @param id id * @return 用户 */ User getUser(long id); /** * CRUD 的删除 * @param id id */ void deleteUser(long id); }
定义完了 Service 接口之后,接下来就是定义 Service 服务的具体实现。不过,这里聚焦的是:如何通过 spring-data-redis 库,使 Service 实现带缓存的功能?
配置 spring-redis.xml
使用 spring-data-redis 库的步骤:
- 配置依赖:
- 在 Maven 的 pom 文件中加上 spring-data-redis 库的依赖,具体如下:
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>${springboot}</version> </dependency>
- 配置 spring-redis.xml:
- 连接池实例、RedisTemplate模板实例;(均为 spring bean)
- 这是两个 spring bean,可以配置在项目统一的 spring xml 配置文件中,也可以编写一个独立的 springredis.xml 配置文件。
- (以下采用第二种方式:“spring-redis.xml”)
<!--加载配置文件 --> <context:property-placeholder location="classpath:redis.properties"/> <!--redis 数据源 --> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!--最大空闲数 --> <property name="maxIdle" value="${redis.maxIdle}"/> <!--最大空连接数 --> <property name="maxTotal" value="${redis.maxTotal}"/> <!--最大等待时间 --> <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/> <!--连接超时的时候是否阻塞,true表示阻塞,直到超过maxWaitMillis, 默认为true --> <property name="blockWhenExhausted" value="${redis.blockWhenExhausted}"/> <!--获取连接时,检测连接是否成功 --> <property name="testOnBorrow" value="${redis.testOnBorrow}"/> </bean> <!-- Spring-redis 连接池管理工厂 --> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <!-- IP地址 --> <property name="hostName" value="${redis.host}"/> <!--端口号 --> <property name="port" value="${redis.port}"/> <!--连接池配置引用 --> <property name="poolConfig" ref="poolConfig"/> <!--usePool:是否使用连接池 --> <property name="usePool" value="true"/> </bean> <!-- redis template definition --> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory"/> <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <property name="valueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/> </property> <property name="hashKeySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <property name="hashValueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/> </property> <!--开启事务 --> <property name="enableTransactionSupport" value="true"></property> </bean> <!-- 省略其他的 spring-redis.xml 配置 -->
spring-data-redis 库在 JedisPool 提供连接池的基础上封装了自己的连接池—— RedisConnectionFactory 连接工厂;并且 spring-data-redis 封装了一个短期、非线程安全的连接类,名为 RedisConnection 连接类。 RedisConnection 类和 Jedis 库中的 Jedis 类原理一样,提供了与 Redis 客户端命令一对一的 API 函数,用于操作远程 Redis 服务。
在使用 spring-data-redis 时,虽然没有直接用到 Jedis 库,但是 spring-data-redis 库底层对 Redis 服务的操作还是调用 Jedis 库完成的。也就是说,spring-data-redis 库从一定程度上使大家更好地使用 Jedis 库。
RedisConnection 的 API 命令操作的对象都是字节级别的 Key 键和 Value 值。为了更进一步地减少开发的工作,spring-data-redis 库在 RedisConnection 连接类的基础上,针对不同的缓存类型,设计了五大数据类型的命令 API 集合,用于完成不同类型的数据缓存操作,并封装在 RedisTemplate 模板类中。
使用 RedisTemplate 模板 API
RedisTemplate 模板类位于核心包 org.springframework.data.redis.core 中,它封装了五大数据类型的命令 API 集合:
- ValueOperations:字符串类型操作 API 集合。
- ListOperations:列表类型操作 API 集合。
- SetOperations:集合类型操作 API 集合。
- ZSetOperations:有序集合类型 API 集合。
- HashOperations:哈希类型操作 API 集合。
- 每一种类型的操作 API 基本上都和每一种类型的Redis客户端命令一一对应。但是在 API 的名称上并不完全一致,RedisTemplate 的 API 名称更加人性化。
- 例如,Redis 客户端命令“setNX”——Key-Value不存在才设值,在 RedisTemplate 的 API 名称为“setIfAbsent”。
【在代码中,除了 key 相关的 Redis 操作(如:keys、hasKey)直接使用 redisTemplate 实例完成。其他的 API 命令,都是在不同类型的命令集合类上完成。】
RedisTemplate 提供了5个方法,取得不同类型的命令集合,具体为:
- redisTemplate.opsForValue():取得 String 类型命令API集合。
- redisTemplate.opsForList():取得 List 类型命令API集合。
- redisTemplate.opsForSet():取得 Set 类型命令API集合。
- redisTemplate.opsForHash():取得 Hash 类型命令API集合。
- redisTemplate.opsForZSet():取得 Zset 类型命令API集合。
然后,在不同类型的命令 API 集合上,使用各种数据类型特有的 API 函数,完成具体的 Redis API 操作。
【在实际开发中,为了尽可能地减少第三方库的“入侵”,或者为了在不同的第三方库之间进行方便的切换,一般来说,要对第三方库进行封装。】
示例:
- 将 RedisTemplate 模板类的大部分缓存操作封装成一个自己的缓存操作 Service 服务 —— CacheOperationService,部分源代码节选如下:
package com.crazymakercircle.redis.springJedis; //... public class CacheOperationService { private RedisTemplate redisTemplate; public void setRedisTemplate(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } // --------------RedisTemplate基础操作 -------------------- /** * 取得指定格式的所有的key键 * * @param patens 匹配的表达式 * @return key 的集合 */ public Set getKeys(Object patens) { try { return redisTemplate.keys(patens); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 指定缓存失效的时间 * * @param key键 * @param time 时间(秒) * @return */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据key 获取过期的时间 * @param key 键不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断key是否存在 * @param key 键 * @return true则存在,false则不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * @param key 可以传一个值或多个 * @return 删除的个数 */ public void del(String... key) { if (key != null &&key.length> 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } // --------------RedisTemplate操作 String字符串 -------------------- /** * 获取String * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 设置String * @param key 键 * @param value 值 * @return true则成功,false则失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } //...省略其他的String 操作,请参见源代码 // --------------RedisTemplate操作list列表 -------------------- /** * 获取list缓存的内容,start == 0 到 end == -1代表所有值 * @param key 键 * @param start开始,从0开始 * @param end结束 * @return */ public List<Object> lGet(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 将list放入缓存,从右边(后端)插入 * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } //...省略其他的list操作,请参见源代码 // --------------RedisTemplate操作 Hash列表 -------------------- /** * HashGet,设置map中的一个field * @param key 键不能为null * @param field项不能为null * @return 值 */ public Object hget(String key, String field) { return redisTemplate.opsForHash().get(key, field); } /** * HashSet * @param key 键 * @param map 对应多个键值 * @return true 成功 false 失败 */ public boolean hmset(String key, Map<String, Object> map) { try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { e.printStackTrace(); return false; } } //...省略其他的hash操作,请参见源代码 // --------------RedisTemplate操作 Set集合 -------------------- /** * 根据key获取Set中的所有值 * * @param key 键 * @return */ public Set<Object> sGet(String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 将数据放入set缓存 * @param key 键 * @param values 值可以是多个 * @return 成功个数 */ public long sSet(String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { e.printStackTrace(); return 0; } } //...省略其他的Set操作,请参见源代码 }
- 配置“spring-redis.xml”:将 redisTemplate 封装成通用服务(为缓存 Service 配置依赖)
<!-- 省略其他的spring-redis.xml配置,具体参见源代码 --> <!-- 1、redis 数据源 2、Spring-redis 连接池管理工厂 3、redis template definition --> <!-- . . . --> <!--将 redisTemplate 封装成通用服务--> <bean id="springRedisService" class="com.crazymakercircle.redis.springJedis.CacheOperationService"> <property name="redisTemplate" ref="redisTemplate"/> </bean>
使用 RedisTemplate 模板 API 完成 CRUD 的实践案例
封装完成了自己的 CacheOperationService 缓存管理服务之后,可以注入到 Spring 的业务 Service 中,就可以完成缓存的 CRUD 操作了。
- 使用 CacheOperationService 后,就能非常方便地进行缓存的管理,同时,在进行 POJO 的查询时,能优先使用缓存数据,省去了数据库访问的时间。
示例:
- 实现业务类 UserServiceImplWithTemplate 类(实现“UserService”接口,包含“CacheOperationService”成员对象),用于完成 User 实例缓存的 CRUD。
package com.crazymakercircle.redis.springJedis; import com.crazymakercircle.im.common.bean.User; import com.crazymakercircle.util.Logger; public class UserServiceImplWithTemplate implements UserService { public static final String USER_UID_PREFIX = "user:uid:"; protected CacheOperationService cacheOperationService; private static final long CASHE_LONG = 60 * 4;//4分钟 public void setCacheOperationService(CacheOperationService cacheOperationService) { this.cacheOperationService = cacheOperationService; } /** * CRUD 的创建/更新 * @param user 用户 */ @Override public User saveUser(final User user) { //保存到缓存 String key = USER_UID_PREFIX + user.getUid(); Logger.info("user :", user); cacheOperationService.set(key, user, CASHE_LONG); //保存到数据库 //...如mysql return user; } /** * CRUD 的查询 * @param id id * @return 用户 */ @Override public User getUser(final long id) { //首先从缓存中获取 String key = USER_UID_PREFIX + id; User value = (User) cacheOperationService.get(key); if (null == value) { //如果缓存中没有,就从数据库中获取 //...如mysql //并且保存到缓存 } return value; } /** * CRUD 的删除 * @param id id */ @Override public void deleteUser(long id) { //从缓存删除 String key = USER_UID_PREFIX + id; cacheOperationService.del(key); //从数据库删除 //...如mysql Logger.info("delete User:", id); } }
- 配置“spring-redis.xml”:在业务 Service 类使用 CacheOperationService 缓存管理之前,还需要在配置文件中为其配置依赖:
<!-- 省略其他的spring-redis.xml配置,具体参见源代码 --> <!-- 1、redis 数据源 2、Spring-redis 连接池管理工厂 3、redis template definition 4、将 redisTemplate 封装成通用服务 --> <!-- . . . --> <!--业务 service,依赖缓存 service--> <bean id="serviceImplWithTemplate" class="com.crazymakercircle.redis.springJedis.UserServiceImplWithTemplate"> <property name="cacheOperationService" ref="cacheOperationService"/> </bean>
- 编写一个用例,测试一下 UserServiceImplWithTemplate,运行之后,可以从 Redis 客户端输入命令来查看缓存的数据。
- 至此,缓存机制已经成功生效,数据访问的时间可以从数据库的百毫秒级别缩小到毫秒级别,性能提升了100倍。
package com.crazymakercircle.redis.springJedis; import com.crazymakercircle.im.common.bean.User; import com.crazymakercircle.util.Logger; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringRedisTester { @Test public void testServiceImplWithTemplate() { ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring-redis.xml"); UserServiceuserService = (UserService) ac.getBean("serviceImplWithTemplate"); long userId = 1L; userService.deleteUser(userId); User userInredis = userService.getUser(userId); Logger.info("delete user", userInredis); User user = new User(); user.setUid("1"); user.setNickName("foo"); userService.saveUser(user); Logger.info("save user:", user); userInredis = userService.getUser(userId); Logger.info("get user", userInredis); } //....省略其他的测试用例 }
使用 RedisCallback 回调完成 CRUD 的实践案例
前面讲到,“RedisConnection 连接类”和“RedisTemplate模板类”都提供了整套 Redis 操作的 API,只不过,它们的层次不同: 1、RedisConnection 连接类更加底层,它负责二进制层面的 Redis 操作,Key、Value 都是二进制字节数组。 2、RedisTemplate 模板类,在 RedisConnection 的基础上,使用在 spring-redis.xml 中配置的序列化、反序列化的工具类,完成上层类型(如:String、Object、POJO类等)的 Redis 操作。
如果不需要 RedisTemplate 配置的序列化、反序列化的工具类,或者由于其他的原因,需要直接使用 RedisConnection 去操作 Redis,怎么办呢?
- 可以使用 RedisCallback 的 doInRedis 回调方法,在 doInRedis 回调方法中,直接使用实参 RedisConnection 连接类实例来完成 Redis 的操作。
- 当然,完成 RedisCallback 回调业务逻辑后,还需要使用 RedisTemplate 模板实例去执行,调用的是“RedisTemplate.execute(RedisCallback)”方法。
示例:
- 通过 RedisCallback 回调方法实现 CRUD 的实例代码如下:UserServiceImplInTemplate 类(实现“UserService”接口,包含“RedisTemplate”成员对象)
package com.crazymakercircle.redis.springJedis; //... public class UserServiceImplInTemplate implements UserService { public static final String USER_UID_PREFIX = "user:uid:"; private RedisTemplate redisTemplate; private static final long CASHE_LONG = 60 * 4;//4分钟 public void setRedisTemplate(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } /** * CRUD 的创建/更新 * @param user 用户 */ @Override public User saveUser(final User user) { //保存到缓存 redisTemplate.execute(new RedisCallback<User>() { @Override public User doInRedis(RedisConnection connection) throws DataAccessException { byte[] key = serializeKey(USER_UID_PREFIX + user.getUid()); connection.set(key, serializeValue(user)); connection.expire(key, CASHE_LONG); return user; } }); //保存到数据库 //...如mysql return user; } private byte[] serializeValue(User s) { return redisTemplate.getValueSerializer().serialize(s); } private byte[] serializeKey(String s) { return redisTemplate.getKeySerializer().serialize(s); } private User deSerializeValue(byte[] b) { return (User) redisTemplate.getValueSerializer().deserialize(b); } /** * CRUD 的查询 * @param id id * @return 用户 */ @Override public User getUser(final long id) { //首先从缓存中获取 User value = (User) redisTemplate.execute(new RedisCallback<User>() { @Override public User doInRedis(RedisConnection connection) throws DataAccessException { byte[] key = serializeKey(USER_UID_PREFIX + id); if (connection.exists(key)) { byte[] value = connection.get(key); return deSerializeValue(value); } return null; } }); if (null == value) { //如果缓存中没有,从数据库中获取 //...如mysql //并且保存到缓存 } return value; } /** * CRUD 的删除 * @param id id */ @Override public void deleteUser(long id) { //从缓存删除 redisTemplate.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection connection) throws DataAccessException { byte[] key = serializeKey(USER_UID_PREFIX + id); if (connection.exists(key)) { connection.del(key); } return true; } }); //从数据库删除 //...如mysql } }
- 配置“spring-redis.xml”:同样的,在使用 UserServiceImplInTemplate 之前,也需要在配置文件中为其配置好依赖关系:
<!-- 省略其他的spring-redis.xml配置,具体参见源代码 --> <!-- 1、redis 数据源 2、Spring-redis 连接池管理工厂 3、redis template definition --> <!-- . . . --> <!--业务service,依赖缓存service--> <bean id="serviceImplInTemplate" class="com.crazymakercircle.redis.springJedis.UserServiceImplInTemplate"> <property name="redisTemplate" ref="redisTemplate"/> </bean>