zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    json转MAP
    责任链模式
    单例模式
    代理模式
    策略模式
    mysql触发器的使用
    Java 中的日期和时间
    MySQL插入数据前检测唯一性
    java字符串转为Map类型:split()方法的应用
    java Socket实例
  • 原文地址:https://www.cnblogs.com/fengyuduke/p/10483622.html
Copyright © 2011-2022 走看看