§ 本文目标

通过本章节练习,掌握基于Spring Cache + Spring boot的入门级开发

§ 练习场景

额度管理信息查询提供缓存支持

前提:

  • 基于CRUD单表增删该查章节,完成该练习
  • Redis环境,基于Redis提供缓存功能;参考Redis部署章节下的Redis部署
  • 工具Postman PLSQL RedisDesktopManager

§ 操作步骤

§ 基础知识准备

Spring cache主要提供了以下几个Annotation:

注解 适用的方法类型 作用
@Cacheable 读数据 方法被调用时,先从缓存中读取数据,如果缓存没有找到数据,再调用方法获取数据,然后把数据添加到缓存中
@CachePut 写数据:如新增/修改数据 调用方法时会自动把相应的数据放入缓存
@CacheEvict 删除数据 调用方法时会从缓存中移除相应的数据

属性说明:

  • alue、cacheNames:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了
  • key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = "#p0"):使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档
  • condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = "#p0", condition = "#p0.length() < 3"),表示只有当第一个参数的长度小于3的时候才会被缓存。 unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断
  • keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的
  • cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用
  • org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定

§ 模板配置

引入缓存依赖包(微服务模板已默认引入)

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12

修改Apollo配置中心,微服务的redis配置,修改host为redis主机地址

spring:
    # 如不使用redis,请将以下配置删除
    redis: 
        database: 0         # Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
        host: localhost     # Redis的地址
        port: 6379          # Redis的端口
        password: 123456    # Redis的密码
        timeout: 60000      # 连接超时时间(毫秒)
        # 使用lettce最为redis客户端
        lettuce: 
            shutdown-timeout: 60000ms # 关闭超时时间
            pool: 
                max-active: 20        # 连接池最大连接数(使用负值表示没有限制) 默认 8
                max-wait: 20000ms     # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
                min-idle: 1           # 连接池中的最小空闲连接 默认 0
                max-idle: 20          # 连接池中的最大空闲连接 默认 8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

§ Service开发

实现查询、修改、删除的缓存实时更新功能

@Service
public class LimitInfoService extends CommonService {
    @Autowired
    private LimitInfoMapper limitInfoMapper;
    @Override
    protected CommonMapper<?> getMapper() {
        return limitInfoMapper;
    }
    
    /**
     * 额度信息查询.
     * <p>
     * 第一次查询,从数据库获取,如果返回结果为空,则不缓存
     * 第二次查询,从缓存获取。
     * 
     * @param limitId
     * @return
     */
    @Cacheable(value = "LimitInfo", key = "#limitId", unless= "#result == null")
    public LimitInfo queryWithCache(String limitId) {
          return limitInfoMapper.selectByPrimaryKey(limitId);
    }
    
    /**
     * 缓存修改.
     * <p>
     * 修改对象后,更新缓存对象.
     * 
     * @param limitInfo
     * @return
     */
    @CachePut(value = "LimitInfo", key = "#limitInfo.limitId", unless= "#result == null")
    public LimitInfo updateWithCache(LimitInfo limitInfo) {
          limitInfoMapper.updateByPrimaryKey(limitInfo);
          return limitInfo;
    }
    
