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>
主要用到的jia包是spring-context、spring-data-redis、jedis以及日誌打印相關的三個jar包
配置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>
在配置文件中先配置一個連接池,然後配置一個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配置文件
<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" />
接下來可以寫個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<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);
}
}
在該實現中繼承了一個AbstractRedisDao,這個主要是提供getRedisTemplate()函數,使我們能夠調用在application-context-redis.xml中配置的redisTemplate bean實例
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;
}
}
主要的程序都已經寫完了,接下來可以用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> 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帶來了極大的方便。
最後附上本文源代碼:redis-test