spring-data-redis连接操作redis

原文转自:http://blog.csdn.net/wanghuiqi2008/article/details/52097381

Java连接redis的客户端有很多,其中比较常用的是Jedis. (参考:redis client)

spring-data-redis则是对Jedis进行了高度封装,使用起来非常方便。下面就以代码为例说明spring-data-redis的使用。

整个项目使用maven管理jar包,pom文件如下:

[html]  view plain  copy
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2.     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  
  3.     <modelVersion>4.0.0</modelVersion>  
  4.   
  5.     <groupId>com.snow</groupId>  
  6.     <artifactId>redis-test</artifactId>  
  7.     <version>0.0.1</version>  
  8.     <packaging>jar</packaging>  
  9.   
  10.     <name>redis-test</name>  
  11.     <url>http://maven.apache.org</url>  
  12.   
  13.     <properties>  
  14.         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  
  15.         <spring.version>4.3.2.RELEASE</spring.version>  
  16.         <slf4j.version>1.7.12</slf4j.version>  
  17.         <log4j.version>1.2.17</log4j.version>  
  18.         <junit.version>4.12</junit.version>  
  19.         <spring-data-redis.version>1.7.2.RELEASE</spring-data-redis.version>  
  20.     </properties>  
  21.   
  22.     <dependencies>  
  23.         <dependency>  
  24.             <groupId>org.springframework</groupId>  
  25.             <artifactId>spring-context</artifactId>  
  26.             <version>${spring.version}</version>  
  27.         </dependency>  
  28.         <dependency>  
  29.             <groupId>org.springframework</groupId>  
  30.             <artifactId>spring-test</artifactId>  
  31.             <version>${spring.version}</version>  
  32.             <scope>test</scope>  
  33.         </dependency>  
  34.   
  35.         <dependency>  
  36.             <groupId>redis.clients</groupId>  
  37.             <artifactId>jedis</artifactId>  
  38.             <version>2.5.0</version>  
  39.             <type>jar</type>  
  40.         </dependency>  
  41.         <dependency>  
  42.             <groupId>org.springframework.data</groupId>  
  43.             <artifactId>spring-data-redis</artifactId>  
  44.             <version>${spring-data-redis.version}</version>  
  45.             <type>jar</type>  
  46.         </dependency>  
  47.   
  48.         <!-- log -->  
  49.         <dependency>  
  50.             <groupId>log4j</groupId>  
  51.             <artifactId>log4j</artifactId>  
  52.             <version>${log4j.version}</version>  
  53.         </dependency>  
  54.         <dependency>  
  55.             <groupId>org.slf4j</groupId>  
  56.             <artifactId>slf4j-log4j12</artifactId>  
  57.             <version>${slf4j.version}</version>  
  58.         </dependency>  
  59.         <dependency>  
  60.             <groupId>org.slf4j</groupId>  
  61.             <artifactId>slf4j-api</artifactId>  
  62.             <version>${slf4j.version}</version>  
  63.         </dependency>  
  64.         <dependency>  
  65.             <groupId>junit</groupId>  
  66.             <artifactId>junit</artifactId>  
  67.             <version>${junit.version}</version>  
  68.             <scope>test</scope>  
  69.         </dependency>  
  70.     </dependencies>  
  71. </project>  
主要用到的jia包是spring-context、spring-data-redis、jedis以及日志打印相关的三个jar包

配置spring-data-redis如下application-context-redis.xml:

[html]  view plain  copy
  1.    <!-- 配置方法见 http://www.2cto.com/database/201311/254449.html -->  
  2. <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">  
  3.      <property name="maxTotal" value="500"/> <!-- 控制一个pool可分配多少个jedis实例 -->   
  4.      <property name="maxIdle" value="100"/><!-- 最大能够保持idel状态的对象数 -->  
  5.      <property name="maxWaitMillis" value="1000"/><!-- 表示当borrow一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException -->   
  6.      <property name="timeBetweenEvictionRunsMillis" value="30000"/><!-- 多长时间检查一次连接池中空闲的连接 -->  
  7.      <property name="minEvictableIdleTimeMillis" value="30000"/><!-- 空闲连接多长时间后会被收回, 单位是毫秒 -->  
  8.      <property name="testOnBorrow" value="true"/> <!-- 当调用borrow Object方法时,是否进行有效性检查 -->  
  9.      <property name="testOnReturn" value="true"/> <!-- 当调用return Object方法时,是否进行有效性检查 -->  
  10.      <property name="testWhileIdle" value="true"/>  
  11. </bean>  
  12.        
  13.   <span style="white-space:pre">  </span><!-- 直连master -->  
  14. <bean id="jedisConnectionFactory"  
  15.     class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">  
  16.     <constructor-arg ref="jedisPoolConfig" />  
  17.     <property name="hostName" value="${redis.hostName}" />  
  18.     <property name="port" value="${redis.port}" />  
  19.     <!-- <property name="password" value ="${redis.password}" /> -->  
  20. </bean>   
  21.      
  22.    <span style="white-space:pre"> </span><bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" >   
  23.     <span style="white-space:pre">    </span><property name="connectionFactory" ref="jedisConnectionFactory" />    
  24.    <span style="white-space:pre"> </span></bean>  
