<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
最近在極客時間上面學習丁雪豐老師的《玩轉 Spring 全家桶》,其中講到存取Redis的方式,我專門把他們抽出來,在一起對比下,體驗一下三種方式開發上面的不同, 分別是這三種方式
開始之前我們需要有Redis安裝,我們採用本機Docker執行Redis, 主要命令如下
docker pull redis docker run --name my_redis -d -p 6379:6379 redis docker exec -it my_redis bash redis-cli
前面兩個命令是啟動redis docker, 後兩個是連線到docker, 在使用redis-cli 去檢視redis裡面的內容,主要檢視我們存在redis裡面的資料。
我們先從RedisTemplate開始,這個是最好理解的一種方式,我之前在工作中也使用過這種方式,先看程式碼範例
我們先定義一個POJO類
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class Book implements Serializable { private Long id; private String name; private String author; }
一個很簡單的BOOK類,三個欄位: id,name和author.
再來一個RedisTemplate的Bean
@Bean public RedisTemplate<String, Book> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Book> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; }
再定義一個使用這個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來儲存這個Book資訊,在上面的方法中查詢書名存不存在Redis中,如果存在就直接返回,如果不存在就去持久化儲存中找,找到就再通過Template寫入到Redis中, 這是快取的通用做法。 使用起來感覺很方便。
我們這裡為了簡單沒有使用持久化儲存,就寫死了幾條資料, 程式碼如下
@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(); } } }
我們呼叫 bookService.findOneBook("python")和bookService.findOneBook("apache kafka"); 來把資料寫入到換存中
我們來看下儲存在Redis的資料長什麼樣子。
127.0.0.1:6379> keys * 1) "xacxedx00x05tx00x04book" 127.0.0.1:6379> type "xacxedx00x05tx00x04book" hash 127.0.0.1:6379> hgetall "xacxedx00x05tx00x04book" 1) "xacxedx00x05tx00x06python" 2) "xacxedx00x05srx00&com.ken.redistemplatesample.model.Book=x19x96xfbx7fx7fxdaxbex02x00x03Lx00x06authortx00x12Ljava/lang/String;Lx00x02idtx00x10Ljava/lang/Long;Lx00x04nameqx00~x00x01xptx00x04lisisrx00x0ejava.lang.Long;x8bxe4x90xccx8f#xdfx02x00x01Jx00x05valuexrx00x10java.lang.Numberx86xacx95x1dx0bx94xe0x8bx02x00x00xpx00x00x00x00x00x00x00x02tx00x06python" 3) "xacxedx00x05tx00x0capache kafka" 4) "xacxedx00x05srx00&com.ken.redistemplatesample.model.Book=x19x96xfbx7fx7fxdaxbex02x00x03Lx00x06authortx00x12Ljava/lang/String;Lx00x02idtx00x10Ljava/lang/Long;Lx00x04nameqx00~x00x01xptx00bzhangsansrx00x0ejava.lang.Long;x8bxe4x90xccx8f#xdfx02x00x01Jx00x05valuexrx00x10java.lang.Numberx86xacx95x1dx0bx94xe0x8bx02x00x00xpx00x00x00x00x00x00x00x01tx00x0capache kafka"
我們可以看到資料被存在了key是“xacxedx00x05tx00x04book”的一個Hash表中, Hash裡面有兩條記錄。 大家發現一個問題沒有? 就是這個key不是我們想象的用“book”做key,而是多了一串16進位制的碼, 這是因為RedisTemplate使用了預設的JdkSerializationRedisSerializer 去序列化我們的key和value, 如果大家都用Java語言那沒有問題, 如果有人用Java語言寫,有人用別的語言讀,那就有問題,就像我開始的時候用hgetall "book"始終拿不到資料那樣。
RedisTemplate也提供了StringRedisTemplate來方便大家需要使用String來序列化redis裡面的資料。簡單看下程式碼
@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(); }
使用上就沒有那麼方便,你就得自己寫需要存的是哪個欄位,讀出來是哪個欄位。
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"
如上圖所示,使用使用者端讀出來看起來就比較清爽一些。也可以看到佔用的Size會小很多,我們這個例子相差7倍,如果是資料量大,這個還是比較大的浪費。
127.0.0.1:6379> keys * 1) "xacxedx00x05tx00x04book" 2) "string_book" 127.0.0.1:6379> memory usage "string_book" (integer) 104 127.0.0.1:6379> memory usage "xacxedx00x05tx00x04book" (integer) 712
我們知道使用JPA Repository來存取DataBase的時候,增刪改查那樣的操作能夠很方便的實現,基本就是定義個介面,程式碼都不用寫,Spring就幫我們完成了大部分的工作,那麼存取Redis是不是也可以這樣呢? 答案是肯定的,我們來看程式碼
首先我們還是定義一個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; }
這個類與我們上面template上面的類的區別就是我們加了兩個註解, 在類開頭加了
@RedisHash(value = "cache-book", timeToLive = 600)
在欄位上面加了@Id和@Indexed
定義一個Repository的介面
public interface CacheBookRepository extends CrudRepository<CacheBook, Long> { Optional<CacheBook> findOneByName(String name); }
再定義一個service和上面那個例子template一樣,快取中有就到快取中拿,沒有就到持久化儲存中找,並寫入快取
@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; } }
程式碼很簡單,簡單到不敢相信是真的。
還是一樣,呼叫這個方法,我們來看存在Redis裡面的資料
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"
哇,感覺存的內容有些多, 不用怕我們來看下各自存什麼資料
首先看最短的一個
127.0.0.1:6379> smembers repository-book 1) "1" 2) "2"
它裡面存的是我們的id所有的value, 可以用來判斷id是否存在
再來看
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"
這個是我們資料存放的地方
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"
另外兩個都是set, 存在在它們裡面的資料是索引資訊。
由此可以看出通過JPA Repository 的方式,程式碼很少,而且儲存的資料也很通用,個人覺得是比較理想的存取方法。
我們已經看了兩種方式,在存取的時候遵循這樣的模式:快取中有就從快取中返回,沒有就從持久化儲存中找,然後寫入快取,這部分程式碼我也不想自己寫,那麼Cache就是你的救星。
我們先看程式碼
我們這次使用記憶體資料庫H2作為持久化儲存, 放一個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; }
完全是和資料庫繫結的程式碼,和快取沒有任何關係
一個Repository來存取資料庫
public interface BookRepository extends JpaRepository<CacheBook, Long> { }
定義一個service來呼叫它
@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() { } }
這裡就比較關鍵了,在類上加上了註解
@CacheConfig(cacheNames = "cache-book")
在方法上面加上了Cacheable和CacheEvict, Cacheable這個方法就是用來實現邏輯,有就從快取中拿,沒有就從資料庫拿的,CacheEvict是呼叫這個方法的時候清除快取。
然後再啟動入口程式的地方加上註解
@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
這樣就可以了, 感覺就是通過設定下就把快取給完成了,非常的簡單
我們來看Redis中是怎麼存的
127.0.0.1:6379> keys * 1) "cache-book::SimpleKey []" 127.0.0.1:6379> get "cache-book::SimpleKey []" "xacxedx00x05srx00x13java.util.ArrayListxx81xd2x1dx99xc7ax9dx03x00x01Ix00x04sizexpx00x00x00x03wx04x00x00x00x03srx00(com.ken.rediscachesample.model.CacheBookxecxcbR=xe1Ux9bxf7x02x00x05Lx00x06authortx00x12Ljava/lang/String;Lx00ncreateTimetx00x10Ljava/util/Date;Lx00x02idtx00x10Ljava/lang/Long;Lx00x04nameqx00~x00x03Lx00nupdateTimeqx00~x00x04xptx00bzhangsansrx00x12java.sql.Timestamp&x18xd5xc8x01Sxbfex02x00x01Ix00x05nanosxrx00x0ejava.util.Datehjx81x01KYtx19x03x00x00xpwbx00x00x01x84xff]x85xb0xb-x81x80srx00x0ejava.lang.Long;x8bxe4x90xccx8f#xdfx02x00x01Jx00x05valuexrx00x10java.lang.Numberx86xacx95x1dx0bx94xe0x8bx02x00x00xpx00x00x00x00x00x00x00x01tx00x06pythonsqx00~x00bwbx00x00x01x84xff]x85xb0xb-x81x80sqx00~x00x02tx00x04lisisqx00~x00bwbx00x00x01x84xff]x85xb0xb<xbfxd8sqx00~x00x0bx00x00x00x00x00x00x00x02tx00x06hadoopsqx00~x00bwbx00x00x01x84xff]x85xb0xb<xbfxd8sqx00~x00x02tx00x06wangwusqx00~x00bwbx00x00x01x84xff]x85xb0xb<xbfxd8sqx00~x00x0bx00x00x00x00x00x00x00x03tx00x04javasqx00~x00bwbx00x00x01x84xff]x85xb0xb<xbfxd8x"
看到沒有,就是當成Redis裡面的String來存的, 如果資料量比較小,那是非常的方便,如果資料量大,這種方式就有些問題了。
我們看了這三種方式,這裡僅僅是做了個入門,每個裡面都有很多細節的地方需要去研究和使用,整體的感覺是要想使用的簡單,那麼儲存在Redis中的資料就要量少,量大後,就需要自己來客製化了,那基本上要用RedisTemplate來做一些工作。 這三個程式比較簡單,我也把它放在github上面了, https://github.com/dengkun39/redisdemo.git
到此這篇關於詳解Spring Boot 存取Redis的三種方式的文章就介紹到這了,更多相關Spring Boot 存取Redis內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45