RedisTemplate訪問Redis的更好方法
開始準(zhǔn)備
開始之前我們需要有Redis安裝,我們采用本機(jī)Docker運(yùn)行Redis, 主要命令如下
docker pull redis docker run --name my_redis -d -p 6379:6379 redis docker exec -it my_redis bash redis-cli
前面兩個(gè)命令是啟動(dòng)redis docker, 后兩個(gè)是連接到docker, 在使用redis-cli 去查看redis里面的內(nèi)容,主要查看我們存在redis里面的數(shù)據(jù)。
RedisTemplate
我們先從RedisTemplate開始,這個(gè)是最好理解的一種方式,我之前在工作中也使用過這種方式,先看代碼示例 我們先定義一個(gè)POJO類
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class Book implements Serializable { private Long id; private String name; private String author; }
一個(gè)很簡(jiǎn)單的BOOK類,三個(gè)字段: id,name和author. 再來(lái)一個(gè)RedisTemplate的Bean
@Bean public RedisTemplate<String, Book> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Book> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; }
再定義一個(gè)使用這個(gè)RedisTemplate的Service類
public Optional<Book> findOneBook(String name) { HashOperations<String, String, Book> hashOperations = redisTemplate.opsForHash(); if (redisTemplate.hasKey(CACHE) && hashOperations.hasKey(CACHE, name)) { log.info("Get book {} from Redis.", name); return Optional.of(hashOperations.get(CACHE, name)); } Optional<Book> book = bookRepository.getBook(name); log.info("Book Found: {}", book); if (book.isPresent()) { log.info("Put book {} to Redis.", name); hashOperations.put(CACHE, name, book.get()); redisTemplate.expire(CACHE, 10, TimeUnit.MINUTES); } return book; }
我們使用Hash來(lái)存儲(chǔ)這個(gè)Book信息,在上面的方法中查找書名存不存在Redis中,如果存在就直接返回,如果不存在就去持久化存儲(chǔ)中找,找到就再通過Template寫入到Redis中, 這是緩存的通用做法。 使用起來(lái)感覺很方便。 我們這里為了簡(jiǎn)單沒有使用持久化存儲(chǔ),就硬編碼了幾條數(shù)據(jù), 代碼如下
@Repository public class BookRepository { Map<String, Book> bookMap = new HashMap<>(); public BookRepository(){ bookMap.put("apache kafka", Book.builder() .name("apache kafka").id(1L).author("zhangsan") .build()); bookMap.put("python", Book.builder() .name("python").id(2L).author("lisi") .build()); } public Optional<Book> getBook(String name){ if(bookMap.containsKey(name)){ return Optional.of(bookMap.get(name)); } else{ return Optional.empty(); } } }
我們調(diào)用 bookService.findOneBook("python")和bookService.findOneBook("apache kafka"); 來(lái)把數(shù)據(jù)寫入到換存中
我們來(lái)看下存儲(chǔ)在Redis的數(shù)據(jù)長(zhǎng)什么樣子。
127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\x04book" 127.0.0.1:6379> type "\xac\xed\x00\x05t\x00\x04book" hash 127.0.0.1:6379> hgetall "\xac\xed\x00\x05t\x00\x04book" 1) "\xac\xed\x00\x05t\x00\x06python" 2) "\xac\xed\x00\x05sr\x00&com.ken.redistemplatesample.model.Book=\x19\x96\xfb\x7f\x7f\xda\xbe\x02\x00\x03L\x00\x06authort\x00\x12Ljava/lang/String;L\x00\x02idt\x00\x10Ljava/lang/Long;L\x00\x04nameq\x00~\x00\x01xpt\x00\x04lisisr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x00\x00\x00\x00\x02t\x00\x06python" 3) "\xac\xed\x00\x05t\x00\x0capache kafka" 4) "\xac\xed\x00\x05sr\x00&com.ken.redistemplatesample.model.Book=\x19\x96\xfb\x7f\x7f\xda\xbe\x02\x00\x03L\x00\x06authort\x00\x12Ljava/lang/String;L\x00\x02idt\x00\x10Ljava/lang/Long;L\x00\x04nameq\x00~\x00\x01xpt\x00\bzhangsansr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x00\x00\x00\x00\x01t\x00\x0capache kafka"
我們可以看到數(shù)據(jù)被存在了key是“\xac\xed\x00\x05t\x00\x04book”的一個(gè)Hash表中, Hash里面有兩條記錄。 大家發(fā)現(xiàn)一個(gè)問題沒有? 就是這個(gè)key不是我們想象的用“book”做key,而是多了一串16進(jìn)制的碼, 這是因?yàn)镽edisTemplate使用了默認(rèn)的JdkSerializationRedisSerializer 去序列化我們的key和value, 如果大家都用Java語(yǔ)言那沒有問題, 如果有人用Java語(yǔ)言寫,有人用別的語(yǔ)言讀,那就有問題,就像我開始的時(shí)候用hgetall "book"始終拿不到數(shù)據(jù)那樣。 RedisTemplate也提供了StringRedisTemplate來(lái)方便大家需要使用String來(lái)序列化redis里面的數(shù)據(jù)。簡(jiǎn)單看下代碼
@Bean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } public Optional<String> getBookString(String name){ HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash(); if (stringRedisTemplate.hasKey(STRINGCACHE) && hashOperations.hasKey(STRINGCACHE, name)) { log.info("Get book {} from Redis.", name); return Optional.of(hashOperations.get(STRINGCACHE, name)); } Optional<Book> book = bookRepository.getBook(name); log.info("Book Found: {}", book); if (book.isPresent()) { log.info("Put book {} to Redis.", name); hashOperations.put(STRINGCACHE, name, book.get().getAuthor()); stringRedisTemplate.expire(STRINGCACHE, 10, TimeUnit.MINUTES); return Optional.of(book.get().getAuthor()); } return Optional.empty(); }
使用上就沒有那么方便,你就得自己寫需要存的是哪個(gè)字段,讀出來(lái)是哪個(gè)字段。
127.0.0.1:6379> keys * 1) "string_book" 127.0.0.1:6379> hgetall string_book 1) "python" 2) "lisi" 3) "apache kafka" 4) "zhangsan"
如上圖所示,使用客戶端讀出來(lái)看起來(lái)就比較清爽一些。也可以看到占用的Size會(huì)小很多,我們這個(gè)例子相差7倍,如果是數(shù)據(jù)量大,這個(gè)還是比較大的浪費(fèi)。
127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\x04book" 2) "string_book" 127.0.0.1:6379> memory usage "string_book" (integer) 104 127.0.0.1:6379> memory usage "\xac\xed\x00\x05t\x00\x04book" (integer) 712
JPA Repository
我們知道使用JPA Repository來(lái)訪問DataBase的時(shí)候,增刪改查那樣的操作能夠很方便的實(shí)現(xiàn),基本就是定義個(gè)接口,代碼都不用寫,Spring就幫我們完成了大部分的工作,那么訪問Redis是不是也可以這樣呢? 答案是肯定的,我們來(lái)看代碼 首先我們還是定義一個(gè)POJO
@RedisHash(value = "cache-book", timeToLive = 600) @Data @Builder @NoArgsConstructor @AllArgsConstructor public class CacheBook implements Serializable { @Id private Long userId; @Indexed private String name; private String author; }
這個(gè)類與我們上面template上面的類的區(qū)別就是我們加了兩個(gè)注解, 在類開頭加了 @RedisHash(value = "cache-book", timeToLive = 600) 在字段上面加了@Id和@Indexed 定義一個(gè)Repository的接口
public interface CacheBookRepository extends CrudRepository<CacheBook, Long> { Optional<CacheBook> findOneByName(String name); }
再定義一個(gè)service和上面那個(gè)例子template一樣,緩存中有就到緩存中拿,沒有就到持久化存儲(chǔ)中找,并寫入緩存
@Slf4j @Service public class BookService { private static final String CACHE = "repository-book"; @Autowired private CacheBookRepository cacheRepository; @Autowired private BookRepository bookRepository; public Optional<CacheBook> findOneBook(String name) { Optional<CacheBook> optionalCacheBook = cacheRepository.findOneByName(name); if(!optionalCacheBook.isPresent()) { Optional<CacheBook> book = bookRepository.getBook(name); log.info("Book Found: {}", book); if (book.isPresent()) { log.info("Put book {} to Redis.", name); cacheRepository.save(book.get()); } return book; } return optionalCacheBook; } }
代碼很簡(jiǎn)單,簡(jiǎn)單到不敢相信是真的。 還是一樣,調(diào)用這個(gè)方法,我們來(lái)看存在Redis里面的數(shù)據(jù)
127.0.0.1:6379> keys * 1) "repository-book:2" 2) "repository-book:2:idx" 3) "repository-book" 4) "repository-book:name:apache kafka" 5) "repository-book:name:python" 6) "repository-book:1:idx" 7) "repository-book:1"
哇,感覺存的內(nèi)容有些多, 不用怕我們來(lái)看下各自存什么數(shù)據(jù) 首先看最短的一個(gè)
127.0.0.1:6379> smembers repository-book 1) "1" 2) "2"
它里面存的是我們的id所有的value, 可以用來(lái)判斷id是否存在 再來(lái)看
127.0.0.1:6379> hgetall repository-book:2 1) "_class" 2) "com.ken.redisrepositorysample.model.CacheBook" 3) "author" 4) "lisi" 5) "name" 6) "python" 7) "userId" 8) "2"
這個(gè)是我們數(shù)據(jù)存放的地方
127.0.0.1:6379> smembers repository-book:1:idx 1) "repository-book:name:apache kafka" 127.0.0.1:6379> smembers "repository-book:name:apache kafka" 1) "1"
另外兩個(gè)都是set, 存在在它們里面的數(shù)據(jù)是索引信息。 由此可以看出通過JPA Repository 的方式,代碼很少,而且存儲(chǔ)的數(shù)據(jù)也很通用,個(gè)人覺得是比較理想的訪問方法。
Cache
我們已經(jīng)看了兩種方式,在訪問的時(shí)候遵循這樣的模式:緩存中有就從緩存中返回,沒有就從持久化存儲(chǔ)中找,然后寫入緩存,這部分代碼我也不想自己寫,那么Cache就是你的救星。 我們先看代碼 我們這次使用內(nèi)存數(shù)據(jù)庫(kù)H2作為持久化存儲(chǔ), 放一個(gè)schema.sql在resouces下面
drop table t_book if exists; create table t_book ( id bigint auto_increment, create_time timestamp, update_time timestamp, name varchar(255), author varchar(200), primary key (id) ); insert into t_book (name, author, create_time, update_time) values ('python', 'zhangsan', now(), now()); insert into t_book (name, author, create_time, update_time) values ('hadoop', 'lisi', now(), now()); insert into t_book (name, author, create_time, update_time) values ('java', 'wangwu', now(), now());
然后定義POJO
@Entity @Table(name = "T_BOOK") @Data @Builder @NoArgsConstructor @AllArgsConstructor public class CacheBook implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String author; @Column(updatable = false) @CreationTimestamp private Date createTime; @UpdateTimestamp private Date updateTime; }
完全是和數(shù)據(jù)庫(kù)綁定的代碼,和緩存沒有任何關(guān)系 一個(gè)Repository來(lái)訪問數(shù)據(jù)庫(kù)
public interface BookRepository extends JpaRepository<CacheBook, Long> { }
定義一個(gè)service來(lái)調(diào)用它
@Slf4j @Service @CacheConfig(cacheNames = "cache-book") public class BookService { @Autowired private BookRepository bookRepository; @Cacheable public List<CacheBook> findAllCoffee() { return bookRepository.findAll(); } @CacheEvict public void reloadCoffee() { } }
這里就比較關(guān)鍵了,在類上加上了注解 @CacheConfig(cacheNames = "cache-book") 在方法上面加上了Cacheable和CacheEvict, Cacheable這個(gè)方法就是用來(lái)實(shí)現(xiàn)邏輯,有就從緩存中拿,沒有就從數(shù)據(jù)庫(kù)拿的,CacheEvict是調(diào)用這個(gè)方法的時(shí)候清除緩存。 然后再啟動(dòng)入口程序的地方加上注解 @EnableJpaRepositories @EnableCaching(proxyTargetClass = true) 在配置文件application.properties中加上
spring.jpa.hibernate.ddl-auto=none spring.jpa.properties.hibernate.show_sql=true spring.jpa.properties.hibernate.format_sql=true management.endpoints.web.exposure.include=* spring.cache.type=redis spring.cache.cache-names=cache-book spring.cache.redis.time-to-live=600000 spring.cache.redis.cache-null-values=false spring.redis.host=localhost
這樣就可以了, 感覺就是通過配置下就把緩存給完成了,非常的簡(jiǎn)單 我們來(lái)看Redis中是怎么存的
127.0.0.1:6379> keys * 1) "cache-book::SimpleKey []" 127.0.0.1:6379> get "cache-book::SimpleKey []" "\xac\xed\x00\x05sr\x00\x13java.util.ArrayListx\x81\xd2\x1d\x99\xc7a\x9d\x03\x00\x01I\x00\x04sizexp\x00\x00\x00\x03w\x04\x00\x00\x00\x03sr\x00(com.ken.rediscachesample.model.CacheBook\xec\xcbR=\xe1U\x9b\xf7\x02\x00\x05L\x00\x06authort\x00\x12Ljava/lang/String;L\x00\ncreateTimet\x00\x10Ljava/util/Date;L\x00\x02idt\x00\x10Ljava/lang/Long;L\x00\x04nameq\x00~\x00\x03L\x00\nupdateTimeq\x00~\x00\x04xpt\x00\bzhangsansr\x00\x12java.sql.Timestamp&\x18\xd5\xc8\x01S\xbfe\x02\x00\x01I\x00\x05nanosxr\x00\x0ejava.util.Datehj\x81\x01KYt\x19\x03\x00\x00xpw\b\x00\x00\x01\x84\xff]\x85\xb0x\b-\x81\x80sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x00\x00\x00\x00\x01t\x00\x06pythonsq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b-\x81\x80sq\x00~\x00\x02t\x00\x04lisisq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b<\xbf\xd8sq\x00~\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x02t\x00\x06hadoopsq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b<\xbf\xd8sq\x00~\x00\x02t\x00\x06wangwusq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b<\xbf\xd8sq\x00~\x00\x0b\x00\x00\x00\x00\x00\x00\x00\x03t\x00\x04javasq\x00~\x00\bw\b\x00\x00\x01\x84\xff]\x85\xb0x\b<\xbf\xd8x"
看到?jīng)]有,就是當(dāng)成Redis里面的String來(lái)存的, 如果數(shù)據(jù)量比較小,那是非常的方便,如果數(shù)據(jù)量大,這種方式就有些問題了。
總結(jié)
我們看了這三種方式,這里僅僅是做了個(gè)入門,每個(gè)里面都有很多細(xì)節(jié)的地方需要去研究和使用,整體的感覺是要想使用的簡(jiǎn)單,那么存儲(chǔ)在Redis中的數(shù)據(jù)就要量少,量大后,就需要自己來(lái)定制了,那基本上要用RedisTemplate來(lái)做一些工作。
以上就是RedisTemplate訪問Redis的更好方法的詳細(xì)內(nèi)容,更多關(guān)于RedisTemplate訪問Redis的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在ssm項(xiàng)目中使用redis緩存查詢數(shù)據(jù)的方法
本文主要簡(jiǎn)單的使用Java代碼進(jìn)行redis緩存,即在查詢的時(shí)候先在service層從redis緩存中獲取數(shù)據(jù)。如果大家對(duì)在ssm項(xiàng)目中使用redis緩存查詢數(shù)據(jù)的相關(guān)知識(shí)感興趣的朋友跟隨腳本之家小編一起看看吧2018-03-03Redis實(shí)戰(zhàn)之Jedis使用技巧詳解
Jedis?是老牌的?Redis?的?Java?客戶端,提供了比較全面的?Redis?命令的操作支持,也是目前使用最廣泛的客戶端。這篇文章主要為大家詳細(xì)介紹了Jedis的使用技巧,需要的可以參考一下2022-12-12Redis?異常?read?error?on?connection?的解決方案
這篇文章主要介紹了Redis異常read?error?on?connection的解決方案,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-08-08分布式鎖為什么要選擇Zookeeper而不是Redis?看完這篇你就明白了
Zookeeper的機(jī)制可以保證分布式鎖實(shí)現(xiàn)業(yè)務(wù)代碼簡(jiǎn)單,成本低,Redis如果要解決分布式鎖的問題,對(duì)于一些復(fù)雜的情況,很難解決,成本較高,這篇文章重點(diǎn)給大家介紹分布式鎖選擇Zookeeper 而不是Redis的理由,一起看看吧2021-05-05redis中hash數(shù)據(jù)結(jié)構(gòu)及說(shuō)明
這篇文章主要介紹了redis中hash數(shù)據(jù)結(jié)構(gòu)及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01基于Redis實(shí)現(xiàn)每日登錄失敗次數(shù)限制
這篇文章主要介紹了通過redis實(shí)現(xiàn)每日登錄失敗次數(shù)限制的問題,通過redis記錄登錄失敗的次數(shù),以用戶的username為key,本文給出了實(shí)例代碼,需要的朋友可以參考下2019-08-08