spring-data-redis连接操作redis

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

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

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


	4.0.0

	com.snow
	redis-test
	0.0.1
	jar

	redis-test
	http://maven.apache.org

	
		UTF-8
		4.3.2.RELEASE
		1.7.12
		1.2.17
		4.12
		1.7.2.RELEASE
	

	
		
			org.springframework
			spring-context
			${spring.version}
		
		
			org.springframework
			spring-test
			${spring.version}
			test
		

		
			redis.clients
			jedis
			2.5.0
			jar
		
		
			org.springframework.data
			spring-data-redis
			${spring-data-redis.version}
			jar
		

		
		
			log4j
			log4j
			${log4j.version}
		
		
			org.slf4j
			slf4j-log4j12
			${slf4j.version}
		
		
			org.slf4j
			slf4j-api
			${slf4j.version}
		
		
			junit
			junit
			${junit.version}
			test
		
	

主要用到的jia包是spring-context、spring-data-redis、jedis以及日志打印相关的三个jar包

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

    
	
		   
		 
		  
		 
		 
		 	
		 	
		 
	
	     
   	
	
		
		
		
		
	 
    
    	 
    		  
    	
在配置文件中先配置一个连接池,然后配置一个connection工厂,最后配置bean redisTemplate,我们使用的class是StringRedisTemplate,从而决定我们后面的操作key及value都必须是String类型的。而通常我们希望将一个对象存入到redis中,这个时候可以将对象转为json字符串之后再存储,取出来的时候再将json字符串转换为对象。这种操作还是比较方便的,都有线程的jar包。除了StringRedisTemplate之外,我们还可以使用RedisTemplate类,这里暂不介绍。

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

redis.hostName=127.0.0.1
redis.port=6379
这个配置文件需要在application-contex.xml中加载,同时application-context.xml还需要加载application-context-redis.xml配置文件

	
		
		
			
				classpath:redis.properties
			
		
		
	
	
	  
接下来可以写个redis操作的接口

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 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);
    }

}
在该实现中继承了一个AbstractRedisDao,这个主要是提供getRedisTemplate()函数,使我们能够调用在application-context-redis.xml中配置的redisTemplate bean实例

public abstract class AbstractRedisDao {

    @Autowired
    protected RedisTemplate redisTemplate;

    // 设置redisTemplate
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public RedisTemplate getRedisTemplate() {
        return redisTemplate;
    }
}
主要的程序都已经写完了,接下来可以用Junit写个单元测试对接口测试下。

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);
    }
    

}
在这个测试类中,为了能够运行这些测试函数,需要对所有的bean进行实例化,这个过程是在 AbstractUnitTest中实现的,代码如下:

@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一下等待线程结束
    }

}
AbstractUnitTest类可以作为测试spring的一个通用类。


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

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 execute(RedisCallback action, boolean exposeConnection) {
    return execute(action, exposeConnection, false);
}
// execute实现如下:
// org.springframework.data.redis.core.RedisTemplate --- 最终实现
public  T execute(RedisCallback 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带来了极大的方便。

最后附上本文源代码:redis-test


你可能感兴趣的:(JAVA)