1 Springboot中使用redis,自動快取、更新、刪除
第一篇記錄一下在springboot中,redis的基礎用法,自動快取新增的資料,自動修改及刪除。
在本機安裝好mysql和redis。新建一個springboot的web專案,在新建專案時勾選redis,mysql。
pom檔案如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.tianyalei</groupId> <artifactId>demo0</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo0</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.18</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
資料庫連線池用的druid,裡面使用了spring-boot-starter-data-redis,有這一個依賴就夠了,系統就能識別並應用redis了。dao工具用的jpa,預設集成了hibernate。
下面配置一下application.yml。如下:
spring: jpa: database: mysql show-sql: true hibernate: ddl-auto: update datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/tx2 username: root password:
配置一下show-sql為true,目的是看看查表時的快取效果。至於redis的ip,埠什麼的都不用配,系統有個預設值,等會看看就知道了。
建立個java bean。
package com.tianyalei.domain; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import java.io.Serializable; /** * Created by wuwf on 17/4/21. */ @Entity public class Post implements Serializable{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private String content; private Integer weight; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Integer getWeight() { return weight; } public void setWeight(Integer weight) { this.weight = weight; } }
建立個repository
package com.tianyalei.repository;
import com.tianyalei.domain.Post;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.transaction.annotation.Transactional;
/**
* Created by wuwf on 17/4/20.
*/
@CacheConfig(cacheNames = "post")
public interface PostRepository extends PagingAndSortingRepository<Post, Integer> {
@Cacheable(key = "#p0")
Post findById(int id);
/**
* 新增或修改時
*/
@CachePut(key = "#p0.id")
@Override
Post save(Post post);
@Transactional
@Modifying
int deleteById(int id);
}
這個裡面有個CacheConfig,配置了cacheNames。
我在findById方法時加了個@Cacheable(key= "#p0"),#p0代表第一個引數,也就是id。這句話加上之後,當你在呼叫findById時,就會先從redis的post快取物件裡去查詢key等於傳過來的id的值。如果沒有,就去查表。
在save方法上加了個CachePut,代表往快取裡新增值,key為引數post的id屬性,這樣當我們save一個Post物件時,redis就會新增一個以id為key的Post物件;如果是update操作,那同樣,redis會覆蓋id相同的Post物件的值,也完成一次更新。更多標籤,請看http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html#cache-spel-context
這樣,在對post的新增和修改時都會自動快取到redis裡。
下面來驗證一下。
加個service
package com.tianyalei.service;
import com.tianyalei.domain.Post;
import com.tianyalei.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Created by wuwf on 17/4/20.
*/
@Service
public class PostService {
@Autowired
private PostRepository postRepository;
public Post findById(int id) {
return postRepository.findById(id);
}
public Post save(Post post) {
return postRepository.save(post);
}
public int delete(int id) {
return postRepository.deleteById(id);
}
}
來個controller
package com.tianyalei.controller;
import com.tianyalei.domain.Post;
import com.tianyalei.service.PostService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Created by wuwf on 17/4/20.
*/
@RestController
public class PostController {
@Autowired
private PostService postService;
@RequestMapping("/query/{id}")
public Object query(@PathVariable int id) {
return postService.findById(id);
}
@RequestMapping("/save")
public Object save(@ModelAttribute Post post) {
return postService.save(post);
}
@RequestMapping("/delete/{id}")
public Object delete(@PathVariable int id) {
return postService.delete(id);
}
@RequestMapping("/queryPage")
public Object query(String name, int pageNum, int count) {
//根據weight倒序分頁查詢
// Pageable pageable = new PageRequest(pageNum, count, Sort.Direction.DESC, "weight");
// return userRepository.findByName(name, pageable);
return null;
}
}
然後啟動Application,在啟動之前需要加上@EnableCaching註解,快取才能正常工作。
package com.tianyalei;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class Demo0Application {
public static void main(String[] args) {
SpringApplication.run(Demo0Application.class, args);
}
}
啟動後訪問 http://localhost:8080/save?content=1&weight=1
這樣就添加了一條記錄,控制檯有一個insert語句。
然後訪問查詢,http://localhost:8080/query/1
會發現查詢到了id為1的這條記錄,並且控制檯沒有走select查詢語句,也就是根本沒訪問資料庫,直接從redis快取拿的值。
下面做一個更新操作,看看是否會同步到redis裡。http://localhost:8080/save?content=1&weight=2&id=1
把weight改為2,訪問地址看看結果。
控制檯列印了兩條語句
Hibernate: select post0_.id as id1_0_0_, post0_.content as content2_0_0_, post0_.weight as weight3_0_0_ from post post0_ where post0_.id=?
Hibernate: update post set content=?, weight=? where id=?
說明資料已經被更新了。然後再查詢http://localhost:8080/query/1
發現查到的資料已經改變,並且控制檯沒有走select語句,說明在update時,redis已經更新了。
下面做刪除操作,可以直接在資料庫裡刪這條記錄,或者通過瀏覽器訪問來刪除。http://localhost:8080/delete/1
控制檯走了刪除delete語句。再訪問查詢地址。發現依舊能查到這條記錄,也就是db的刪除成功了,但redis並沒有刪除。
那麼怎麼在db刪除時,也刪除redis的相關記錄呢?
用CacheEvict
@CacheConfig(cacheNames = "post")
public interface PostRepository extends PagingAndSortingRepository<Post, Integer> {
@Cacheable(key = "#p0")
Post findById(int id);
/**
* 新增或修改時
*/
@CachePut(key = "#p0.id")
@Override
Post save(Post post);
@Transactional
@Modifying
@CacheEvict(key = "#p0")
int deleteById(int id);
}
加上這個標籤後,再走deleteById方法時,就會刪除掉key為id的redis記錄了。可以重啟試試,訪問http://localhost:8080/delete/1
然後再查詢就會發現id為1的值已經查不到了。
這樣我們就完成了一個最簡單的整合redis的demo。包括了對單個物件的增刪改查的快取。
那麼下面來講幾個疑問:
1.為什麼不用配置redis的地址,port,密碼什麼的?
上面的那些預設的對redis的操作,來源於Springboot裡整合的RedisTemplate,template裡會預設使用一個JedisConnectionFactory來做預設的連線屬性配置。
這裡面已經對jedis的連線地址和jedisPool做了初始化操作了,都是預設值。系統就會使用這些預設值來操作redis。
後面我們會對Connection進行自定義,設定value的序列化方式,還有修改連線地址,那時就會使用自定義的配置了。
2.能否用上面的方法來儲存集合?譬如所有的Post集合,當新增時集合也隨之改變?
不行的,假如給List<Post> findAll做了個快取,那下次查詢時確實不用查表了,但是當你新增、修改、刪除任何一個物件時,這個快取的集合都是不變的。
除非你在所有的能修改物件的地方,都加上CacheEvict,key為集合的key,這樣任何修改,都是刪除整個集合物件的快取,下次再查時才能快取起來。而大部分時候,集合物件都是在不停變化的,除了一些不變的如城市列表之類的,其他的都不適合用這種快取方式。會導致頻繁建立大物件,而且大部分時候也不需要查整個集合,而是分頁。
3.怎麼用redis來做集合查詢,分頁查詢,甚至於條件分頁查詢?
這個也是問題2的延續,後面一篇會講。