Redis之序列化POJO


redis存儲方式有很多種,但是我個人覺得最好用的並非是String存儲類型,而是Hash存儲類型,如果在使用redis的時候單純的只使用到String存儲類型的話,我個人覺得完全體現不了redis的特性。

    redis 是一個key-value數據庫,但在我看來他並不是單純的key-value數據庫,因為他相對於其他同類型的nosql數據來講,redis提供了更多數據類型存儲格式。比如如果需要使用nosql類型的數據庫作為應用的緩存,我相信memcached比redis更適合,但是現實中往往很多人使用redis就僅僅只是使用String類型來做緩存。

    redis有hash類型的value存儲數據格式,不知道說到這里,大家有沒有想到我們傳統的關系型數據的數據存儲格式。

    其實我一直都想把redis做成一個應用與傳統關系型數據庫之間的媒介,從而實現盡量在高負載的環境下減少數據庫的IO,並實現數據庫持久化,且能夠更好的實現異步與傳統數據庫的數據同步。

    Hash

   常用命令:

    hget,hset,hgetall 等。

    應用場景:

    我們簡單舉個實例來描述下Hash的應用場景,比如我們要存儲一個用戶信息對象數據,包含以下信息:

    用戶ID為查找的key,存儲的value用戶對象包含姓名,年齡,生日等信息,如果用普通的key/value結構來存儲,主要有以下2種存儲方式:

第一種方式將用戶ID作為查找key,把其他信息封裝成一個對象以序列化的方式存儲,這種方式的缺點是,增加了序列化/反序列化的開銷,並且在需要修改其中一項信息時,需要把整個對象取回,並且修改操作需要對並發進行保護,引入CAS等復雜問題。

第二種方法是這個用戶信息對象有多少成員就存成多少個key-value對兒,用用戶ID+對應屬性的名稱作為唯一標識來取得對應屬性的值,雖然省去了序列化開銷和並發問題,但是用戶ID為重復存儲,如果存在大量這樣的數據,內存浪費還是非常可觀的。

那么Redis提供的Hash很好的解決了這個問題,Redis的Hash實際是內部存儲的Value為一個HashMap,並提供了直接存取這個Map成員的接口,如下圖:

也就是說,Key仍然是用戶ID, value是一個Map,這個Map的key是成員的屬性名,value是屬性值,這樣對數據的修改和存取都可以直接通過其內部Map的Key(Redis里稱內部Map的key為field), 也就是通過 key(用戶ID) + field(屬性標簽) 就可以操作對應屬性數據了,既不需要重復存儲數據,也不會帶來序列化和並發修改控制的問題。很好的解決了問題。

     hash操作對應數據庫的表數據存儲方式其實是一樣的,所以我們能夠很容易切入到redis中,並把傳統數據庫的表數據存入到redis的hash數據類型中。這里我需要跟大家分享的是如果快速的插入對象到redis中。

    網上很多資料上都有pojo存入到redis的hash中,但是大多操作都比較繁瑣,這里說的繁瑣是開發者需要做更多的事並不是代碼的繁瑣,我這里跟大家分享一個更簡單方便的方法:

/**  *  * @Title: set * @Description: 保存實體 * @return void  (region + ":H:" + setKey(region)) key標示。 * @throws  */ @SuppressWarnings({ "unchecked", "rawtypes" }) public void save(V v) { Map<K, V> map = new JacksonHashMapper(entityClass).toHash(v); redisTemplate.boundHashOps((K) (region + ":H:" + setKey(region)))  .putAll(map); };

    存儲之后格式是:

    獲取數據:

/**  *  * @Title: get * @Description: 根據redis 中對象的key 獲取對象信息  想請請看entries()方法,connection.hGetAll * @return E * @throws  */ @SuppressWarnings({ "unchecked", "rawtypes" }) public E get(String key) { Map<K, V> map = (Map<K, V>) redisTemplate.boundHashOps( (K) (region + ":H:" + key)).entries(); return (E) new JacksonHashMapper(entityClass).fromHash(map); }

    以上介紹的是單個pojo的數據存儲於獲取,但是如果是大批量的數據,我們會不會也只是多次執行同一個方法的方式去獲取對象集合的呢?答案是否定的,因為redis已經為我們提供了更好的處理方法Pipelined  管道操作,意指多個數據統一返回。

