Spring Boot中除了對常用的關係型資料庫提供了優秀的自動化支持之外,對於很多NoSQL資料庫一樣提供了自動化配置的支持,包括:Redis, MongoDB, 等。 Redis簡單介紹 Redis是Redis是Remote DIctionary Server的縮寫,是目前業界使用最廣泛的記憶體數 ...
Spring Boot中除了對常用的關係型資料庫提供了優秀的自動化支持之外,對於很多NoSQL資料庫一樣提供了自動化配置的支持,包括:Redis, MongoDB, 等。
Redis簡單介紹
Redis是Redis是Remote DIctionary Server的縮寫,是目前業界使用最廣泛的記憶體數據存儲。相比memcached,Redis支持更豐富的數據結構(Memcached完全基於記憶體,而Redis具有持久化保存特性,Redis可以將數據寫入到磁碟中(以位元組(0101這樣的二進位數據)的形式寫入的),例如hashes, lists, sets等,同時支持數據持久化。除此之外,Redis還提供一些類資料庫的特性,比如事務,HA,主從庫。可以說Redis兼具了緩存系統和資料庫的一些特性,因此有著豐富的應用場景。
Spring boot集成Redis
添加依賴
Spring Boot提供的數據訪問框架Spring Data Redis基於Jedis。可以通過引入spring-boot-starter-redis來配置依賴關係。
<!-- 添加Spring-boot-starter-redis依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency>
對Redis進行配置,修改配置文件 application.properties
# REDIS (RedisProperties) # Redis資料庫索引(預設為0) spring.redis.database=0 # Redis伺服器地址 spring.redis.host=localhost # Redis伺服器連接埠 spring.redis.port=6379 # Redis伺服器連接密碼(預設為空) spring.redis.password=qpc_redis # 連接池最大連接數(使用負值表示沒有限制) spring.redis.pool.max-active=8 # 連接池最大阻塞等待時間(使用負值表示沒有限制) spring.redis.pool.max-wait=-1 # 連接池中的最大空閑連接 spring.redis.pool.max-idle=8 # 連接池中的最小空閑連接 spring.redis.pool.min-idle=0 # 連接超時時間(毫秒) spring.redis.timeout=0
其中spring.redis.database的配置通常使用0即可,Redis在配置的時候可以設置資料庫數量,預設為16,可以理解為資料庫的schema.
使用Redis
使用自動配置的StringRedisTemplate對象進行Redis讀寫操作。
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(Application.class) public class ApplicationTest { private static final Logger LOG = Logger.getLogger(RedisApplicationTest.class); @Autowired private StringRedisTemplate stringRedisTemplate; //@Autowired //private RedisTemplate<Serializable, Object> redisTemplate; //@Autowired //private RedisService redisService; @Test public void testStringWithRedis(){ stringRedisTemplate.opsForValue().set("name", "guanguan"); String val = stringRedisTemplate.opsForValue().get("name"); Assert.assertEquals("guanguan", val); } }
當然,根據StringRedisTemplate對象命名我們可以知道該對象支持String類型,但是在實際的應用中,我們可能需要存入Object對象。那該怎麼存儲呢。聰明的你,肯定立刻想到了,直接把對象轉成json格式字元串,不就可以存儲了嘛。這裡我使用jackson依賴轉換成json數據。
首先添加jackson依賴
<!-- java json解析依賴 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.3</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.3</version> </dependency>
實現json轉換工具類
public class JsonUtil { private static ObjectMapper objectMapper = new ObjectMapper(); public static String convertObj2String(Object object) { String s = null; try { s = objectMapper.writeValueAsString(object); } catch (JsonProcessingException e) { e.printStackTrace(); } return s; } public static <T> T convertString2Obj(String s, Class<T> clazz) { T t = null; try { t = objectMapper.readValue(s, clazz); } catch (IOException e) { e.printStackTrace(); } return t; } }
我們知道,RedisTemplate 是 redis 模塊的核心類,是對 redis 操作的較高抽象具有豐富的特性。他關註的是序列化和連接管理,線程安全,提供瞭如下操作介面:
HashOperations
HyperLogLogOperations
ListOperations
SetOperations
ValueOperations
ZSetOperations
那我們就實現一個通用的RedisService類完成Redis的讀寫操作
@Service public class RedisService { @Autowired private StringRedisTemplate redisTemplate; /** * 一周有多少秒 */ private static final long WEEK_SECONDS = 7 * 24 * 60 * 60; /** * 將 key,value 存放到redis資料庫中,預設設置過期時間為一周 * * @param key * @param value */ public void set(String key, Object value) { redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), WEEK_SECONDS, TimeUnit.SECONDS); } /** * 將 key,value 存放到redis資料庫中,設置過期時間單位是秒 * * @param key * @param value * @param expireTime */ public void set(String key, Object value, long expireTime) { redisTemplate.opsForValue().set(key, JsonUtil.convertObj2String(value), expireTime, TimeUnit.SECONDS); } /** * 判斷 key 是否在 redis 資料庫中 * * @param key * @return */ public boolean exists(final String key) { return redisTemplate.hasKey(key); } /** * 獲取與 key 對應的對象 * @param key * @param clazz 目標對象類型 * @param <T> * @return */ public <T> T get(String key, Class<T> clazz) { String s = get(key); if (s == null) { return null; } return JsonUtil.convertString2Obj(s, clazz); } /** * 獲取 key 對應的字元串 * @param key * @return */ public String get(String key) { return redisTemplate.opsForValue().get(key); } /** * 刪除 key 對應的 value * @param key */ public void delete(String key) { redisTemplate.delete(key); } }
新建一個User對象
public class User implements Serializable{ /** * */ private static final long serialVersionUID = 3456232569272497427L; private int id; private String name; private int age; public User() { } public User(int id, String name, int age) { super(); this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", age=" + age + "]"; } }
新建測試類
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(Application.class) public class ApplicationTest { private static final Logger LOG = Logger.getLogger(ApplicationTest.class); @Autowired private RedisService redisService; @Test public void testRedisService(){ User user3 = new User(2,"xiaoxiaoping",16); redisService.set("user3", user3, 1000*60l); User userV3 = redisService.get("user3",User.class); LOG.info("userV3====="+userV3.toString()); } }
測試結果
通過使用StringRedisTemplate對象完全實現了對Object對象的存儲.通過redis-cli.exe可以查看到我們存儲的Object對象是json格式字元串,但是當某個對象很大時,這個json字元串會很冗長,那我們有沒有其他方式實現呢。如果有使用過spring-data-redis的開發者一定熟悉RedisTemplate<K, V>介面,StringRedisTemplate就相當於RedisTemplate<String, String>的實現。沒有使用過,可以先看下StringRedisTemplate類源碼。
public class StringRedisTemplate extends RedisTemplate<String, String> { /** * Constructs a new <code>StringRedisTemplate</code> instance. {@link #setConnectionFactory(RedisConnectionFactory)} * and {@link #afterPropertiesSet()} still need to be called. */ public StringRedisTemplate() { RedisSerializer<String> stringSerializer = new StringRedisSerializer(); setKeySerializer(stringSerializer); setValueSerializer(stringSerializer); setHashKeySerializer(stringSerializer); setHashValueSerializer(stringSerializer); } /** * Constructs a new <code>StringRedisTemplate</code> instance ready to be used. * * @param connectionFactory connection factory for creating new connections */ public StringRedisTemplate(RedisConnectionFactory connectionFactory) { this(); setConnectionFactory(connectionFactory); afterPropertiesSet(); } protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) { return new DefaultStringRedisConnection(connection); } }
從源碼分析,我們可以看出StringRedisTemplate實現RedisTemplate<K, V>介面,那我們完全可以模仿寫一個RedisTemplate<Serializable, Object>模板類。但是Spring boot不支直接使用,所以根據源碼,我們需要實現一個RedisSerializer<T>將來對傳入對象進行序列化和反序列化。這個實現類ObjectRedisSerializer可以參考StringRedisSerializer類。另外,根據源碼,可以發現,Redis預設的序列化方式為JdkSerializationRedisSerializer ,利用JDK的序列化和反序列化,持久化就是以位元組(0101這樣的二進位數據)的形式寫入的。
Redis存儲對象實現如下
添加ObjectRedisSerializer實現類,需要實現RedisSerializer<T>介面。
/** * 實現Redis對象的序列化介面 * 參考:JdkSerializationRedisSerializer源碼 * */ public class ObjectRedisSerializer implements RedisSerializer<Object>{ private static final Logger LOG = Logger.getLogger(ObjectRedisSerializer.class); /** * 定義序列化和發序列化轉化類 */ private Converter<Object, byte[]> serializer = new SerializingConverter(); private Converter<byte[], Object> deserializer = new DeserializingConverter(); /** * 定義轉換空位元組數組 */ private static final byte[] EMPTY_ARRAY = new byte[0]; @Override public byte[] serialize(Object obj) throws SerializationException { byte[] byteArray = null; if (null == obj) { LOG.warn("Redis待序列化的對象為空."); byteArray = EMPTY_ARRAY; } else { try { byteArray = serializer.convert(obj); } catch (Exception e) { LOG.error("Redis序列化對象失敗,異常:"+e.getMessage()); byteArray = EMPTY_ARRAY; } } return byteArray; } @Override public Object deserialize(byte[] datas) throws SerializationException { Object obj = null; if(isNullOrEmpty(datas)){ LOG.warn("Redis待反序列化的對象為空."); }else{ try { obj = deserializer.convert(datas); } catch (Exception e) { LOG.error("Redis反序列化對象失敗,異常:"+e.getMessage()); } } return obj; } private boolean isNullOrEmpty(byte[] datas){ return (null == datas)|| (datas.length == 0); } }
創建RedisConfig配置類,將RedisTemplate的setValueSerializer設置成ObjectRedisSerializer轉換類。
@Configuration public class RedisConfig { // /** // * 連接 redis 需要 RedisConnection 和 RedisConnectionFactory, // * RedisConnection 是通過 RedisConnectionFactory 進行創建 // * RedisConnection 提供較低級的數據操作 (byte arrays) // */ // @Bean // RedisConnectionFactory initJedisConnectionFactory(){ // //在這裡設置redis連接對象配置 // return new JedisConnectionFactory(); // } /** * 配置RedisTemplate實例 * @param factory * @return */ @Bean public RedisTemplate<Serializable, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<Serializable, Object> template = new RedisTemplate<Serializable, Object>(); template.setConnectionFactory(connectionFactory); template.afterPropertiesSet(); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new ObjectRedisSerializer()); return template; } }
需要註意幾點:
在添加RedisConfig配置時,因為連接redis需要RedisConnection和RedisConnectionFactory,RedisConnection是通過RedisConnectionFactory進行創建若註入JedisConnnectionFactory,如果我們Redis設置了密碼,在重新註入RedisConnectionFactory(如上註釋代碼),就會報錯如下:
org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192) Caused by: redis.clients.jedis.exceptions.JedisDataException: NOAUTH Authentication required. at redis.clients.jedis.Protocol.processError(Protocol.java:117) at redis.clients.jedis.Protocol.process(Protocol.java:151) at redis.clients.jedis.Protocol.read(Protocol.java:205) at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:297) at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:196) at redis.clients.jedis.BinaryJedis.set(BinaryJedis.java:126) at org.springframework.data.redis.connection.jedis.JedisConnection.set(JedisConnection.java:1136) ... 36 more
根據StringRedisTemplate源碼,在註入RedisTemplate<Serializable, Object>直接使用預設的連接對象即可。設置如下代碼:
template.setConnectionFactory(connectionFactory);
template.afterPropertiesSet();
或者我們註入RedisConnectionFactory設置連接屬性應該也是可以的,有興趣可以嘗試下。
創建測試類
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(Application.class) public class ApplicationTest { private static final Logger LOG = Logger.getLogger(ApplicationTest.class); @Autowired private RedisTemplate<Serializable, Object> redisTemplate; @Test public void testObjectWithRedis(){ User user1 = new User(1,"guanguan",18); redisTemplate.opsForValue().set("user1", user1); User userV1 = (User)redisTemplate.opsForValue().get("user1"); LOG.info("userV1====="+userV1.toString()); User user2 = new User(2,"xiaoyan",16); redisTemplate.opsForValue().set("user2", user2); User userV2 = (User)redisTemplate.opsForValue().get("user2"); LOG.info("user2====="+userV2.toString()); User user3 = new User(3,"xiaoxiaoping",18); redisTemplate.opsForValue().set("user3", user3); User userV3 = (User)redisTemplate.opsForValue().get("user3"); LOG.info("userV3====="+userV3.toString()); } }
測試結果:
可以看出,是以位元組方式存儲的。