在配置文件中先配置一个连接池,然后配置一个connection工厂,最后配置bean redisTemplate,我们使用的class是StringRedisTemplate,从而决定我们后面的操作key及value都必须是String类型的。而通常我们希望将一个对象存入到redis中,这个时候可以将对象转为json字符串之后再存储,取出来的时候再将json字符串转换为对象。这种操作还是比较方便的,都有线程的jar包。除了StringRedisTemplate之外,我们还可以使用RedisTemplate类,这里暂不介绍。

在application-context-redis.xml这个配置文件中还需要用到redis的host和port信息,我们可以配置一个文件properties文件如下:redis.properties

[html]  view plain  copy
  1. redis.hostName=127.0.0.1  
  2. redis.port=6379  
这个配置文件需要在application-contex.xml中加载,同时application-context.xml还需要加载application-context-redis.xml配置文件

[html]  view plain  copy
  1. <bean id="dbPropertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
  2.     <property name="ignoreUnresolvablePlaceholders" value="true" />  
  3.     <property name="locations">  
  4.         <list>  
  5.             <value>classpath:redis.properties</value>  
  6.         </list>  
  7.     </property>  
  8.     <property name="fileEncoding" value="UTF-8" /><!-- 资源文件的编码 -->  
  9. </bean>  
  10.   
  11. <import resource="classpath:application-context-redis.xml" />    
接下来可以写个redis操作的接口

[java]  view plain  copy
  1. public interface RedisService {  
  2.   
  3.     public void setStr(String key, String value);  
  4.       
  5.     public String getStr(String key);  
  6.       
  7.     public void rPushList(String key, String value);  
  8.   
  9.     public String lPopList(String key);  
  10.   
  11.     public void delKey(String key);  
  12.   
  13. }  
接口实现如下:

[java]  view plain  copy
  1. @Service(value = "redisService")  
  2. public class RedisServiceImpl extends AbstractRedisDao<String, String> implements RedisService {  
  3.   
  4.     @Override  
  5.     public void setStr(String key, String value) {  
  6.         getRedisTemplate().opsForValue().set(key, value);  
  7.     }  
  8.   
  9.     @Override  
  10.     public String getStr(String key) {  
  11.         return getRedisTemplate().opsForValue().get(key);  
  12.     }  
  13.   
  14.     @Override  
  15.     public void rPushList(String key, String value) {  
  16.         getRedisTemplate().opsForList().rightPush(key, value);  
  17.   
  18.     }  
  19.   
  20.     @Override  
  21.     public String lPopList(String key) {  
  22.         return getRedisTemplate().opsForList().leftPop(key);  
  23.     }  
  24.       
  25.     @Override  
  26.     public void delKey(String key) {  
  27.         getRedisTemplate().delete(key);  
  28.     }  
  29.   
  30. }  
在该实现中继承了一个AbstractRedisDao,这个主要是提供getRedisTemplate()函数,使我们能够调用在application-context-redis.xml中配置的redisTemplate bean实例

[java]  view plain  copy
  1. public abstract class AbstractRedisDao<K, V> {  
  2.   
  3.     @Autowired  
  4.     protected RedisTemplate<K, V> redisTemplate;  
  5.   
  6.     // 设置redisTemplate  
  7.     public void setRedisTemplate(RedisTemplate<K, V> redisTemplate) {  
  8.         this.redisTemplate = redisTemplate;  
  9.     }  
  10.   
  11.     public RedisTemplate<K, V> getRedisTemplate() {  
  12.         return redisTemplate;  
  13.     }  
  14. }  
