springboot(五)-使用Redis

Redis服务器

springboot要使用redis,首先当然要确保redis服务器能够正常跑起来。

 pom.xml

这里添加redis的依赖,当然也是springboot集成好的。

<!-- redis -->       
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

application.properties

增加redis相关配置
同时让hibernate的sql语句显示出来,这样才知道到底是通过 Redis 取到的数据,还是依然是从数据库取到的数据

 1 spring.mvc.view.prefix=/WEB-INF/jsp/
 2 spring.mvc.view.suffix=.jsp
 3 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8
 4 spring.datasource.username=root
 5 spring.datasource.password=admin
 6 spring.datasource.driver-class-name=com.mysql.jdbc.Driver
 7 spring.jpa.properties.hibernate.hbm2ddl.auto=update
 8 
 9 ###########################redis#########################
10 #Redis数据库索引(默认为0)
11 spring.redis.database=0
12 #Redis服务器地址
13 spring.redis.host=127.0.0.1
14 #Redis服务器连接端口
15 spring.redis.port=6379
16 #Redis服务器连接密码(默认为空)
17 spring.redis.password=
18 #连接池最大连接数(使用负值表示没有限制)
19 spring.redis.pool.max-active=10
20 #连接池最大阻塞等待时间(使用负值表示没有限制)
21 spring.redis.pool.max-wait=-1
22 #连接池中的最大空闲连接
23 spring.redis.pool.max-idle=8
24 #连接池中的最小空闲连接
25 spring.redis.pool.min-idle=0
26 #连接超时时间(毫秒)
27 spring.redis.timeout=0
28 
29 spring.jpa.show-sql=true

Application.java

增加注解,以开启缓存

@EnableCaching

 1 package com.how2java.springboot;
 2  
 3 import org.springframework.boot.SpringApplication;
 4 import org.springframework.boot.autoconfigure.SpringBootApplication;
 5 import org.springframework.cache.annotation.EnableCaching;
 6  
 7 @SpringBootApplication
 8 @EnableCaching
 9 public class Application {
10  
11     public static void main(String[] args) {
12         SpringApplication.run(Application.class, args);
13     }
14  
15 }

RedisConfig.java

Redis 缓存配置类。
这个配置,一个作用: 让保存到 Redis 里的 key 和 value 都转换为可读的 json 格式。 否则会是二进制格式,通过RedisClient 工具也无法识别。

 1 package com.how2java.springboot.config;
 2 import org.springframework.cache.CacheManager;
 3 import org.springframework.cache.annotation.CachingConfigurerSupport;
 4 import org.springframework.cache.annotation.EnableCaching;
 5 import org.springframework.context.annotation.Bean;
 6 import org.springframework.context.annotation.Configuration;
 7 import org.springframework.data.redis.cache.RedisCacheManager;
 8 import org.springframework.data.redis.core.RedisTemplate;
 9 import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
10 import org.springframework.data.redis.serializer.RedisSerializer;
11 import org.springframework.data.redis.serializer.StringRedisSerializer;
12  
13 import com.fasterxml.jackson.annotation.JsonAutoDetect;
14 import com.fasterxml.jackson.annotation.PropertyAccessor;
15 import com.fasterxml.jackson.databind.ObjectMapper;
16  
17 @Configuration
18 @EnableCaching
19 //Redis 缓存配置类
20 public class RedisConfig extends CachingConfigurerSupport {
21  
22     @Bean
23     public CacheManager cacheManager(RedisTemplate<?,?> redisTemplate) {
24         RedisSerializer stringSerializer = new StringRedisSerializer();
25         Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
26         ObjectMapper om = new ObjectMapper();
27         om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.PUBLIC_ONLY);
28         om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
29         jackson2JsonRedisSerializer.setObjectMapper(om);
30         redisTemplate.setKeySerializer(stringSerializer);
31         redisTemplate.setHashKeySerializer(stringSerializer);  
32          
33         redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);         
34         redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
35         CacheManager cacheManager = new RedisCacheManager(redisTemplate);
36         return cacheManager;
37   
38     }
39 }
点击展开

Page4Navigator

