版权声明:版权归博主所有,转载请带上本文链接!联系方式:abel533@gmail.com https://blog.csdn.net/isea533/article/details/84563949
这里的缓存主要是用于 Service 层的,所以下面的配置,都是针对 service 模块的。
本文来自内部分享,对特殊信息进行了简单处理。
本文都是在以缓存来讲 Redis 的使用,实际上 Redis 不仅仅用于缓存,本身还是 NoSQL 数据库,大家可以自己查找学习 Redis 的常用场景。
<!--缓存--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.3.14.RELEASE</version> </dependency> <!--redis--> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.8.10.RELEASE</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.4.3</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
增加 spring-redis.xml
配置文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxIdle" value="${redis.pool.maxIdle}"/> <property name="maxTotal" value="${redis.pool.maxTotal}"/> <property name="maxWaitMillis" value="${redis.pool.maxWaitMillis}"/> <property name="testOnBorrow" value="${redis.pool.testOnBorrow}"/> <property name="testOnReturn" value="${redis.pool.testOnReturn}"/> </bean> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="${redis.master.ip}"/> <property name="port" value="${redis.master.port}"/> <property name="poolConfig" ref="jedisPoolConfig"/> </bean> <bean id="redisKeySerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/> <bean id="redisValueSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"/> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory"/> <property name="keySerializer" ref="redisKeySerializer"/> <property name="hashKeySerializer" ref="redisKeySerializer"/> <property name="valueSerializer" ref="redisValueSerializer"/> <property name="hashValueSerializer" ref="redisValueSerializer"/> </bean> <!--在 redis.properties 配置缓存详细信息--> <util:properties id="redisExpires" location="classpath*:META-INF/spring/redis.properties"/> <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"> <constructor-arg index="0" ref="redisTemplate"/> <!--默认缓存 10 分钟--> <property name="defaultExpiration" value="600"/> <property name="usePrefix" value="true"/> <property name="expires" ref="redisExpires"/> </bean> <!--启用 cache 注解--> <cache:annotation-driven cache-manager="cacheManager" proxy-target-class="true"/> </beans>
在 src/main/resources/
下面如果没有 META-INF/spring/
目录就创建一个,然后增加 redis.properties
配置,示例如下:
# 缓存名=有效时间 halfHour=1800 oneHour=3600 oneDay=86400 webSession=1800 user=1800
除了上面配置外,在系统的 application.properties 中还需要提供下面几个配置:
# redis 连接配置 redis.master.ip=10.10.10.100 redis.master.port=6379 # redis 连接池配置 redis.pool.maxIdle=200 redis.pool.maxTotal=1024 redis.pool.maxWaitMillis=1000 redis.pool.testOnBorrow=true redis.pool.testOnReturn=true
示例中, redis.propreties 配置如下:
# 数据库定义,缓存 30 天 databaseDef=2592000 # 数据库元数据,缓存 1 小时 databaseMeta=3600
这个示例在 数据库服务 上配置的,数据库服务中,查询次数远远大于新增、修改、删除的次数,非常适合使用缓存。
@Cacheable
@Override @Cacheable(value = "databaseDef", key = "'all'") public List<DatabaseDefinitionVo> selectAll() { return databaseDefinitionDao.selectAllVo(); }
特别注意: 所有
这些注解中, key
的值是 Spel
表达式,必须按照 Spel
要求来写。上面这个例子中,直接定义返回值的 key 是 all
字符串,需要加上单引号 '
括起来,下面还有其他用法。
在例子中,下面的方法也使用了这个注解:
@Override @Cacheable(value = "databaseDef", key = "#id.toString()") public DatabaseDefinition selectByPrimaryKey(Long id) { Assert.notNull(id, "数据库 ID 不能为空!"); DatabaseDefinition definition = databaseDefinitionDao.selectByPrimaryKey(id); Assert.notNull(definition, "数据库定义不存在!"); return definition; }
在上面注解中, key
中的 #id
指的是参数中的 id,在 IDEA 中会有自动提示。 .toString()
是调用 id
的方法,在系统中规定了 key
必须是 字符串
类型,所以当类型是 Long
的时候,需要转换。
使用缓存的目的就是为了减少上面两个方法调用时减少和数据库的交互,减小数据库的压力,这是两个主要的缓存数据的方法,下面的几个操作都和上面这两个方法有一定的 关系 。
重点:这里以及下面几个注解中,都指定了 value = "databaseDef"
,这里的意思是说,要使用前面配置中的 databaseDef
对应的配置,也就是会缓存 30天
。
@CachePut
@Override @CachePut(value = "databaseDef", key = "#result.id.toString()") public DatabaseDefinition save(DatabaseDefinition definition, CurrentUser userModel) throws ServiceException { //代码 return definition; }
更新缓存的方法需要注意的是 返回值
,在上面 save 方法中,有可能是新增,有可能是更新,不管是那个操作,当操作完成后,上面注解会根据 key
的值生成 key
,然后将方法的返回值作为 value
保存到缓存中。
这里 key
的写法中 #result
指代返回值, .id
是返回值的属性, .toString()
是调用 id
属性的方法,在系统中规定了 key
必须是 字符串
类型,所以当类型是 Long
的时候,需要转换。
这个方法上加的缓存还有问题,当新增或者更新后,通过 selectAll()
返回的值已经发生了变化,但是这里没有清除 all
的缓存值,会导致 selectAll()
出现脏数据,下面会通过 @Caching
注解改造这里。
@CacheEvict
@Override @CacheEvict(value = "databaseDef", key = "#id.toString()") public void deleteByPrimaryKey(Long id) throws ServiceException { DatabaseDefinition definition = selectByPrimaryKey(id); if (definition.getLoadState().equals(DatabaseDefinition.LoadState.UP)) { throw new ServiceException("请先卸载数据库!"); } databaseDefinitionDao.deleteByPrimaryKey(id); }
在上面新增或者修改的时候根据 id
缓存或者更新了缓存数据,这里当删除数据的时候,还需要清空对应的缓存数据。
在上面注解中, key
中的 #id
指的是参数中的 id,在 IDEA 中会有自动提示。
这个方法上加的缓存还有问题,当删除后,通过 selectAll()
返回的值已经发生了变化,但是这里没有清除 all
的缓存值,会导致 selectAll()
出现脏数据,下面会通过 @Caching
注解改造这里。
@Caching
上面两个注解中,都提到了 脏数据
,通过 @Caching
注解可以解决这个问题。
先修改第二个注解,来解决 save 时的脏数据:
@Override @Caching(put = @CachePut(value = "databaseDef", key = "#result.id.toString()"), evict = @CacheEvict(value = "databaseDef", key = "'all'")) public DatabaseDefinition save(DatabaseDefinition definition, CurrentUser userModel) throws ServiceException { //其他代码 return definition; }
前面说明,新增或者修改的时候, all
缓存中的数据已经不对了,因此这里在 put
的同时,使用 evict
将 'all'
中的数据清除,这就保证了 selelctAll
下次调用时,会重新从库中读取数据。
对上面的 删除 方法,也进行类似的修改:
@Override @Caching(evict = { @CacheEvict(value = "databaseDef", key = "#id.toString()"), @CacheEvict(value = "databaseDef", key = "'all'") }) public void deleteByPrimaryKey(Long id) throws ServiceException { DatabaseDefinition definition = selectByPrimaryKey(id); if (definition.getLoadState().equals(DatabaseDefinition.LoadState.UP)) { throw new ServiceException("请先卸载数据库!"); } databaseDefinitionDao.deleteByPrimaryKey(id); }
注意这里的 evict 是个数组,里面配置了两个 清除缓存 的配置。
@CacheConfig
在上面所有例子中,都指定了 value = "databaseDef"
,实际上可以通过在 类上
使用 @CacheConfig
注解配置当前类中的 cacheNames
值,配置后,如果和类上的 value
一样就不需要在每个注解单独配置。只有不同时再去指定,方法上的 value
值优先级更高。
@Service @CacheConfig(cacheNames = "databaseDef") public class DatabaseDefinitionServiceImpl implements DatabaseDefinitionService, DatabaseSqlExecuteService, ApplicationListener {
有了上面配置后,其他缓存名字相同的地方可以简化,例如删除方法修改后如下:
@Override @Caching(evict = { @CacheEvict(key = "#id.toString()"), @CacheEvict(key = "'all'") }) public void deleteByPrimaryKey(Long id) throws ServiceException { DatabaseDefinition definition = selectByPrimaryKey(id); if (definition.getLoadState().equals(DatabaseDefinition.LoadState.UP)) { throw new ServiceException("请先卸载数据库!"); } databaseDefinitionDao.deleteByPrimaryKey(id); }
除了上面针对 databaseDef 的缓存外,还有 databaseMeta 的配置:
@Override @Cacheable(value = "databaseMeta", key = "#databaseId.toString()") public List<TableVo> selectTablesByDatabaseId(Long databaseId) throws Exception { //代码 } @Override @Cacheable(value = "databaseMeta", key = "#databaseId + '_' + #tableName") public TableVo selectTableByDatabaseIdAndTableName(Long databaseId, String tableName) throws Exception { //代码 }
这两个方法是获取 数据库元数据 的,只有修改数据库表的时候才会变化,因此不存在清除缓存的情况,但是万一修改表后,想要新的 元数据 ,该怎么办?
因此增加了一个空的方法来清空数据,方法如下:
@Override @CacheEvict(value = "databaseMeta", allEntries = true) public void cleanTablesCache() { }
这里指定了 databaseMeta ,通过 allEntries = true 清空所有 key 的缓存。通过这个方法可以保证在有需要的时候清空所有元数据的缓存。
实际上如果想要更精确的清除,可以传入要清除的 databaseId
和 tableName
来更精确的清除。
调用 selectTablesByDatabaseId 多次时,输出的日志如下:
INFO c.n.d.u.DynamicDataSource - 当前数据源:默认数据源 DEBUG c.n.d.DataScopeContextProviderFilter - 服务调用返回前清除数据权限信息 INFO c.n.d.u.DynamicDataSource - 当前数据源:默认数据源 DEBUG c.n.d.d.D.selectByPrimaryKey - ==> Preparing: SELECT xxx (隐藏完整 SQL) DEBUG c.n.d.d.D.selectByPrimaryKey - ==> Parameters: 1(Long) DEBUG c.n.d.d.D.selectByPrimaryKey - <== Total: 1 INFO c.n.d.u.DynamicDataSource - 当前数据源:10.10.10.130/datareporting DEBUG c.n.d.DataScopeContextProviderFilter - 服务调用返回前清除数据权限信息 INFO c.n.d.u.DynamicDataSource - 当前数据源:默认数据源 DEBUG c.n.d.DataScopeContextProviderFilter - 服务调用返回前清除数据权限信息 INFO c.n.d.u.DynamicDataSource - 当前数据源:默认数据源 DEBUG c.n.d.DataScopeContextProviderFilter - 服务调用返回前清除数据权限信息
从日志可以看出来,只有第一次进行了数据库查询,后续通过日志看不到数据库操作。
初次调用时,WEB请求花了 700多ms,后面再次调用时,平均不到 30 ms,这就是缓存最明显的作用。
连接到 redis 服务后,查看所有 key ,结果如下:
redis@redissvr:~$ redis-cli 127.0.0.1:6379> keys * 1) "databaseMeta:1" 2) "databaseDef:all" 127.0.0.1:6379>
缓存中的数据都有 value 前缀,上面缓存了 all 和 id 为 1 的数据。
缓存注解是一种最简单的缓存方式,但是需要配合 value
属性的配置来使用,许多时候我们可能需要更精确的控制缓存,此时可以使用 RedisTemplate
来控制。
有关这部分的详细用法可以从网上搜索相关内容进行学习,这里列举一个简单的例子。
针对前面的 selectAll 我们换一种方式进行缓存。
首先注入下面的接口:
@Resource(name = "redisTemplate") private ValueOperations<String, List> valueOper;
修改 selectAll 方法如下:
@Override //@Cacheable(key = "'all'") public List<DatabaseDefinitionVo> selectAll() { List<DatabaseDefinitionVo> vos = valueOper.get("databaseDef:all"); if(vos != null){ return vos; } vos = databaseDefinitionDao.selectAllVo(); //缓存 1 小时 valueOper.set("databaseDef:all", vos, 1, TimeUnit.HOURS); return vos; }
首先通过 valueOper.get("databaseDef:all")
尝试获取缓存信息,如果存在就直接返回。
如果不存在,就查询数据库,然后将查询结果通过 set
进行缓存。
特别注意:上面的 key
,写的是 "databaseDef:all"
,也就是前缀需要自己加上,如果直接写成 all
,在 Redis 中的 key
就是 all
,不会自动增加前缀。
如果没有前缀,那么当不同系统都使用 all
时,数据就会混乱!
内网使用的服务器,特殊配置如下:
# By default protected mode is enabled. You should disable it only if # you are sure you want clients from other hosts to connect to Redis # even if no authentication is configured, nor a specific set of interfaces # are explicitly listed using the "bind" directive. # protected-mode yes protected-mode no
关闭了保护模式。
# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES # JUST COMMENT THE FOLLOWING LINE. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # bind 127.0.0.1
注释了绑定的 IP,这样可以让所有电脑访问 Redis。