主要的程序都已经写完了,接下来可以用Junit写个单元测试对接口测试下。

[java]  view plain  copy
  1. public class RedisServiceTest extends AbstractUnitTest {  
  2.     private static final Logger logger = LoggerFactory.getLogger(RedisServiceTest.class);  
  3.   
  4.     @Resource  
  5.     private RedisService redisService;  
  6.   
  7.     @Test  
  8.     public void testSetStr() {  
  9.         String key = "test";  
  10.         String value = "valuetest";  
  11.         redisService.setStr(key, value);  
  12.     }  
  13.   
  14.     @Test  
  15.     public void testGetStr() {  
  16.         String key = "test";  
  17.         String value = redisService.getStr(key);  
  18.         logger.info("The value is {}", value);  
  19.     }  
  20.   
  21.     @Test  
  22.     public void testRPushList() {  
  23.         String key = "list";  
  24.         for (int i = 0; i < 10; i++) {  
  25.             redisService.rPushList(key, String.valueOf(i));  
  26.         }  
  27.     }  
  28.       
  29.       
  30.     @Test  
  31.     public void testLPopList() {  
  32.         String key = "list";  
  33.           
  34.         for(int i = 0; i < 9; i++) {  
  35.             String value = redisService.lPopList(key);  
  36.             logger.info("lpop value is {}", value);  
  37.         }  
  38.     }  
  39.       
  40.     @Test  
  41.     public void testDelKey() {  
  42.         String key = "list";  
  43.         redisService.delKey(key);  
  44.     }  
  45.       
  46.   
  47. }  
在这个测试类中,为了能够运行这些测试函数,需要对所有的bean进行实例化,这个过程是在 AbstractUnitTest中实现的,代码如下:

[java]  view plain  copy
  1. @RunWith(SpringJUnit4ClassRunner.class)  
  2. @ContextConfiguration(locations = {"classpath:application-context.xml"})  
  3. public abstract class AbstractUnitTest {  
  4.     private static final Logger logger = LoggerFactory.getLogger(AbstractUnitTest.class);  
  5.   
  6. //    @Test  
  7. //    public void stub() {  
  8. //        logger.info("msg from abstract unit test, just ignore this.");  
  9. //    }  
  10.   
  11.     @After  
  12.     public void teardown() throws InterruptedException {  
  13.         logger.info("unit test complete.");  
  14.         TimeUnit.MILLISECONDS.sleep(500);// 因为有些测试是需要异步插入操作记录的,sleep一下等待线程结束  
  15.     }  
  16.   
  17. }  
AbstractUnitTest类可以作为测试spring的一个通用类。


主要的代码就这些了,运行下可以看到结果是没有问题的。下面我摘抄一段打印输出说明一个问题:

[html]  view plain  copy
  1. 016-08-02 20:43:16,608 DEBUG RedisConnectionUtils:125 - Opening RedisConnection  
  2. 2016-08-02 20:43:16,609 DEBUG RedisConnectionUtils:205 - Closing Redis Connection  
  3. 2016-08-02 20:43:16,610  INFO RedisServiceTest:54 - lpop value is 0  
  4. 2016-08-02 20:43:16,610 DEBUG RedisConnectionUtils:125 - Opening RedisConnection  
  5. 2016-08-02 20:43:16,611 DEBUG RedisConnectionUtils:205 - Closing Redis Connection  
  6. 2016-08-02 20:43:16,611  INFO RedisServiceTest:54 - lpop value is 1  
  7. 2016-08-02 20:43:16,611 DEBUG RedisConnectionUtils:125 - Opening RedisConnection  
  8. 2016-08-02 20:43:16,611 DEBUG RedisConnectionUtils:205 - Closing Redis Connection  
  9. 2016-08-02 20:43:16,612  INFO RedisServiceTest:54 - lpop value is 2  
  10. 2016-08-02 20:43:16,612 DEBUG RedisConnectionUtils:125 - Opening RedisConnection  
  11. 2016-08-02 20:43:16,612 DEBUG RedisConnectionUtils:205 - Closing Redis Connection  
  12. 2016-08-02 20:43:16,612  INFO RedisServiceTest:54 - lpop value is 3  
  13. 2016-08-02 20:43:16,612 DEBUG RedisConnectionUtils:125 - Opening RedisConnection  
  14. 2016-08-02 20:43:16,613 DEBUG RedisConnectionUtils:205 - Closing Redis Connection  
  15. 2016-08-02 20:43:16,613  INFO RedisServiceTest:54 - lpop value is 4  
  16. 2016-08-02 20:43:16,613 DEBUG RedisConnectionUtils:125 - Opening RedisConnection  
  17. 2016-08-02 20:43:16,613 DEBUG RedisConnectionUtils:205 - Closing Redis Connection  
  18. 2016-08-02 20:43:16,613  INFO RedisServiceTest:54 - lpop value is 5  
  19. 2016-08-02 20:43:16,613 DEBUG RedisConnectionUtils:125 - Opening RedisConnection  
  20. 2016-08-02 20:43:16,614 DEBUG RedisConnectionUtils:205 - Closing Redis Connection  
  21. 2016-08-02 20:43:16,614  INFO RedisServiceTest:54 - lpop value is 6  
  22. 2016-08-02 20:43:16,614 DEBUG RedisConnectionUtils:125 - Opening RedisConnection  
  23. 2016-08-02 20:43:16,614 DEBUG RedisConnectionUtils:205 - Closing Redis Connection  
  24. 2016-08-02 20:43:16,618  INFO RedisServiceTest:54 - lpop value is 7  
  25. 2016-08-02 20:43:16,618 DEBUG RedisConnectionUtils:125 - Opening RedisConnection  
  26. 2016-08-02 20:43:16,618 DEBUG RedisConnectionUtils:205 - Closing Redis Connection  
  27. 2016-08-02 20:43:16,618  INFO RedisServiceTest:54 - lpop value is 8  
  28. 2016-08-02 20:43:16,618  INFO AbstractUnitTest:34 - unit test complete.  

