集成 Spring Redis 缓存

版权声明:版权归博主所有,转载请带上本文链接!联系方式: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

这个示例在 数据库服务
上配置的,数据库服务中,查询次数远远大于新增、修改、删除的次数,非常适合使用缓存。

1. 缓存数据 @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天

2. 更新缓存 @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
注解改造这里。

3. 清除缓存 @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
注解改造这里。

4. 组合使用 @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
是个数组,里面配置了两个 清除缓存
的配置。

5. 全局配置 @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
来控制。

四、通过 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
时,数据就会混乱!

五、Redis 服务器配置注意事项

内网使用的服务器,特殊配置如下:

# 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。

原文 

https://blog.csdn.net/isea533/article/details/84563949

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 集成 Spring Redis 缓存

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址