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