这段输出是运行testLPopList得到的,这里面opening RedisConnection进行了9次,然后又Closing Redis Connection 9次,这是不是说每次执行redis操作都需要创建一个连接,操作完然后又关闭连接呢?实际上不是这样的,阅读源代码我们可以发现我们对redis的所有操作都是通过回调execute函数执行的,其代码如下:

[java]  view plain  copy
  1. public <T> T execute(RedisCallback<T> action, boolean exposeConnection) {  
  2.     return execute(action, exposeConnection, false);  
  3. }  
  4. // execute实现如下:  
  5. // org.springframework.data.redis.core.RedisTemplate<K, V> --- 最终实现  
  6. public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {  
  7.     Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");  
  8.     Assert.notNull(action, "Callback object must not be null");  
  9.     RedisConnectionFactory factory = getConnectionFactory();  
  10.     RedisConnection conn = null;  
  11.     try {  
  12.         if (enableTransactionSupport) {  
  13.             // only bind resources in case of potential transaction synchronization  
  14.             conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);  
  15.         } else {  
  16.             conn = RedisConnectionUtils.getConnection(factory);  
  17.         }  
  18.         boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);  
  19.         RedisConnection connToUse = preProcessConnection(conn, existingConnection);  
  20.         boolean pipelineStatus = connToUse.isPipelined();  
  21.         if (pipeline && !pipelineStatus) {  
  22.             connToUse.openPipeline();  
  23.         }  
  24.         RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));  
  25.         T result = action.doInRedis(connToExpose);  
  26.         // close pipeline  
  27.         if (pipeline && !pipelineStatus) {  
  28.             connToUse.closePipeline();  
  29.         }  
  30.         // TODO: any other connection processing?  
  31.         return postProcessResult(result, connToUse, existingConnection);  
  32.     } finally {  
  33.         if (!enableTransactionSupport) {  
  34.             RedisConnectionUtils.releaseConnection(conn, factory);  
  35.         }  
  36.     }  
  37. }  

这里面每次执行action.doInRedis(connToExpose)前都要调用RedisConnectionUtils.getConnection(factory);获得一个连接,进入RedisConnnectionUtils类中,getConnection(factory)最终调用的是doGetConnection(factory, true, false, enableTranactionSupport)这个函数。这个函数我们可以看下api文档,发现实际上并不是真的创建一个新的redis连接,它只是在connectFactory中获取一个连接,也就是从连接池中取出一个连接。当然如果connectFactory没有连接可用,此时如果allowCreate=true便会创建出一个新的连接,并且加入到connectFactory中。

基本上可以确定真实的情况是spring-data-redis已经帮我们封装了连接池管理,我们只需要调用一系列操作函数即可,这给操作redis带来了极大的方便。

猜你喜欢

转载自blog.csdn.net/howinfun/article/details/79405834