创建缓存服务
创建缓存服务接口项目
- 创建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)