缓存中的服务详解!SpringBoot中二级缓存服务实现

创建缓存服务

创建缓存服务接口项目

  • 创建myshop-service-redis-api项目,该项目只负责定义接口
  • 创建项目的pom.xml:

    
    
      4.0.0
      
          com.oxford
          myshop-dependencies
          1.0.0-SNAPSHOT
          ../myshop-dependencies/pom.xml
      
    
      myshop-service-redis-api
      jar
    
  • 定义数据Redis接口RedisService:

    package com.oxford.myshop.service.redis.api
    
    public interface RedisService{
      void set(String key,Object value);
      
      void set(String key,Object value,int seconds);
    
      void del(String key);
    
      Object get(String key);
    }

    创建缓存服务提供者项目

  • 创建myshop-service-redis-provider项目,该项目用作缓存服务提供者
  • 创建项目的pom.xml:

    
    
      4.0.0
      
          com.oxford
          myshop-dependencies
          1.0.0-SNAPSHOT
          ../myshop-dependencies/pom.xml
      
    
      myshop-service-redis-api
      jar
    
      
          
          
              org.springframework.boot
              spring-boot-starter-data-redis
          
    
          
              org.springframework.boot
              spring-boot-starter-test
              test
          
    
    
          
          
              org.apache.commons
              commons-pool2
          
          
              de.javakaffee
              kryo-serializers
          
    
          
          
              com.oxford
              my-shop-commons-dubbo
              ${Project.parent.version}
          
          
              com.oxford
              my-shop-service-redis-api
              ${Project.parent.version}
          
      
    
      
          
              
                  org.springframework.boot
                  spring-boot-maven-plugin
                  
                      com.oxford.myshop.service.redis.provider.MyshopServiceRedisProviderApplication
                  
              
          
      
    

    Redis底层实现的Java的lettuce客户端

  • 创建缓存服务接口实现类RedisServiceImpl

    package com.oxford.myshop.service.redis.provider.api.impl;
    
    @Service(version="${service.versions.redis.v1}")
    public class RedisServiceImpl implements RedisService{
      
      @Override
      public void set(String key,Object value){
          redisTemplate.opsForValue().set(key,value);
      }
    
      @Override
      public void set(String key,Object value,int seconds){
          redisTemplate.opsForValue().set(key,value,seconds,TimeUnit.SECONDS);
      }
    
      @Override
      public void del(String key){
          redisTemplate.delete(key);
      }
    
      @Override
      public Object get(String key){
          return redisTemplate.opsForValue().get(key);
      }
    }
  • 创建启动类SpringBootApplication

    package com.oxford.myshop.service.redis.provider;
    
    import com.alibaba.dubbo.container.Main;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.hystrix.EnableHystrix;
    import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
    
    
    
    @EnableHystrix
    @EnableHystrixDashboard
    public class MyShopServiceRedisrProviderApplication {
      public static void main(String[]args) {
          SpringApplication.run(MyShopServiceRedisProviderApplication.class,args);
          Main.main(args);
      }
    }
  • 创建配置文件application.yml

    spring:
    application:
      name: myshop-service-redis-provider
    redis:
        lettuce:
        pool:
            max-active: 8
            max-idle: 8
            max-wait: -1ms
            min-idle: 0
      sentinel:
        master: mymaster
        nodes: 192.168.32.255:26379,192.168.32.255:26380,192.168.32.255:26381
    server:
    port: 8503
    
    services:
    version:
      redis:
          v1: 1.0.0
      user:
        v1: 1.0.0
    
    dubbo:
    scan:
      basePackages: com.oxford.myshop.service.redis.provider.api.impl
    application:
      id: com.oxford.myshop.service.redis.provider.api
      name: com.oxford.myshop.service.redis.provider.api
      qos-port: 22224
      qos-enable: true
    protocal:
      id: dubbo
      name: dubbo
      port: 20883
      status: server
      serialization: kryo
    
    regitry:
      id: zookeeper
      address: zookeeper://localhost:2181?backup=192.168.32.255:2182,192.168.32.255:2183
    
    management:
    endpoint:
      dubbo:
        enabled: true
      dubbo-shutdown:
        enabled: true
      dubbo-configs:
        enabled: true
      dubbo-sevicies:
        enabled: true
      dubbo-reference:
        enabled: true
      dubbo-properties:
        enabled: true
    health:
      dubbo:
        status:
          defaults: memory
          extras: load,threadpool

    创建缓存服务消费者项目

  • 在pom文件中引入redis接口依赖
  • 在缓存服务消费者项目的ServiceImpl中调用RedisService

    @Reference(version="services.versions.redis.v1")
    private RedisService redisService;

    MyBatis Redis二级缓存

    MyBatis缓存

  • 一级缓存:

    • MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存: 将每次查询到的结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询
    • 一级缓存是SqlSession级别的缓存:

      • 在操作数据库时需要构造SqlSession对象
      • 对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据
      • 不同的SqlSession之间的缓存数据区域(HashMap)互不影响,
      • 一级缓存的作用域是同一个SqlSession
      • 在同一个SqlSession中两次执行相同的SQL语句: 第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据,将不再从数据库查询,从而提高查询效率
      • 当一个SqlSession结束后该SqlSession中的一级缓存就不存在了
      • MyBatis默认开启一级缓存
  • 二级缓存:

    • 二级缓存是Mapper级别的缓存: 多个SqlSession去操作同一个Mapper的SQL语句,多个SqlSession去操作数据库得到数据会存在二级缓存区域,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的
    • 二级缓存的作用域是mapper的同一个namespace
    • 不同的SqlSession两次执行相同namespace下的SQL语句且向SQL中传递参数也相同即最终执行相同的SQL语句: 第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率
    • MyBatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存

      配置MyBatis二级缓存

      SpringBoot中开启MyBatis二级缓存
  • 在myshop-service-user-provider的配置文件中开启MyBatis二级缓存

    spring:
    application:
      name: myshop-service-user-provider
    datasource:
      druid:
        url: jdbc:mysql://localhost:3306/myshop?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: 123456
        initial-size: 1
        min-idle: 1
        main-active: 20
        test-on-borrow: true
        driver-class-name: com.mysql.cj.jdbc.Driver
    redis:
        lettuce:
        pool:
            max-active: 8
            max-idle: 8
            max-wait: -1ms
            min-idle: 0
      sentinel:
        master: mymaster
        nodes: 192.168.32.255:26379,192.168.32.255:26380,192.168.32.255:26381
    
    server:
    port: 8501
    
    # MyBatis Config properties
    mybatis:
    configuration:
        cache-enabled: true
    type-aliases-package: com.oxford.myshop.commons.domain
    mapper-location: classpath:mapper/*.xml
    
    services:
    version:
        redis:
          v1: 1.0.0
      user:
        v1: 1.0.0
    
    dubbo:
    scan:
      basePackages: com.oxford.myshop.service.user.provider.api.impl
    application:
      id: com.oxford.myshop.service.user.provider.api
      name: com.oxford.myshop.service.user.provider.api
      qos-port: 22222
      qos-enable: true
    protocal:
      id: dubbo
      name: dubbo
      port: 20001
      status: server
      serialization: kryo
    
    regitry:
      id: zookeeper
      address: zookeeper://localhost:2181?backup=192.168.32.255:2182,192.168.32.255:2183
    
    management:
    endpoint:
      dubbo:
        enabled: true
      dubbo-shutdown:
        enabled: true
      dubbo-configs:
        enabled: true
      dubbo-sevicies:
        enabled: true
      dubbo-reference:
        enabled: true
      dubbo-properties:
        enabled: true
    health:
      dubbo:
        status:
          defaults: memory
          extras: load,threadpool
  • 在myshop-commons-mapper的pom.xml中增加redis依赖:

    
      org.springframework.boot
      spring-boot-starter-data-redis
    
    
    
      org.apache.commons
      commons-pool2
    
    实体类实现序列化接口并声明序列号
    private static final long serialVersionUID = 82897704415244673535L
  • 使用GenerateSerialVersionUID插件生成,安装完插件后在实现了序列化接口的类中
  • 使用快捷键Alt+Insert即可呼出生成菜单,即可自动生成序列号

  • 在myshop-commons项目中创建ApplicationContextHolder类

    package com.oxford.myshop.commons.context;
    
    @Component
    public class ApplicationContextHolder implements ApplicationContextAware,DisposableBean{
    
      private static final Logger logger=LoggerFactory.getLogger(ApplicationContext.class);
    
      private static ApplicationContext applicationContext;
    
      /**
       * 获取存储在静态变量中的ApplicationContext
       */
       public static ApplicationContext getApplicationContext(){
           assertContextInjected();
           return applicationContext;
       }
    
      /**
       * 从静态变量applicationContext中获取Bean,自动转型成所赋值对象的类型
       */
       public static  T getBean(String name){
           assertContextInjected();
           return (T) applicationContext.getBean(name);
       }
    
      /**
       * 从静态变量applicationContext中获取Bean,自动转型成所赋值对象的类型
       */
      public static  T getBean(Class clazz){
           assertContextInjected();
           return (T) applicationContext.getBean(clazz);
       }
    
      /**
       * 实现DisposableBean接口,在Context关闭时清理静态变量
       */
       public void destroy() throws Exception{
           logger.debug("清除 SpringContext 中的 ApplicationContext: {}",applicationContext);
           applicationContext=null;
       }
    
      /**
       * 实现ApplicationContextAware接口,注入Context到静态变量中
       */
       public void setApplicationContext(ApplicationContext applicationContext) throws BeanException{
           ApplicationContext.applicationContext=applicationContext;
       }
    
      /**
       * 断言Context已经注入
       */
       private static void assertContextInjected(){
          Validate.validState(applicationContext !=null,"applicationContext 属性未注入,请在配置文件中配置定义ApplicationContextContext");
      }
    }
  • 在myshop-commons-mapper项目中创建一个RedisCache的工具类

    package com.oxford.myshop.commons.utils;
    
    public class RedisCache implements Cache{
      private static final Logger logger=LoggerFactory.getLogger(RedisCache.class);
    
      private final ReadWriteLock readWriteLock=new ReentranReadWriteLock();
      private final String id;
      private RedisTemplate redisTemplate;
    
      private static final long EXPIRE_TIME_IN_MINUTES=30        // redis过期时间
    
      public RedisCache(String id){
          if(id==null){
              throw new IllegalArgumentException("Cache instances require an ID");
          }
          this.id=id;
      }
    
      @Override
      public String getId(){
          return id;
      }
    
      /**
       * Put query result to redis 
       */
      @Override
      public void putObject(Object key,Object value){
          try{
              RedisTemplate redisTemplate=getRedisTemplate();
              ValueOperations opsForValue=redisTemplate.opsForValue();
              opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
              logger.debug("Put query result to redis");
          }catch(Throwable t){
              logger.error("Redis put failed",t);
          }
      }
    
      /**
       * Get cached query result from redis 
       */
       @Override
       public Object getObject(Object key){
          try{
              RedisTemplate redisTemplate=getRedisTemplate();
              ValueOperations opsForValue=redisTemplate.opsForValue();
              opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
              logger.debug("Get cache query result from redis");
              return opsForValue.get(key);
          }catch(Throwable t){
              logger.error("Redis get failed, fail over to db");
              return null;
          }
      }
    
      /**
       * Get cached query result from redis 
       */
       @Override
       public Object getObject(Object key){
          try{
              RedisTemplate redisTemplate=getRedisTemplate();
              ValueOperations opsForValue=redisTemplate.opsForValue();
              opsForValue.set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
              logger.debug("Get cache query result from redis");
              return opsForValue.get(key);
          }catch(Throwable t){
              logger.error("Redis get failed, fail over to db");
              return null;
          }
      }
    
      /**
       * Remove cached query result from redis 
       */
       @Override
       @SuppressWarnings("unchecked")
       public Object removeObject(Object key){
          try{
              RedisTemplate redisTemplate=getRedisTemplate();
              redisTemplate.delete(key);
              logger.debug("Remove cached query result from redis");
          }catch(Throwable t){
              logger.error("Redis remove failed");
          }
              return null;
      }
    
      /**
       * Clear this cache instance
       */
       @Override
       public void clear(){
           RedisTemplate redisTemplate=getRedisTemplate();
           redisTemplate.execute((RedisCallback)->{
               connection.flushDb();
               return null;
           });
           logger.debug("Clear all the cached query result from redis");
       }
    
      @Override
      public int getSize(){
          return 0;
      }
      
      @Override
      public ReadWriteLock getReadWriteLock(){
          return readWriteLock;
      }
    
      private RedisTemplate getRedisTemplate(){
          if(redisTemplate==null){
              redisTemplate=ApplicationContextHolder.getBean("redisTemplate");
          }
          return redisTemplate;
      }
    }
    Mapper接口类中标注注解
  • 在Mapper接口类上标注注解,声明使用二级缓存

    @CacheNamespace(implementation=RedisCache.class)

你可能感兴趣的:(缓存)