创建一个工具类 Page4Navigator 用以替换 原本分页查询要返回的 org.springframework.data.domain.Page 类。 原因是 Page 类对json 还原不支持,在放如 Redis 之后,再拿出来,就会报错失败。
使用 Page4Navigator 对其包裹,就解决了这个问题了。

  1 package com.how2java.springboot.util;
  2  
  3 import java.util.List;
  4  
  5 import org.springframework.data.domain.Page;
  6  
  7 public class Page4Navigator<T> {
  8     Page<T> page4jpa;
  9     int navigatePages;
 10      
 11     int totalPages;
 12  
 13     int number;
 14      
 15     long totalElements;
 16      
 17     int size;
 18  
 19     int numberOfElements;
 20  
 21     List<T> content;
 22  
 23     boolean isHasContent;
 24  
 25     boolean first;
 26  
 27     boolean last;
 28      
 29     boolean isHasNext;
 30  
 31     boolean isHasPrevious;
 32      
 33     int[] navigatepageNums;
 34      
 35     public Page4Navigator() {
 36         //这个空的分页是为了 Redis 从 json格式转换为 Page4Navigator 对象而专门提供的
 37     }
 38      
 39     public Page4Navigator(Page<T> page4jpa,int navigatePages) {
 40         this.page4jpa = page4jpa;
 41         this.navigatePages = navigatePages;
 42          
 43         totalPages = page4jpa.getTotalPages();
 44          
 45         number  = page4jpa.getNumber() ;
 46          
 47         totalElements = page4jpa.getTotalElements();
 48          
 49         size = page4jpa.getSize();
 50          
 51         numberOfElements = page4jpa.getNumberOfElements();
 52          
 53         content = page4jpa.getContent();
 54          
 55         isHasContent = page4jpa.hasContent();
 56                  
 57         first = page4jpa.isFirst();
 58          
 59         last = page4jpa.isLast();
 60          
 61         isHasNext = page4jpa.hasNext();
 62          
 63         isHasPrevious  = page4jpa.hasPrevious();       
 64          
 65         calcNavigatepageNums();
 66          
 67     }
 68  
 69     private void calcNavigatepageNums() {
 70         int navigatepageNums[];
 71         int totalPages = getTotalPages();
 72         int num = getNumber();
 73         //当总页数小于或等于导航页码数时
 74         if (totalPages <= navigatePages) {
 75             navigatepageNums = new int[totalPages];
 76             for (int i = 0; i < totalPages; i++) {
 77                 navigatepageNums[i] = i + 1;
 78             }
 79         } else { //当总页数大于导航页码数时
 80             navigatepageNums = new int[navigatePages];
 81             int startNum = num - navigatePages / 2;
 82             int endNum = num + navigatePages / 2;
 83  
 84             if (startNum < 1) {
 85                 startNum = 1;
 86                 //(最前navigatePages页
 87                 for (int i = 0; i < navigatePages; i++) {
 88                     navigatepageNums[i] = startNum++;
 89                 }
 90             } else if (endNum > totalPages) {
 91                 endNum = totalPages;
 92                 //最后navigatePages页
 93                 for (int i = navigatePages - 1; i >= 0; i--) {
 94                     navigatepageNums[i] = endNum--;
 95                 }
 96             } else {
 97                 //所有中间页
 98                 for (int i = 0; i < navigatePages; i++) {
 99                     navigatepageNums[i] = startNum++;
100                 }
101             }
102         }  
103         this.navigatepageNums = navigatepageNums;
104     }
105  
106     public int getNavigatePages() {
107         return navigatePages;
108     }
109  
110     public void setNavigatePages(int navigatePages) {
111         this.navigatePages = navigatePages;
112     }
113  
114     public int getTotalPages() {
115         return totalPages;
116     }
117  
118     public void setTotalPages(int totalPages) {
119         this.totalPages = totalPages;
120     }
121  
122     public int getNumber() {
123         return number;
124     }
125  
126     public void setNumber(int number) {
127         this.number = number;
128     }
129  
130     public long getTotalElements() {
131         return totalElements;
132     }
133  
134     public void setTotalElements(long totalElements) {
135         this.totalElements = totalElements;
136     }
137  
138     public int getSize() {
139         return size;
140     }
141  
142     public void setSize(int size) {
143         this.size = size;
144     }
145  
146     public int getNumberOfElements() {
147         return numberOfElements;
148     }
149  
150     public void setNumberOfElements(int numberOfElements) {
151         this.numberOfElements = numberOfElements;
152     }
153  
154     public List<T> getContent() {
155         return content;
156     }
157  
158     public void setContent(List<T> content) {
159         this.content = content;
160     }
161  
162     public boolean isHasContent() {
163         return isHasContent;
164     }
165  
166     public void setHasContent(boolean isHasContent) {
167         this.isHasContent = isHasContent;
168     }
169  
170     public boolean isFirst() {
171         return first;
172     }
173  
174     public void setFirst(boolean first) {
175         this.first = first;
176     }
177  
178     public boolean isLast() {
179         return last;
180     }
181  
182     public void setLast(boolean last) {
183         this.last = last;
184     }
185  
186     public boolean isHasNext() {
187         return isHasNext;
188     }
189  
190     public void setHasNext(boolean isHasNext) {
191         this.isHasNext = isHasNext;
192     }
193  
194     public boolean isHasPrevious() {
195         return isHasPrevious;
196     }
197  
198     public void setHasPrevious(boolean isHasPrevious) {
199         this.isHasPrevious = isHasPrevious;
200     }
201  
202     public int[] getNavigatepageNums() {
203         return navigatepageNums;
204     }
205  
206     public void setNavigatepageNums(int[] navigatepageNums) {
207         this.navigatepageNums = navigatepageNums;
208     }
209  
210 }
点击展开

