SpringBoot整合Redis使用注解实现同步缓存
1 SpringBoot整合Redis的环境配置
1.1 添加依赖
<properties>
<java.version>1.8</java.version>
<druid.version>1.1.9</druid.version>
<!-- mybatis的版本 -->
<mybatis.version>3.4.6</mybatis.version>
</properties>
<dependencies>
<!-- 配置web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 配置mybatis-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>
<!-- 配置数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions> <!--不依赖Redis的异步客户端lettuce-->
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--引入Redis的客户端驱动jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--SpringBoot缓存支持启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--Ehcache坐标-->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
</dependencies>
这样我们就引入了Spring对Redis的starter,只是在默认的情况下,spring-boot-starter-data-redis(版本2.x)
会依赖Lettuce的Redis客户端驱动,而在一般的项目中,我们会使用Jedis,所以在代码中使用了<exclusions>
元素将其依赖排除了,与此同时引入了Jedis的依赖,这样就可以使用Jedis进行编程了。
1.2 配置application.properties文件
################# 配置数据库连接参数*************************************************
spring.datasource.driverClassName =com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
###### 指定mapper文件的位置
mybatis.mapper-locations=classpath:mapper/*.xml
## 设置视图层的别名,类似于在spring项目中的mybatis-config.xml文件中配置<typeAliases>
mybatis.type-aliases-package=com.fyzn12.pojo
################## 更改springboot内置tomcat的参数**********************
# 连接池中的最大空闲连接
spring.datasource.tomcat.max-idle=10
# 连接池最大连接数(使用负值表示没有限制)
spring.datasource.tomcat.max-active=50
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.datasource.tomcat.max-wait=10000
spring.datasource.tomcat.initial-size=5
################### 设置默认的隔离级别为读已提交 ************************
spring.datasource.tomcat.default-transaction-isolation=2
################### 配置redis****************************************************
# 配置连接池属性
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
#spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=5000
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=1000
#spring-session 使用
spring.session.store-type=none
################### 配置缓存管理 **********************************************
# 配置redis的缓存管理器
# spring.cache.type=redis配置的是缓存类型,Spring Boot会自动生成RedisCacheManager对象
spring.cache.type=redis
# spring.cache.cache-names则是配置缓存名称,多个名称可以使用逗号分隔,以便于缓存注解的引用。
spring.cache.cache-names=redisCache
################### 配置日志 **************************************************
#logging.level.root=DEBUG
#logging.level.org.springframework=DEBUG
#logging.level.org.mybatis=DEBUG
在上面配置文件中详细配置了redis的使用,这里注意“配置缓存管理”的配置
2 三个Spring注解的讲解 @Cacheable 、 @CacheEvict 和 @CachePut
2.1 @Cacheable用于查询
@Cacheable作用:把方法的返回值添加到Ehcache中做缓存Value属性:指定一个Ehcache配置文件中的缓存策略,如果么有给定value,name则表示使用默认的缓存策略。
在Spring中使用xml文件配置时采用如下配置
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <diskStore path="java.io.tmpdir"/> <!--defaultCache:echcache的默认缓存策略 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxElementsOnDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap"/> </defaultCache> <!-- 自定义缓存策略 --> <cache name="users" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxElementsOnDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap"/> </cache> </ehcache>
使用方式
@Override //@Cacheable:对当前查询的对象做缓存处理 @Cacheable(value="users") public Users findUserById(Integer id) { return this.usersRepository.findOne(id); } //其中value值为xml文件中配置的name的值
在SpringBoot中使用
/** * @Param: param * @description:查询 * @Return * @Cacheable注解的说明 * value:该值为在application.properties文件中配置spring.cache.cache-names=redisCache指定的值 * key :为该缓存取一个名字 */ @Override @Cacheable(value = "redisCache", key = "'key_user'+#param.getParam()") @Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRES_NEW, rollbackFor = RuntimeException.class) public Object strategy(HandlerParam param) throws IOException { System.out.println("执行查询"); if (param.getParam() instanceof Integer) { //如果是全是数字组成的查询值,则认为是按照id进行查询,否则认为按照name查询 Users20174046 users20174046 = users20174046Mapper.selectById((Integer) param.getParam()); return users20174046; } else { List<Users20174046> list = users20174046Mapper.selectByName((String) param.getParam()); return list; } }
2.2 @CachePut 用于插入和修改
用于插入时用法
/** * @Param: param * @description:插入 * @Return * @CachePut注解的说明 * value:该值为在application.properties文件中配置spring.cache.cache-names=redisCache指定的值 * key :为该缓存取一个名字 */ @Override @CachePut(value = "redisCache",key = "'redis_user'+#result.id") @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRES_NEW,rollbackFor = RuntimeException.class) public Object strategy(HandlerParam param) { int count = users20174046Mapper.insertUsers((Users20174046) param.getParam()); return null; } /* 在插入数据库前,对应的用户是没有id的,而这个id值会在插入数据库后由MyBatis的机制回填, 所以我们希望使用返回结果,这样使用#result就代表返回的结果对象了,它是一个User对象, 所以#result.id是取出它的属性id,这样就可以引用这个由数据库生成的id了。 */
用于修改时的用法
/** * @Param: param * @description:修改 * @Return * @CachePut注解的说明 * value:该值为在application.properties文件中配置spring.cache.cache-names=redisCache指定的值 * key :为该缓存取一个名字 * condition:设置修改条件 */ @Override @CachePut(value = "redisCache",key = "'key_user'+#param.getParam().getId()",condition = "#result != null") @Transactional(isolation = Isolation.REPEATABLE_READ,propagation = Propagation.REQUIRES_NEW,rollbackFor = RuntimeException.class) public Object strategy(HandlerParam param) throws IOException { //因为这里我写了两个接口,因此可以通过参数的类型判断调用哪个接口 if (param.getParam() instanceof Map){ //获取要修改类型的参数 Map<Object,Object> map = (Map)param.getParam(); users20174046Mapper.updateUsers(map); System.out.println("调用的是动态sql的接口"); }else if (param.getParam() instanceof Users20174046){ users20174046Mapper.updateByUsers((Users20174046)param.getParam()); System.out.println("调用的map接口"); } System.out.println("执行修改完成"); return null; } /* 从代码中可以看到方法,可能返回null。如果为null,则不需要缓存数据,所以在注解@CachePut中加入了condition 配置工页,它也是一个SpringEL表达式,这个表达式要求返回Boolean类型值,如果为仕ue,则使用缓存操作,否则就不使用。 这里的表达式为#result!=’null’,意味着如果返回null,则方法结束后不再操作缓存。同样地,@Cacheable和 @CacheEvict也具备这个配置项。 */
2.3 @CacheEvict 清除缓存
该注解用于方法体上会清除与该方法体有关的缓存
3 在启动类上开启缓存
/**
* @author ZhangRongjun
* @EnableCaching :开启缓存
*/
@SpringBootApplication
@MapperScan(basePackages = "com.fyzn12.mapper",annotationClass = Repository.class)
@EnableCaching
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
4 自调用使注解失效
//获取id,取参数id缓存用户
@Override
@Transactional
@Cacheable(value="redisCache",key ="'redis_user_'+#id")
public User getUser (Long id) {
return userDao.getUser(id);
}
//更新数据后,更新缓存,如果condition配置项使结果返回为null,不缓存
Override
@Transactional
@CachePut(value="redisCache" , condition="#result == null ",key =="'redis_user_'+#id")
public User updateUserName (Long id, String userName) {
//此处调用getUser方法,该方法缓存注解失效,
// 所以这里还会执行SQL,将查询到数据库最新数据
User user = this.getUser(id);
if (user == null) {
return null;
}
user.setUserName(userName);
userDao.updateUser(user);
return user; //命中率低,所以不采用缓存机制
}
为什么会这样呢?其实在数据库事务中已经探讨过其原理,只要回顾一下就清楚了,那是因为Spring的缓存机制也是
基于SpringAOP的原理,而在Spring中AOP是通过动态代理技术来实现的,这里的updateUserName方法调用getUser
方法是类内部的自调用,并不存在代理对象的调用,这样便不会出现AOP,也就不会使用到标注在getUser上的缓存注解
去获取缓存的值了,这是需要注意的地方。要克服这个问题,可以参考《数据库的事务》一文中关于数据库事务那样用两个
服务(Service)类相互调用,或者直接从SpringIoC容器中获取代理对象来操作,这样就能成功克服自调用的问题了。