使用Spring Data Redis操作Redis(一) 很全面
Spring-Data-Redis專案(簡稱SDR)對Redis的Key-Value資料儲存操作提供了更高層次的抽象,類似於Spring Framework對JDBC支援一樣。
本文主要介紹Spring Data Redis的實際使用。
1.Spring Data Redis 1.5新特性
增加了Redis HyperLogLog命令PFADD,PFCOUNT,PFMERGE
可以使用Jackson基於RedisSerializer對Java型別序列化
使用PropertySource配置Redis Sentinel連線,目前僅Jedis客戶端支援
2.Spring Data Redis ?
Spring Data Redis使得在Spring應用中讀寫Redis資料庫更加容易。
Spring Data Redis提供了四種Redis服務的Java客戶端包的整合,分別是 Jedis ,JRedis , SRP and Lettuce
3.版本要求
Spring Data Redis1.2.x要求JDK1.6+,Spring Framwork3.2.8+
Key-Value儲存服務Redis 2.6.x+
4.搭建環境
本文假設已經安裝完成了Redis服務,併成功執行。
建立maven專案,新增依賴的Jar,本文主要使用jedis
<dependency>
<groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId><groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.5.0.RELEASE</version> </dependency><version>1.5.0.RELEASE</version> </dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.6.2</version>
</dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.6.2</version>
</dependency>
5.連線Redis服務
在Spring Data Redis中通過org.springframework.data.redis.connection包中的RedisConnection和RedisConnectionFactory類來獲取Redis連線。
5.1配置JedisConnectionFactory
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name="server" p:port="6379" />
</beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name="server" p:port="6379" />
</beans>
5.2配置Jredis,SRP,Lettuce的配置和上面配置類似,只需要配置對應的RedisConnectionFactory介面實現介面,它門位於如下包中:
5.3關於RedisConnectionFactory注意問題
上面四種聯結器並不是都支援Redis的所有特性,它們之間有差異性,如果呼叫方法在Connection API中不支援則丟擲“UnsupportedOperationException”異常,具體情況需要了解對應的Redis Java客戶端Jar的實現。
5.4關於Redis Sentinel(這裡暫稱為:哨兵)支援
Redis Sentinel監聽主服務,再主服務發生故障時能夠切換至從服務,將從服務升為主服務來保證故障恢復,使用該功能需要在JedisConnectionFactory設定RedisSentinelConfiguration屬性,目前Jedis對Redis Sentinel提供支援。
編碼方式如下:
public RedisConnectionFactory jedisConnectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() .master("mymaster")
.sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380);
return new JedisConnectionFactory(sentinelConfig);
}
RedisConnectionFactory jedisConnectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration() .master("mymaster")
.sentinel("127.0.0.1", 26379) .sentinel("127.0.0.1", 26380);
return new JedisConnectionFactory(sentinelConfig);
}
在Spring容器中配置:
<bean id="sentinelConfig"
class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
<constructor-arg name="master" value="mymaster" />
<constructor-arg name="sentinelHostAndPorts">
<set>
<value>192.168.88.153:26379</value>
<value>192.168.88.153:26380</value>
<value>192.168.88.153:26382</value>
</set>
</constructor-arg>
</bean>
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg ref="sentinelConfig" />
</bean>
<constructor-arg name="master" value="mymaster" />
<constructor-arg name="sentinelHostAndPorts">
<set>
<value>192.168.88.153:26379</value>
<value>192.168.88.153:26380</value>
<value>192.168.88.153:26382</value>
</set>
</constructor-arg>
</bean>
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg ref="sentinelConfig" />
</bean>
注意:在配置Redis的sentinel.conf檔案時注意使用外部可以訪問的ip地址,因為當redis-sentinel服務和redis-server在同一臺機器的時候,主服務發生變化時配置檔案中將主服務ip變為127.0.0.1,這樣外部就無法訪問了。如果應用程式,Redis服務在同一臺機器則不存在這樣的隱患,具體情況則更加實際的網路環境。
配置好之後,在例項化JedisConnectionFactory之後,可見如下日誌:
2015-4-1 17:29:30 redis.clients.jedis.JedisSentinelPool initSentinels
資訊: Trying to find master from available Sentinels...
2015-4-1 17:29:30 redis.clients.jedis.JedisSentinelPool initSentinels
資訊: Redis master running at 192.168.88.153:6384, starting Sentinel listeners...
2015-4-1 17:29:30 redis.clients.jedis.JedisSentinelPool initPool
資訊: Created JedisPool to master at 192.168.88.153:6384
:29:30 redis.clients.jedis.JedisSentinelPool initSentinels
資訊: Trying to find master from available Sentinels...
2015-4-1 17:29:30 redis.clients.jedis.JedisSentinelPool initSentinels
資訊: Redis master running at 192.168.88.153:6384, starting Sentinel listeners...
2015-4-1 17:29:30 redis.clients.jedis.JedisSentinelPool initPool
資訊: Created JedisPool to master at 192.168.88.153:6384
實驗環境中192.168.88.153:6384的Redis例項是主服務。
5.5下面通過一組程式碼展示具體使用
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestJedis {
public static ApplicationContext ctx;
public static JedisConnectionFactory jedisConnetionFactory;
public JedisConnection jedisConnection;
@SuppressWarnings("unchecked")
@BeforeClass
public static void setBeforeClass() {
ctx = new ClassPathXmlApplicationContext("spring-redis.xml");
jedisConnetionFactory = (JedisConnectionFactory) ctx
.getBean("jedisConnectionFactory");
}
@Before
public void setBefore() {
jedisConnection = jedisConnetionFactory.getConnection();
}
@After
public void setAfter() {
jedisConnection.close();
}
private void print(Collection<RedisServer> c) {
for (Iterator<RedisServer> iter = c.iterator(); iter.hasNext();) {
RedisServer rs = (RedisServer) iter.next();
System.out.println(rs.getHost() + ":" + rs.getPort());
}
}
// 簡單測試JedisConnection
@Ignore
@Test
public void test1() {
if (!jedisConnection.exists(new String("zz").getBytes())) {
jedisConnection.set(new String("zz").getBytes(),
new String("zz").getBytes());
}
}
@Ignore
@Test
public void test2() {
Set<byte[]> keys = jedisConnection.keys(new String("*").getBytes());
for (Iterator<byte[]> iter = keys.iterator(); iter.hasNext();) {
System.out.println(new String(iter.next()));
}
}
// 測試Sentinel
@Ignore
@Test
public void test3() throws InterruptedException {
if (jedisConnetionFactory.getSentinelConnection().isOpen()) {
Collection<RedisServer> c = jedisConnetionFactory
.getSentinelConnection().masters();
print(c);
RedisNode rn = new RedisNode("192.168.88.153", 6380);
rn.setName("mymaster");
c = jedisConnetionFactory.getSentinelConnection().slaves(rn);
print(c);
}
for (int i = 0; i < 1000; i++) {
jedisConnection.set(new String("k" + i).getBytes(), new String("v"
+ i).getBytes());
Thread.sleep(1000);
}
Set<byte[]> keys = jedisConnection.keys(new String("k*").getBytes());
Assert.assertEquals(1000, keys.size());
}
}
(MethodSorters.NAME_ASCENDING)
public class TestJedis {
public static ApplicationContext ctx;
public static JedisConnectionFactory jedisConnetionFactory;
public JedisConnection jedisConnection;
@SuppressWarnings("unchecked")
@BeforeClass
public static void setBeforeClass() {
ctx = new ClassPathXmlApplicationContext("spring-redis.xml");
jedisConnetionFactory = (JedisConnectionFactory) ctx
.getBean("jedisConnectionFactory");
}
@Before
public void setBefore() {
jedisConnection = jedisConnetionFactory.getConnection();
}
@After
public void setAfter() {
jedisConnection.close();
}
private void print(Collection<RedisServer> c) {
for (Iterator<RedisServer> iter = c.iterator(); iter.hasNext();) {
RedisServer rs = (RedisServer) iter.next();
System.out.println(rs.getHost() + ":" + rs.getPort());
}
}
// 簡單測試JedisConnection
@Ignore
@Test
public void test1() {
if (!jedisConnection.exists(new String("zz").getBytes())) {
jedisConnection.set(new String("zz").getBytes(),
new String("zz").getBytes());
}
}
@Ignore
@Test
public void test2() {
Set<byte[]> keys = jedisConnection.keys(new String("*").getBytes());
for (Iterator<byte[]> iter = keys.iterator(); iter.hasNext();) {
System.out.println(new String(iter.next()));
}
}
// 測試Sentinel
@Ignore
@Test
public void test3() throws InterruptedException {
if (jedisConnetionFactory.getSentinelConnection().isOpen()) {
Collection<RedisServer> c = jedisConnetionFactory
.getSentinelConnection().masters();
print(c);
RedisNode rn = new RedisNode("192.168.88.153", 6380);
rn.setName("mymaster");
c = jedisConnetionFactory.getSentinelConnection().slaves(rn);
print(c);
}
for (int i = 0; i < 1000; i++) {
jedisConnection.set(new String("k" + i).getBytes(), new String("v"
+ i).getBytes());
Thread.sleep(1000);
}
Set<byte[]> keys = jedisConnection.keys(new String("k*").getBytes());
Assert.assertEquals(1000, keys.size());
}
}
6.RedisTemplate支援
熟悉Spring的JdbcTemplate物件的話,應該大概能猜出來RedisTemplate的作用了,RedisTemplate物件對RedisConnection進行了封裝,它提供了連線管理,序列化等功能,它對Redis的互動進行了更高層次的抽象。另外還提供了Redis操作命令的操作檢視,這極大的方便和簡化了Redis的操作。
下表是具體的操作檢視介面類介紹:
Key型別操作 | |
ValueOperations | Redis String/Value 操作 |
ListOperations | Redis List 操作 |
SetOperations | Redis Set 操作 |
ZSetOperations | Redis Sort Set 操作 |
HashOperations | Redis Hash 操作 |
Value約束操作 | |
BoundValueOperations | Redis String/Value key 約束 |
BoundListOperations | Redis List key 約束 |
BoundSetOperations | Redis Set key 約束 |
BoundZSetOperations | Redis Sort Set key 約束 |
BoundHashOperations | Redis Hash key 約束 |
在org.springframework.data.redis.core包中對錶中的介面都提供了相應的預設實現。
6.1RedisSerializer
Spring Data Redis提供了對Key-Value的序列號,在使用RedisTemplate物件是預設使用JdkSerializationRedisSerializer實現。還提供了其它的序列化實現如:Jackson2JsonRedisSerializer,JacksonJsonRedisSerializer,GenericToStringSerializer,StringRedisSerializer,OxmSerializer。
另外使用者可以提供自己的序列化實現
6.2配置RedisTemplate
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory" />
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
這裡配置了RedisTemplate和StringRedisTemplate,不同之處在於StringRedisTemplate的Key-Value序列化使用的是StringRedisSerializer。使用StringRedisTemplate操作Redis之後的結果是讀友好的。
另外對Hash型別而言,還有對應的HashKey序列化(其對應於Hash型別的欄位名)。
6.3RedisTemplate的使用
// 測試RedisTemplate,自主處理key的可讀性(String序列號)
@Ignore
@Test
public void test4() {
String key = "spring";
ListOperations<String, String> lop = redisTemplate.opsForList();
RedisSerializer<String> serializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(serializer);
redisTemplate.setValueSerializer(serializer);
// rt.setDefaultSerializer(serializer);
lop.leftPush(key, "aaa");
lop.leftPush(key, "bbb");
long size = lop.size(key); // rt.boundListOps(key).size();
Assert.assertEquals(2, size);
}
// 測試便捷物件StringRedisTemplate
@Ignore
@Test
public void test5() {
ValueOperations<String, String> vop = stringRedisTemplate.opsForValue();
String key = "string_redis_template";
String v = "use StringRedisTemplate set k v";
vop.set(key, v);
String value = vop.get(key);
Assert.assertEquals(v, value);
}
@Ignore
@Test
public void test4() {
String key = "spring";
ListOperations<String, String> lop = redisTemplate.opsForList();
RedisSerializer<String> serializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(serializer);
redisTemplate.setValueSerializer(serializer);
// rt.setDefaultSerializer(serializer);
lop.leftPush(key, "aaa");
lop.leftPush(key, "bbb");
long size = lop.size(key); // rt.boundListOps(key).size();
Assert.assertEquals(2, size);
}
// 測試便捷物件StringRedisTemplate
@Ignore
@Test
public void test5() {
ValueOperations<String, String> vop = stringRedisTemplate.opsForValue();
String key = "string_redis_template";
String v = "use StringRedisTemplate set k v";
vop.set(key, v);
String value = vop.get(key);
Assert.assertEquals(v, value);
}
具體使用那種序列化策略則更加儲存的Key-Value內容做權衡即可。
通過RedisTemplate中的方法的引數RedisCallback回撥介面來獲取RedisConnection,進一步操作Redis,比如事務控制。需要注意的是如果使用StringRedisTemplate則返回的是StringRedisConnection物件。
測試RedisCallback程式碼示例:
// 測試Callback
@Ignore
@Test
public void test61() {
Long dbsize = (Long) stringRedisTemplate
.execute(new RedisCallback<Object>() {
@Override
public Long doInRedis(RedisConnection connection)
throws DataAccessException {
StringRedisConnection stringRedisConnection=(StringRedisConnection)connection;
return stringRedisConnection.dbSize();
}
});
System.out.println("dbsize:" + dbsize);
}
@Ignore
@Test
public void test61() {
Long dbsize = (Long) stringRedisTemplate
.execute(new RedisCallback<Object>() {
@Override
public Long doInRedis(RedisConnection connection)
throws DataAccessException {
StringRedisConnection stringRedisConnection=(StringRedisConnection)connection;
return stringRedisConnection.dbSize();
}
});
System.out.println("dbsize:" + dbsize);
}
測試SessionCallback程式碼示例:
@Test
public void test62() {
List<Object> txresult = stringRedisTemplate
.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations)
throws DataAccessException {
operations.multi();
operations.opsForHash().put("hkey", "multikey4",
"multivalue4");
operations.opsForHash().get("hkey", "k1");
return operations.exec();
}
});
for (Object o : txresult) {
System.out.println(o);
/**
* 0. false/true
* 1. v1
*/
}
}
public void test62() {
List<Object> txresult = stringRedisTemplate
.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations)
throws DataAccessException {
operations.multi();
operations.opsForHash().put("hkey", "multikey4",
"multivalue4");
operations.opsForHash().get("hkey", "k1");
return operations.exec();
}
});
for (Object o : txresult) {
System.out.println(o);
/**
* 0. false/true
* 1. v1
*/
}
}
說明:
-
在事務中的操作返回都是null,因此不能在execute中對操作的結果進行處理。比如這裡get("hkey","k1")的結果。
-
Hash操作中如果欄位存在則返回false(redis中是0),不存在返回true(redis中是1)與欄位對應的值是否更新無關聯。
-
exec()方法返回List<Object>中的物件對應事務中執行的每條命令的返回結果。
7.執行Lua指令碼
Spring Data Redis中執行Lua指令碼更加便利,下面示例展示使用RedisTemplate物件執行Lua指令碼。
// 測試Lua指令碼
@Ignore
@Test
public void test71() {
List<String> keys = new ArrayList<String>();
RedisScript<Long> script = new DefaultRedisScript<Long>(
"local size = redis.call('dbsize'); return size;", Long.class);
Long dbsize = stringRedisTemplate
.execute(script, keys, new Object[] {});
System.out.println("sha1:" + script.getSha1());
System.out.println("Lua:" + script.getScriptAsString());
System.out.println("dbsize:" + dbsize);
}
@Test
public void test72() {
DefaultRedisScript<Boolean> script = new DefaultRedisScript<Boolean>();
/**
* isexistskey.lua內容如下:
*
* return tonumber(redis.call("exists",KEYS[1])) == 1;
*/
script.setScriptSource(new ResourceScriptSource(new ClassPathResource(
"/isexistskey.lua")));
script.setResultType(Boolean.class);// Must Set
System.out.println("script:" + script.getScriptAsString());
Boolean isExist = stringRedisTemplate.execute(script,
Collections.singletonList("k2"), new Object[] {});
Assert.assertTrue(isExist);
}
@Ignore
@Test
public void test71() {
List<String> keys = new ArrayList<String>();
RedisScript<Long> script = new DefaultRedisScript<Long>(
"local size = redis.call('dbsize'); return size;", Long.class);
Long dbsize = stringRedisTemplate
.execute(script, keys, new Object[] {});
System.out.println("sha1:" + script.getSha1());
System.out.println("Lua:" + script.getScriptAsString());
System.out.println("dbsize:" + dbsize);
}
@Test
public void test72() {
DefaultRedisScript<Boolean> script = new DefaultRedisScript<Boolean>();
/**
* isexistskey.lua內容如下:
*
* return tonumber(redis.call("exists",KEYS[1])) == 1;
*/
script.setScriptSource(new ResourceScriptSource(new ClassPathResource(
"/isexistskey.lua")));
script.setResultType(Boolean.class);// Must Set
System.out.println("script:" + script.getScriptAsString());
Boolean isExist = stringRedisTemplate.execute(script,
Collections.singletonList("k2"), new Object[] {});
Assert.assertTrue(isExist);
}
8.支援類操作
在org.springframework.data.redis.support包中提供了各種可重用元件,這些元件可以應用到Redis儲存,如atomic計數,JDK集合,Redis的型別集合(RedisList,RedisSet等)
<span style="color:#000080"><redis:collection <span style="color:#008080">id</span>=<span style="color:#dd1144">"springList"</span> <span style="color:#008080">key</span>=<span style="color:#dd1144">"springlist"</span>
<span style="color:#008080">template</span>=<span style="color:#dd1144">"stringRedisTemplate"</span> <span style="color:#008080">type</span>=<span style="color:#dd1144">"LIST"</span> /></span>
@Ignore
@Test
public void test8() {
RedisAtomicInteger rai = new RedisAtomicInteger("redis:atomic",
jedisConnetionFactory);
System.out.println(rai.get());
}
// 測試Redis Collection
@Ignore
@Test
public void test9() {
@SuppressWarnings("unchecked")
RedisList<String> redisList = (RedisList<String>) ctx
.getBean("springList");
redisList.clear();
redisList.addFirst("china");
redisList.add("in");
redisList.add("go");
redisList.addLast("made");
System.out.println(redisList.getKey());
}
@Test
public void test8() {
RedisAtomicInteger rai = new RedisAtomicInteger("redis:atomic",
jedisConnetionFactory);
System.out.println(rai.get());
}
// 測試Redis Collection
@Ignore
@Test
public void test9() {
@SuppressWarnings("unchecked")
RedisList<String> redisList = (RedisList<String>) ctx
.getBean("springList");
redisList.clear();
redisList.addFirst("china");
redisList.add("in");
redisList.add("go");
redisList.addLast("made");
System.out.println(redisList.getKey());
}
贈送視訊如下:
不定期送視訊
1700G it視訊 等你來!
留言給他 即可贈送一套視訊
更新!!
元宵節鉅獻:2月12 號 贈送 多套花錢買的架構師視訊
趕緊去關注吧!!
注意:關注微訊號發訊息 給我們 不一定及時回覆 !!每天會抽時間統一回復兩三次!
注意:關注微訊號發訊息 給我們 不一定及時回覆 !!每天會抽時間統一回復兩三次!
注意:關注微訊號發訊息 給我們 不一定及時回覆 !!每天會抽時間統一回復兩三次!
注意:關注微訊號發訊息 給我們 不一定及時回覆 !!每天會抽時間統一回復兩三次!