CategoryService

增加 Service接口。 注意: list 返回的是 Page4Navigator 而不再是 Page 类型了。

 1 package com.how2java.springboot.service;
 2  
 3 import org.springframework.data.domain.Pageable;
 4  
 5 import com.how2java.springboot.pojo.Category;
 6 import com.how2java.springboot.util.Page4Navigator;
 7  
 8 public interface CategoryService {
 9  
10     public Page4Navigator<Category> list(Pageable pageable);
11      
12     public void save(Category category);
13      
14     public void delete(int id);
15      
16     public Category get(int id);
17 }

CategoryServiceImpl

实现类CategoryServiceImp 做了一下工作:
1. 实现 CategoryService 接口,提供 crud

2. 在相应方法实现的时候,都是通过调用 dao 实现的

3. CacheConfig,表示 分类数据在 redis 中都放在 category 这个分组里。

@CacheConfig(cacheNames="category")

4. list方法讲解:
先说注解

@Cacheable(key="'category '+#p0.offset + '-' + #p0.pageSize ")

假如是第一页,即offset=0,pageSize=0,那么会创建一个 key: "category 0-5"
首先根据这个key 到 redis中查询数据。 第一次是不会有数据的,那么就会从数据库中取到这5条数据,然后以这个 key: "category 0-5" 保存到 redis 数据库中。
下一次再次访问的时候,根据这个key,就可以从 redis 里取到数据了。

5. get 方法讲解
先说注解:

@Cacheable(key="'category '+ #p0")

假如是获取id=71的数据,那么
就会以 key= "category 71" 到reids中去获取,如果没有就会从数据库中拿到,然后再以 key= "category 71" 这个值存放到 redis 当中。
下一次再次访问的时候,根据这个key,就可以从 redis 里取到数据了。

6. add 方法讲解
先说注解:

@CacheEvict(allEntries=true)
// @CachePut(key="'category '+ #p0")

可以看到,本来有个 CachePut,但是被注释掉了。 按理说,本来是应该用这个的。 这样会到在,在增加数据之后,就会在Redis 中以 key= "category 71" 缓存一条数据。 但是为什么被注释掉不用呢? 
因为加入这样做了,那么 list 对应的数据,在缓存在对应的数据,并没有发生变化呀? 因为 list 对应的数据是这样的 key: "category 0-5"。 如果用这种方式,就会导致数据不同步,即,虽然增加了,并且也增加到缓存中了,但是因为 key 不一样,通过查询拿到的数据,是不会包含新的这一条的。 
所以,才会使用CacheEvict 这个注解,这个注解就表示清除掉缓存。 allEntries= true 是表示清除掉 category 分组 下所有的keys. 注意看截图,里面有一个 category~keys ,里面就表明了都有哪些 keys,都会被清除掉。 
假如这个时候,还有一个分组 cacheNames="product", 那么它下面对应的缓存,都是不会被影响到的。 这样就保证了,只清楚当前分组下的缓存,而不是清除 redis 所有的数据了。

7. delete 方法讲解
先说注解:

@CacheEvict(allEntries=true)
// @CacheEvict(key="'category '+ #p0")

这个道理和 add 是一样的,仅仅删除 key= "category 71" ,没有什么意义, key: "category 0-5" 里面的数据没有影响呀。 所以还是通过 CacheEvict删除掉所有的缓存就好了。

 1 package com.how2java.springboot.service.impl;
 2  
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 import org.springframework.cache.annotation.CacheConfig;
 5 import org.springframework.cache.annotation.CacheEvict;
 6 import org.springframework.cache.annotation.CachePut;
 7 import org.springframework.cache.annotation.Cacheable;
 8 import org.springframework.data.domain.Page;
 9 import org.springframework.data.domain.Pageable;
