查看“Spring-data-redis 的使用实践”的源代码
←
Spring-data-redis 的使用实践
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[category:Redis]] == 关于 == 无论是 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 方法: : <syntaxhighlight lang="Java" highlight=""> 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等方法 } </syntaxhighlight> : 然后定义一个完成 CRUD 操作的 Service 接口:'''UserService''',定义三个方法: :# saveUser完成创建(C)、更新操作(U)。 :# getUser完成查询操作(R)。 :# deleteUser完成删除操作(D)。 :: Service接口的代码如下: : <syntaxhighlight lang="Java" highlight=""> 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); } </syntaxhighlight> 定义完了 Service 接口之后,接下来就是定义 Service 服务的具体实现。不过,这里聚焦的是:'''如何通过 spring-data-redis 库,使 Service 实现带缓存的功能?''' == 配置 spring-redis.xml == 使用 spring-data-redis 库的步骤: # '''配置依赖''': #: 在 Maven 的 pom 文件中加上 spring-data-redis 库的依赖,具体如下: #: <syntaxhighlight lang="xml" highlight=""> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>${springboot}</version> </dependency> </syntaxhighlight> # '''配置 spring-redis.xml''': #: 连接池实例、RedisTemplate模板实例;(均为 spring bean) #* 这是两个 spring bean,可以配置在项目统一的 spring xml 配置文件中,也可以编写一个独立的 springredis.xml 配置文件。 #*:(以下采用第二种方式:“'''spring-redis.xml'''”) #: <syntaxhighlight lang="xml" highlight=""> <!--加载配置文件 --> <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 配置 --> </syntaxhighlight> spring-data-redis 库在 JedisPool 提供连接池的基础上封装了自己的连接池—— '''RedisConnectionFactory''' 连接工厂;并且 spring-data-redis 封装了一个短期、非线程安全的连接类,名为 <big>'''RedisConnection'''</big> 连接类。 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''',部分源代码节选如下: #: <syntaxhighlight lang="Java" highlight="20,92,124,158,189"> 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操作,请参见源代码 } </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 的实践案例 == 封装完成了自己的 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>
返回至“
Spring-data-redis 的使用实践
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
大陆简体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
笔记
服务器
数据库
后端
前端
工具
《To do list》
日常
阅读
电影
摄影
其他
Software
Windows
WIKIOE
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息