    /**
     * 缓存删除.
     * <p>
     * 删除数据库记录后,同时删除缓存信息.
     * 
     * @param limitInfo
     * @return
     */
    @CacheEvict(value = "LimitInfo", key = "#limitId")
    public void deleteWithCache(String limitId) {
          limitInfoMapper.deleteByPrimaryKey(limitId);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

§ Resource开发

提供API接口

@RestController
@RequestMapping("/api/limitinfo")
public class LimitInfoResource extends CommonResource<LimitInfo, String> {
          @Autowired
          private LimitInfoService limitInfoService;
          @Override
          protected CommonService getCommonService() {
                    // TODO 自动生成的方法存根
                    return limitInfoService;
          }
          @GetMapping(value = "/query")
          public LimitInfo query(@RequestParam(value = "limitId") String limitId) {
                    return limitInfoService.selectByPrimaryKey(limitId);
          }
          /**
           * 基于缓存实现查询.
           * 
           * @param limitId
           * @return
           */
          @GetMapping(value = "/querycache")
          public LimitInfo queryWithCache(@RequestParam(value = "limitId") String limitId) {
                    return limitInfoService.queryWithCache(limitId);
          }
          /**
           * 修改同时更新缓存.
           * 
           * @param limitId
           * @return
           */
          @PostMapping(value = "/updatecache")
          public LimitInfo updateWithCache(@RequestBody LimitInfo limitInfo) {
                    return limitInfoService.updateWithCache(limitInfo);
          }
          /**
           * 删除同时清理缓存.
           * 
           * @param limitId
           * @return
           */
          @GetMapping(value = "/deletecache")
          public void deleteWithCache(@RequestParam(value = "limitId") String limitId) {
                    limitInfoService.deleteWithCache(limitId);
          }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

§ 测试

§ 查询

第一次查询,从数据库查询数据后,设置信息到缓存数据库,耗时150ms+

cache_01

缓存数据信息:

cache_02

第二次查询,响应耗时90ms左右,降低了2/5,查询性能有了很大的提升

cache_03

§ 修改limitDesc内容为airabl_update

http://localhost:6001/api/limitinfo/updatecache

{
 "limitId": "00000",
 "limitCode": "101010",
 "limitAmt": "2222222",
 "limitDesc": "airabl_update",
 "limitUserId": "air"
 }
1
2
3
4
5
6
7

cache_04

cache_05

缓存数据库信息,成功更新为最新记录

cache_06

§ 删除

http://localhost:6001/api/limitinfo/deletecache?limitId=00000

cache_07

redis缓存数据库结果查询,信息已经被清理,数据库记录被删除

cache_08

§ 进阶

§ Spring Cache 基本接口

Spring cache只是对缓存的抽象,并不是缓存的一个具体实现。不同的具体缓存实现方案可能会有各自不同的特性。Spring cache作为缓存的抽象,它只能抽象出共同的属性/功能,对于无法统一的那部分属性/功能它就无能为力了,这部分差异化的属性/功能应该由具体的缓存实现者去配置。

Spring框架提供了基础的两个接口类,用户第三缓存应用进行扩展

缓存管理接口

package org.springframework.cache;
import java.util.Collection;
/**
 * Spring's central cache manager SPI.
 * Allows for retrieving named {@link Cache} regions.
 *
 * @author Costin Leau
 * @since 3.1
 */
public interface CacheManager {
          /**
           * Return the cache associated with the given name.
           * @param name the cache identifier (must not be {@code null})
           * @return the associated cache, or {@code null} if none found
           */
          Cache getCache(String name);
          /**
           * Return a collection of the cache names known by this manager.
           * @return the names of all caches known by the cache manager
           */
          Collection`<String>` getCacheNames();
}
 
package org.springframework.cache;
import java.util.concurrent.Callable;
/**
 * Interface that defines common cache operations.
 *
 * <b>Note:</b> Due to the generic use of caching, it is recommended that
 * implementations allow storage of <tt>null</tt> values (for example to
 * cache methods that return {@code null}).
 *
 * @author Costin Leau
 * @author Juergen Hoeller
 * @author Stephane Nicoll
 * @since 3.1
 */
public interface Cache {
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

§ 常用实现类

常用的缓存,已经提供了实现类:

image-20220421134543846

如果系统未配置任何第三方缓存信息,系统默认通过ConcurrentHashMap、ConcurrentMapCache、SimpleCacheManager,由JVM提供缓存支持

§ 注意事项

只有在Bean的入口方法,注解才能生效(Bean内部的方法调用,注解不会生效)

最后更新于: 5/5/2022, 6:41:08 PM