10 import org.springframework.stereotype.Service;
11  
12 import com.how2java.springboot.dao.CategoryDAO;
13 import com.how2java.springboot.pojo.Category;
14 import com.how2java.springboot.service.CategoryService;
15 import com.how2java.springboot.util.Page4Navigator;
16  
17 @Service
18 @CacheConfig(cacheNames="category")
19 public class CategoryServiceImpl implements CategoryService{
20  
21     @Autowired CategoryDAO categoryDAO;
22      
23     @Override
24     @Cacheable(key="'category '+#p0.offset + '-' + #p0.pageSize ")
25     public Page4Navigator<Category> list(Pageable pageable) {
26         Page<Category> pageFromJPA=  categoryDAO.findAll(pageable);
27         Page4Navigator<Category> page = new Page4Navigator<>(pageFromJPA,5);
28         return page;
29     }
30  
31     @Override
32     @Cacheable(key="'category '+ #p0")
33     public Category get(int id) {
34         Category c =categoryDAO.findOne(id);
35         return c;
36     }  
37      
38     @Override
39     @CacheEvict(allEntries=true)
40 //  @CachePut(key="'category '+ #p0")
41     public void save(Category category) {
42         // TODO Auto-generated method stub
43         categoryDAO.save(category);
44     }
45      
46     @Override
47     @CacheEvict(allEntries=true)
48 //  @CacheEvict(key="'category '+ #p0")
49     public void delete(int id) {
50         // TODO Auto-generated method stub
51         categoryDAO.delete(id);
52     }
53  
54 }
点击展开

CategoryController

 1 package com.how2java.springboot.web;
 2 import org.springframework.beans.factory.annotation.Autowired;
 3 import org.springframework.data.domain.PageRequest;
 4 import org.springframework.data.domain.Pageable;
 5 import org.springframework.data.domain.Sort;
 6 import org.springframework.stereotype.Controller;
 7 import org.springframework.ui.Model;
 8 import org.springframework.web.bind.annotation.RequestMapping;
 9 import org.springframework.web.bind.annotation.RequestParam;
10  
11 import com.how2java.springboot.pojo.Category;
12 import com.how2java.springboot.service.CategoryService;
13 import com.how2java.springboot.util.Page4Navigator;
14   
15 @Controller
16 public class CategoryController {
17     @Autowired CategoryService categoryService;
18      
19     @RequestMapping("/listCategory")
20      
21     public String listCategory(Model m,@RequestParam(value = "start", defaultValue = "0") int start,@RequestParam(value = "size", defaultValue = "5") int size) throws Exception {
22         start = start<0?0:start;
23         Sort sort = new Sort(Sort.Direction.DESC, "id");
24         Pageable pageable = new PageRequest(start, size, sort);
25         Page4Navigator<Category> page =categoryService.list(pageable);
26         m.addAttribute("page", page);
27         return "listCategory";
28     }
29  
30     @RequestMapping("/addCategory")
31     public String addCategory(Category c) throws Exception {
32         categoryService.save(c);
33         return "redirect:listCategory";
34     }
35     @RequestMapping("/deleteCategory")
36     public String deleteCategory(Category c) throws Exception {
37         categoryService.delete(c.getId());
38         return "redirect:listCategory";
39     }
40     @RequestMapping("/updateCategory")
41     public String updateCategory(Category c) throws Exception {
42         categoryService.save(c);
43         return "redirect:listCategory";
44     }
45     @RequestMapping("/editCategory")
46     public String ediitCategory(int id,Model m) throws Exception {
47         Category c= categoryService.get(id);
48         m.addAttribute("c", c);
49         return "editCategory";
50     }
51 }

 运行

我们来看运行结果。

1.向上面一样,打开redis服务器。

2.打开redis客户端,并建立连接。

就是这个,安装这个可以很清晰的看到redis里面的值。

 运行springboot工程,访问http://127.0.0.1:8080/listCategory?start=0

 在控制台上你会看到这个:

Hibernate: select category0_.id as id1_0_, category0_.name as name2_0_ from category_ category0_ order by category0_.id desc limit ?
Hibernate: select count(category0_.id) as col_0_0_ from category_ category0_

第一次访问,需要查询数据库。然后再看redis客户端:

我这里用的是db2,上面application.properties代码里是db0.这个可以随便该,每个项目用一个嘛。

 第一次查询的内容保存到redis里面了。然后你刷新下页面,也就是再访问一次,控制台就没有打印sql语句了。

好了,你现在多点几次下一页,目的是给redis缓存多一点数据。

 你在下面添加一条数据。

 控制台打印三条sql语句

Hibernate: insert into category_ (name) values (?)
Hibernate: select category0_.id as id1_0_, category0_.name as name2_0_ from category_ category0_ order by category0_.id desc limit ?
Hibernate: select count(category0_.id) as col_0_0_ from category_ category0_

 很明显的呀,一条插入,两条查询。这两条查询语句对应着第一页记录。

这时候再看redis客户端:

这就说明,我们在修改数据之后,redis中的数据就清空了。实现了我们想要的结果。


代码下载地址:https://gitee.com/fengyuduke/my_open_resources/blob/master/springboot-redis.zip

原文地址:https://www.cnblogs.com/fengyuduke/p/10483622.html