/**  *  * @Title: listConn * @Description: 管道批量獲取對象集合 傳入對象的key  官方提供方法 * @return List<E> * @throws  */ @SuppressWarnings("unchecked") public List<E> listPipe(final List<String> keys) { List<Object> result = redisTemplate.executePipelined(new RedisCallback<List<Object>>() { @Override public List<Object> doInRedis(RedisConnection connection) throws DataAccessException { for (int i = 0; i < keys.size(); i++) { byte[] key = redisTemplate.getStringSerializer().serialize( (region + ":H:"+keys.get(i))); connection.hGetAll(key); } return null; } }); return ((List<E>) result); } 自己實現的方法:executeConn @SuppressWarnings("unchecked") public List<E> listConn(final List<String> keys) { HashMapper<E, K, V> jhm = (HashMapper<E, K, V>) new JacksonHashMapper<E>(entityClass); List<Object> result = redisTemplate.executeConn(new RedisCallback<Map<byte[], byte[]>>() { @Override public Map<byte[], byte[]> doInRedis(RedisConnection connection) throws DataAccessException { Map<byte[], byte[]> map = new HashMap<byte[], byte[]>(); for (int i = 0; i < keys.size(); i++) { byte[] key = redisTemplate.getStringSerializer().serialize( (region + ":H:"+keys.get(i))); connection.hGetAll(key); } return null; } },jhm); return ((List<E>) result); }

    上述方法會返回一個結果集,但是我發現返回的結果集中序列化的屬性並沒有反序列化完全導致輸出出現亂碼,先看下springdataredis提供的反序列化方法:

@SuppressWarnings({ "unchecked", "rawtypes" }) private List<Object> deserializeMixedResults(List<Object> rawValues, RedisSerializer valueSerializer, RedisSerializer hashKeySerializer, RedisSerializer hashValueSerializer) { if (rawValues == null) { return null; } List<Object> values = new ArrayList<Object>(); for (Object rawValue : rawValues) { if (rawValue instanceof byte[] && valueSerializer != null) { values.add(valueSerializer.deserialize((byte[]) rawValue)); } else if (rawValue instanceof List) { // Lists are the only potential Collections of mixed values.... values.add(deserializeMixedResults((List) rawValue, valueSerializer, hashKeySerializer, hashValueSerializer)); } else if (rawValue instanceof Set && !(((Set) rawValue).isEmpty())) { values.add(deserializeSet((Set) rawValue, valueSerializer)); } else if (rawValue instanceof Map && !(((Map) rawValue).isEmpty()) && ((Map) rawValue).values().iterator().next() instanceof byte[]) { values.add(SerializationUtils.deserialize((Map) rawValue, hashKeySerializer, hashValueSerializer)); } else { values.add(rawValue); } } return values; } SerializationUtils類: public static <HK, HV> Map<HK, HV> deserialize(Map<byte[], byte[]> rawValues, RedisSerializer<HK> hashKeySerializer, RedisSerializer<HV> hashValueSerializer) { if (rawValues == null) { return null; } Map<HK, HV> map = new LinkedHashMap<HK, HV>(rawValues.size()); for (Map.Entry<byte[], byte[]> entry : rawValues.entrySet()) { // May want to deserialize only key or value HK key = hashKeySerializer != null ? (HK) hashKeySerializer.deserialize(entry.getKey()) : (HK) entry.getKey(); HV value = hashValueSerializer != null ? (HV) hashValueSerializer.deserialize(entry.getValue()) : (HV) entry .getValue(); map.put(key, value); } return map; }

    再看下我修改的之后的反序列話方法:

@SuppressWarnings({ "unchecked", "rawtypes" }) private List<Object> deserializeMixedResults(List<Object> rawValues,RedisSerializer hashKeySerializer, RedisSerializer hashValueSerializer,HashMapper<E, K, V> jhm) { if (rawValues == null) { return null; } List<Object> values = new ArrayList<Object>(); for (Object rawValue : rawValues) { Map<byte[], byte[]>  obj = (Map<byte[], byte[]>) rawValue; Map<K, V> map = new LinkedHashMap<K, V>(obj.size()); for (Map.Entry<byte[], byte[]> entry : obj.entrySet()) { map.put((K) hashKeySerializer.deserialize((entry.getKey())), (V) hashValueSerializer.deserialize((entry.getValue()))); } values.add(jhm.fromHash(map)); } return values; }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM