原文转自:http://blog.youkuaiyun.com/wanghuiqi2008/article/details/52097381
Java连接redis的客户端有很多,其中比较常用的是Jedis. (参考:redis client)
spring-data-redis则是对Jedis进行了高度封装,使用起来非常方便。下面就以代码为例说明spring-data-redis的使用。
整个项目使用maven管理jar包,pom文件如下:
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.snow</groupId>
- <artifactId>redis-test</artifactId>
- <version>0.0.1</version>
- <packaging>jar</packaging>
- <name>redis-test</name>
- <url>http://maven.apache.org</url>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <spring.version>4.3.2.RELEASE</spring.version>
- <slf4j.version>1.7.12</slf4j.version>
- <log4j.version>1.2.17</log4j.version>
- <junit.version>4.12</junit.version>
- <spring-data-redis.version>1.7.2.RELEASE</spring-data-redis.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-test</artifactId>
- <version>${spring.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>redis.clients</groupId>
- <artifactId>jedis</artifactId>
- <version>2.5.0</version>
- <type>jar</type>
- </dependency>
- <dependency>
- <groupId>org.springframework.data</groupId>
- <artifactId>spring-data-redis</artifactId>
- <version>${spring-data-redis.version}</version>
- <type>jar</type>
- </dependency>
- <!-- log -->
- <dependency>
- <groupId>log4j</groupId>
- <artifactId>log4j</artifactId>
- <version>${log4j.version}</version>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-log4j12</artifactId>
- <version>${slf4j.version}</version>
- </dependency>
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- <version>${slf4j.version}</version>
- </dependency>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>${junit.version}</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- </project>
配置spring-data-redis如下application-context-redis.xml:
- <!-- 配置方法见 http://www.2cto.com/database/201311/254449.html -->
- <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
- <property name="maxTotal" value="500"/> <!-- 控制一个pool可分配多少个jedis实例 -->
- <property name="maxIdle" value="100"/><!-- 最大能够保持idel状态的对象数 -->
- <property name="maxWaitMillis" value="1000"/><!-- 表示当borrow一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException -->
- <property name="timeBetweenEvictionRunsMillis" value="30000"/><!-- 多长时间检查一次连接池中空闲的连接 -->
- <property name="minEvictableIdleTimeMillis" value="30000"/><!-- 空闲连接多长时间后会被收回, 单位是毫秒 -->
- <property name="testOnBorrow" value="true"/> <!-- 当调用borrow Object方法时,是否进行有效性检查 -->
- <property name="testOnReturn" value="true"/> <!-- 当调用return Object方法时,是否进行有效性检查 -->
- <property name="testWhileIdle" value="true"/>
- </bean>
- <span style="white-space:pre"> </span><!-- 直连master -->
- <bean id="jedisConnectionFactory"
- class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
- <constructor-arg ref="jedisPoolConfig" />
- <property name="hostName" value="${redis.hostName}" />
- <property name="port" value="${redis.port}" />
- <!-- <property name="password" value ="${redis.password}" /> -->
- </bean>
- <span style="white-space:pre"> </span><bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate" >
- <span style="white-space:pre"> </span><property name="connectionFactory" ref="jedisConnectionFactory" />
- <span style="white-space:pre"> </span></bean>
在application-context-redis.xml这个配置文件中还需要用到redis的host和port信息,我们可以配置一个文件properties文件如下:redis.properties
- redis.hostName=127.0.0.1
- redis.port=6379
- <bean id="dbPropertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="ignoreUnresolvablePlaceholders" value="true" />
- <property name="locations">
- <list>
- <value>classpath:redis.properties</value>
- </list>
- </property>
- <property name="fileEncoding" value="UTF-8" /><!-- 资源文件的编码 -->
- </bean>
- <import resource="classpath:application-context-redis.xml" />
- public interface RedisService {
- public void setStr(String key, String value);
- public String getStr(String key);
- public void rPushList(String key, String value);
- public String lPopList(String key);
- public void delKey(String key);
- }
- @Service(value = "redisService")
- public class RedisServiceImpl extends AbstractRedisDao<String, String> implements RedisService {
- @Override
- public void setStr(String key, String value) {
- getRedisTemplate().opsForValue().set(key, value);
- }
- @Override
- public String getStr(String key) {
- return getRedisTemplate().opsForValue().get(key);
- }
- @Override
- public void rPushList(String key, String value) {
- getRedisTemplate().opsForList().rightPush(key, value);
- }
- @Override
- public String lPopList(String key) {
- return getRedisTemplate().opsForList().leftPop(key);
- }
- @Override
- public void delKey(String key) {
- getRedisTemplate().delete(key);
- }
- }
- public abstract class AbstractRedisDao<K, V> {
- @Autowired
- protected RedisTemplate<K, V> redisTemplate;
- // 设置redisTemplate
- public void setRedisTemplate(RedisTemplate<K, V> redisTemplate) {
- this.redisTemplate = redisTemplate;
- }
- public RedisTemplate<K, V> getRedisTemplate() {
- return redisTemplate;
- }
- }
- public class RedisServiceTest extends AbstractUnitTest {
- private static final Logger logger = LoggerFactory.getLogger(RedisServiceTest.class);
- @Resource
- private RedisService redisService;
- @Test
- public void testSetStr() {
- String key = "test";
- String value = "valuetest";
- redisService.setStr(key, value);
- }
- @Test
- public void testGetStr() {
- String key = "test";
- String value = redisService.getStr(key);
- logger.info("The value is {}", value);
- }
- @Test
- public void testRPushList() {
- String key = "list";
- for (int i = 0; i < 10; i++) {
- redisService.rPushList(key, String.valueOf(i));
- }
- }
- @Test
- public void testLPopList() {
- String key = "list";
- for(int i = 0; i < 9; i++) {
- String value = redisService.lPopList(key);
- logger.info("lpop value is {}", value);
- }
- }
- @Test
- public void testDelKey() {
- String key = "list";
- redisService.delKey(key);
- }
- }
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration(locations = {"classpath:application-context.xml"})
- public abstract class AbstractUnitTest {
- private static final Logger logger = LoggerFactory.getLogger(AbstractUnitTest.class);
- // @Test
- // public void stub() {
- // logger.info("msg from abstract unit test, just ignore this.");
- // }
- @After
- public void teardown() throws InterruptedException {
- logger.info("unit test complete.");
- TimeUnit.MILLISECONDS.sleep(500);// 因为有些测试是需要异步插入操作记录的,sleep一下等待线程结束
- }
- }
主要的代码就这些了,运行下可以看到结果是没有问题的。下面我摘抄一段打印输出说明一个问题:
- 016-08-02 20:43:16,608 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
- 2016-08-02 20:43:16,609 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
- 2016-08-02 20:43:16,610 INFO RedisServiceTest:54 - lpop value is 0
- 2016-08-02 20:43:16,610 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
- 2016-08-02 20:43:16,611 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
- 2016-08-02 20:43:16,611 INFO RedisServiceTest:54 - lpop value is 1
- 2016-08-02 20:43:16,611 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
- 2016-08-02 20:43:16,611 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
- 2016-08-02 20:43:16,612 INFO RedisServiceTest:54 - lpop value is 2
- 2016-08-02 20:43:16,612 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
- 2016-08-02 20:43:16,612 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
- 2016-08-02 20:43:16,612 INFO RedisServiceTest:54 - lpop value is 3
- 2016-08-02 20:43:16,612 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
- 2016-08-02 20:43:16,613 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
- 2016-08-02 20:43:16,613 INFO RedisServiceTest:54 - lpop value is 4
- 2016-08-02 20:43:16,613 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
- 2016-08-02 20:43:16,613 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
- 2016-08-02 20:43:16,613 INFO RedisServiceTest:54 - lpop value is 5
- 2016-08-02 20:43:16,613 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
- 2016-08-02 20:43:16,614 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
- 2016-08-02 20:43:16,614 INFO RedisServiceTest:54 - lpop value is 6
- 2016-08-02 20:43:16,614 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
- 2016-08-02 20:43:16,614 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
- 2016-08-02 20:43:16,618 INFO RedisServiceTest:54 - lpop value is 7
- 2016-08-02 20:43:16,618 DEBUG RedisConnectionUtils:125 - Opening RedisConnection
- 2016-08-02 20:43:16,618 DEBUG RedisConnectionUtils:205 - Closing Redis Connection
- 2016-08-02 20:43:16,618 INFO RedisServiceTest:54 - lpop value is 8
- 2016-08-02 20:43:16,618 INFO AbstractUnitTest:34 - unit test complete.
这段输出是运行testLPopList得到的,这里面opening RedisConnection进行了9次,然后又Closing Redis Connection 9次,这是不是说每次执行redis操作都需要创建一个连接,操作完然后又关闭连接呢?实际上不是这样的,阅读源代码我们可以发现我们对redis的所有操作都是通过回调execute函数执行的,其代码如下:
- public <T> T execute(RedisCallback<T> action, boolean exposeConnection) {
- return execute(action, exposeConnection, false);
- }
- // execute实现如下:
- // org.springframework.data.redis.core.RedisTemplate<K, V> --- 最终实现
- public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
- Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
- Assert.notNull(action, "Callback object must not be null");
- RedisConnectionFactory factory = getConnectionFactory();
- RedisConnection conn = null;
- try {
- if (enableTransactionSupport) {
- // only bind resources in case of potential transaction synchronization
- conn = RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
- } else {
- conn = RedisConnectionUtils.getConnection(factory);
- }
- boolean existingConnection = TransactionSynchronizationManager.hasResource(factory);
- RedisConnection connToUse = preProcessConnection(conn, existingConnection);
- boolean pipelineStatus = connToUse.isPipelined();
- if (pipeline && !pipelineStatus) {
- connToUse.openPipeline();
- }
- RedisConnection connToExpose = (exposeConnection ? connToUse : createRedisConnectionProxy(connToUse));
- T result = action.doInRedis(connToExpose);
- // close pipeline
- if (pipeline && !pipelineStatus) {
- connToUse.closePipeline();
- }
- // TODO: any other connection processing?
- return postProcessResult(result, connToUse, existingConnection);
- } finally {
- if (!enableTransactionSupport) {
- RedisConnectionUtils.releaseConnection(conn, factory);
- }
- }
- }
这里面每次执行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带来了极大的方便。