spring的缓存(cache)-本地

时间:2022-07-25
本文章向大家介绍spring的缓存(cache)-本地,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

注:本文篇幅有点长,所以建议各位下载源码学习。(如需要请收藏!转载请声明来源,谢谢!)

代码下载:https://gitee.com/hong99/spring/issues/I1N1DF

什么是缓存(cache)?

缓存简称cache,全称:即高速缓冲存储器(Cache Memory)。

缓存分为:硬件缓存和软件缓存。

硬件缓存有CPU、内存条、硬盘等...以内存条为主,可以参照:

https://baike.baidu.com/item/%E7%BC%93%E5%AD%98

软件缓存:分为客户端缓存和服务端缓存。

客户端缓存:就类似我们常用的浏览器,QQ、微信、手机的应用账号密码等或者,方便不用请求服务端直接展示出来,提高效率。

服务端缓存:某些服务的应用,自身的缓存,当客户端请求的时候直接从缓存中快速获取,而减少直接请求数据库,提升服务端性能和效率。

不管是硬件还是软件缓存最终的目的都是为了提升系统性能和效率,减少不必要的开销。

缓存解决了什么问题?

当用户量小的时候,QPS可能就几个或者几十个,这是没什么问题的,没觉得需要什么缓存,服务、db都坑得住,对响应时间的要求不高。但是当用户量QPS是百万甚至千万的时候,如果这时候服务没有缓存,可以说几乎是服务是秒崩溃的...如下图,若服务直接都去调用db,那db会导致db连接超时,甚至崩溃,接着导致服务异常,最后服务崩溃....

所以缓存主要是解决用户快速访问/请求,以达到更高的用户体验,间接使服务以免由于用户量过大而导致系统奔溃或者出现各种异常,并且提升系统的鲁棒性

相关概念:

PV(Page View):页面访问量,每次用户访问或者刷新页面都会被计算在内。

QPS(Query Per Second):每秒查询数,每秒系统能够处理的查询请求次数。

TPS(Transactions Per Second):每秒事务数,每秒系统能够处理的事务次数。

缓存的使用场景?缓存有哪些策略?

数据使用的次数非常频繁导致每次都查库或者必须建立缓存以备不时之需。当然缓存也有基于业务场景建立,也有基础安全场景,以及用户体验等。但不管基础何种场景建立缓存的目的是需要非常明确,并且是必要解决因为没有缓存导致的一系列问题。否则乱建缓存可能引发缓存数据不一致,死数据等问题。

缓存创建的场景有两种:被动创建、初始化创建;

被动创建:需要用到的时候才创建;

场景:用户登陆,当第一次登陆的时候需要获取用户信息,先查数据库然后同步到缓存中,当第二次获取用户信息的时候直接从缓存中获取。

初始化创建:随着服务的启动就存在了;

场景:创建新用户的时候就该用户放到缓存中,当用户每次获取的时候直接从缓存中获取,若获取不到再到数据库中获取,减少查询db的次数。

如何避免缓存与数据库数据不一致?

1.当原始数据(数据库)被更新时,同步先删除缓存数据再更新。

2.需要为缓存数据加过期时间;

缓存有哪些策略?

先进先出策略 FIFO(First In,First Out)

最少使用策略 LFU(Least Frequently Used)

最近最少使用策略 LRU(Least Recently Used)

缓存有哪些?有哪些框架?

缓存大致分为两在种,本地缓存和分布式缓存,当然如果考虑分级的话还有一级、二级、三级等缓存,但这里以前种来区别。

本地缓存

hashmap,Guava ,ehcache,spring cache

分布式缓存

redis,memcached

代码下载:https://gitee.com/hong99/spring/issues/I1N1DF

源码实现

项目结构

注:项目大部分代码与ORM中的spring_mybatis_plus一致,请参考!

本地缓存实现

hashmap实现

com.hong.spring.service.IUserService 新增方法

/**
 *
 * 功能描述:通过缓存查询
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2020/8/27 16:56
 */
User findLocalCacheById(Integer id);

com.hong.spring.service.impl.UserServiceImpl 新增实现

@Override
public User findLocalCacheById(Integer id) {
    if(null==id){
        log.info("id为空!");
        return null;
    }
    boolean userExist = map.containsKey(id);
    if(userExist){
        log.info("从缓存获取用户信息:{}", JSONObject.toJSONString(map.get(id)));
        return map.get(id);
    }else{
        User user = userMapper.findById(id);
        log.info("从数据库中获取用户信息{}",JSONObject.toJSONString(user));
        if(null!=user){
            map.put(id,user);
        }
        return user;
    }
}

com.hong.spring.service.UserServiceTest 新增测试

@Test
public void findCacheById(){

    for(int i=0;i<10;i++){
        log.info("第{}次,获取结果{}",i,JSONObject.toJSONString(userService.findLocalCacheById(1)));
    }
}

结果

17:03:10.737 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从数据库中获取用户信息{"age":100,"id":1,"username":"333"}
17:03:10.738 [main] INFO  com.hong.spring.service.UserServiceTest - 第0次,获取结果{"age":100,"id":1,"username":"333"}
17:03:10.738 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从缓存获取用户信息:{"age":100,"id":1,"username":"333"}
17:03:10.738 [main] INFO  com.hong.spring.service.UserServiceTest - 第1次,获取结果{"age":100,"id":1,"username":"333"}
17:03:10.738 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从缓存获取用户信息:{"age":100,"id":1,"username":"333"}
17:03:10.739 [main] INFO  com.hong.spring.service.UserServiceTest - 第2次,获取结果{"age":100,"id":1,"username":"333"}
17:03:10.739 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从缓存获取用户信息:{"age":100,"id":1,"username":"333"}
17:03:10.739 [main] INFO  com.hong.spring.service.UserServiceTest - 第3次,获取结果{"age":100,"id":1,"username":"333"}
17:03:10.739 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从缓存获取用户信息:{"age":100,"id":1,"username":"333"}
17:03:10.739 [main] INFO  com.hong.spring.service.UserServiceTest - 第4次,获取结果{"age":100,"id":1,"username":"333"}
17:03:10.740 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从缓存获取用户信息:{"age":100,"id":1,"username":"333"}
17:03:10.740 [main] INFO  com.hong.spring.service.UserServiceTest - 第5次,获取结果{"age":100,"id":1,"username":"333"}
17:03:10.740 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从缓存获取用户信息:{"age":100,"id":1,"username":"333"}
17:03:10.740 [main] INFO  com.hong.spring.service.UserServiceTest - 第6次,获取结果{"age":100,"id":1,"username":"333"}
17:03:10.740 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从缓存获取用户信息:{"age":100,"id":1,"username":"333"}
17:03:10.740 [main] INFO  com.hong.spring.service.UserServiceTest - 第7次,获取结果{"age":100,"id":1,"username":"333"}
17:03:10.741 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从缓存获取用户信息:{"age":100,"id":1,"username":"333"}
17:03:10.741 [main] INFO  com.hong.spring.service.UserServiceTest - 第8次,获取结果{"age":100,"id":1,"username":"333"}
17:03:10.741 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从缓存获取用户信息:{"age":100,"id":1,"username":"333"}
17:03:10.741 [main] INFO  com.hong.spring.service.UserServiceTest - 第9次,获取结果{"age":100,"id":1,"username":"333"}

总结:hashmap缓存作为缓存有一个缺点就是若没有设置过期时间会导致缓存越堆越多,导致内存被占用过多,而且永远没有失效,数据更新或修改的时候也没有更新到会导致不一致问题,当然也可以在修改的时候将map中的数据移除,但是还是存在没有过期导致死数据问题。

Guava 的实现

简介:Guava是Google的一组核心Java库,其中包括新的集合类型(例如多图和多集),不可变的集合,图形库以及用于并发,I / O,哈希,缓存,基元,字符串等的实用程序!它广泛用于Google的大多数Java项目中,也被许多其他公司广泛使用。

本地实现

com.hong.spring.service.IUserService#findGuavaCacheById

/**
 *
 * 功能描述:通过guava缓存获取
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2020/8/27 17:53
 */
User findGuavaCacheById(Integer id);

com.hong.spring.service.impl.UserServiceImpl#findGuavaCacheById

@Override
public User findGuavaCacheById(Integer id) {
    if(null==id){
        log.info("获取guava缓存未传id!");
        return null;
    }
    User user = guavaCache.getIfPresent(id);
    if(user!=null){
        log.info("从guava缓存获取用户信息:{}", JSONObject.toJSONString(map.get(id)));
        return user;
    }else{
        user = userMapper.findById(id);
        log.info("从数据库中获取用户信息{}",JSONObject.toJSONString(user));
        if(null!=user){
            guavaCache.put(id,user);
        }
        return user;
    }
}

com.hong.spring.service.UserServiceTest#findGuavaCacheById

@Test
public void findGuavaCacheById() throws InterruptedException {
    for(int i=0;i<10;i++){
        log.info("第{}次,获取结果{}",i,JSONObject.toJSONString(userService.findGuavaCacheById(1)));
    }
    log.info("睡上10秒再拿!");
    Thread.sleep(11000);
    log.info("重新获取结果{}",JSONObject.toJSONString(userService.findGuavaCacheById(1)));
}

结果

18:02:20.861 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
18:02:20.868 [main] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5c748168] was not registered for synchronization because synchronization is not active
18:02:20.878 [main] DEBUG org.mybatis.spring.transaction.SpringManagedTransaction - JDBC Connection [com.mysql.jdbc.JDBC4Connection@47547132] will not be managed by Spring
18:02:20.882 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==>  Preparing: SELECT * FROM user WHERE id = ? 
18:02:20.908 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==> Parameters: 1(Integer)
18:02:20.928 [main] DEBUG com.hong.spring.dao.UserMapper.findById - <==      Total: 1
18:02:20.932 [main] DEBUG com.alibaba.druid.pool.PreparedStatementPool - stmt enter cache
18:02:20.933 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5c748168]
18:02:20.950 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从数据库中获取用户信息{"age":100,"id":1,"username":"333"}
18:02:20.954 [main] INFO  com.hong.spring.service.UserServiceTest - 第0次,获取结果{"age":100,"id":1,"username":"333"}
18:02:20.954 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从guava缓存获取用户信息:null
18:02:20.955 [main] INFO  com.hong.spring.service.UserServiceTest - 第1次,获取结果{"age":100,"id":1,"username":"333"}
18:02:20.955 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从guava缓存获取用户信息:null
18:02:20.955 [main] INFO  com.hong.spring.service.UserServiceTest - 第2次,获取结果{"age":100,"id":1,"username":"333"}
18:02:20.955 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从guava缓存获取用户信息:null
18:02:20.956 [main] INFO  com.hong.spring.service.UserServiceTest - 第3次,获取结果{"age":100,"id":1,"username":"333"}
18:02:20.956 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从guava缓存获取用户信息:null
18:02:20.956 [main] INFO  com.hong.spring.service.UserServiceTest - 第4次,获取结果{"age":100,"id":1,"username":"333"}
18:02:20.956 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从guava缓存获取用户信息:null
18:02:20.956 [main] INFO  com.hong.spring.service.UserServiceTest - 第5次,获取结果{"age":100,"id":1,"username":"333"}
18:02:20.957 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从guava缓存获取用户信息:null
18:02:20.957 [main] INFO  com.hong.spring.service.UserServiceTest - 第6次,获取结果{"age":100,"id":1,"username":"333"}
18:02:20.957 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从guava缓存获取用户信息:null
18:02:20.957 [main] INFO  com.hong.spring.service.UserServiceTest - 第7次,获取结果{"age":100,"id":1,"username":"333"}
18:02:20.957 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从guava缓存获取用户信息:null
18:02:20.958 [main] INFO  com.hong.spring.service.UserServiceTest - 第8次,获取结果{"age":100,"id":1,"username":"333"}
18:02:20.958 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从guava缓存获取用户信息:null
18:02:20.958 [main] INFO  com.hong.spring.service.UserServiceTest - 第9次,获取结果{"age":100,"id":1,"username":"333"}
18:02:20.958 [main] INFO  com.hong.spring.service.UserServiceTest - 睡上10秒再拿!
18:02:31.968 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
18:02:31.968 [main] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7cf283e1] was not registered for synchronization because synchronization is not active
18:02:31.969 [main] DEBUG org.mybatis.spring.transaction.SpringManagedTransaction - JDBC Connection [com.mysql.jdbc.JDBC4Connection@47547132] will not be managed by Spring
18:02:31.969 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==>  Preparing: SELECT * FROM user WHERE id = ? 
18:02:31.969 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==> Parameters: 1(Integer)
18:02:31.970 [main] DEBUG com.hong.spring.dao.UserMapper.findById - <==      Total: 1
18:02:31.970 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7cf283e1]
18:02:31.971 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从数据库中获取用户信息{"age":100,"id":1,"username":"333"}
18:02:31.971 [main] INFO  com.hong.spring.service.UserServiceTest - 重新获取结果{"age":100,"id":1,"username":"333"}

总结:可以看出guava的性能非常优秀,而且可以设置过期时间,当然该实现只是guava功能的冰山一角,guava还有很多其他优秀的功能,可以参照以下文章。

参考文章:

api:https://guava.dev/releases/snapshot-jre/api/docs/

github:https://github.com/google/guava

https://blog.csdn.net/pzjtian/article/details/106910046

https://cloud.tencent.com/developer/article/1074135

https://www.cnblogs.com/rickiyang/p/11074159.html

spring集成guava 实现注解方法

applicationContext-mybatis_plus.xml添加如下

<!--开启缓存注解-->
<cache:annotation-driven />

<bean id="cacheManager" class="org.springframework.cache.guava.GuavaCacheManager">
   <!--初始化 500个 最大5000个 过期时间10s-->
   <property name="cacheSpecification" value="initialCapacity=500,maximumSize=5000,expireAfterAccess=10s,softValues" />
   <property name="cacheNames">
      <list>
         <value>questionCreatedTrack</value>
      </list>
   </property>
</bean>

com.hong.spring.service.IUserService#findSpringGuavaCacheById

/**
 *
 * 功能描述:通过spring缓存获取
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2020/8/27 18:54
 */
User findSpringGuavaCacheById(Integer id);

com.hong.spring.service.impl.UserServiceImpl#findSpringGuavaCacheById

@Cacheable(value = "questionCreatedTrack",key="#id",condition = "#id>0")
@Override
public User findSpringGuavaCacheById(Integer id) {
    if(null==id){
        return null;
    }
    User user = userMapper.findById(id);
    log.info("从数据库中获取用户信息{}",JSONObject.toJSONString(user));
    return user;
}

com.hong.spring.service.UserServiceTest#findSpringGuavaCacheById

/**
 *
 * 功能描述:通过spring获取缓存
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2020/8/27 19:04
 */
@Test
public void findSpringGuavaCacheById() throws InterruptedException {
    for(int i=0;i<10;i++){
        log.info("第{}次,获取结果{}",i,JSONObject.toJSONString(userService.findSpringGuavaCacheById(1)));
    }
    log.info("睡上10秒再拿!");
    Thread.sleep(11000);
    log.info("重新获取结果{}",JSONObject.toJSONString(userService.findGuavaCacheById(1)));
}

结果

19:01:57.450 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
19:01:57.455 [main] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1706a5c9] was not registered for synchronization because synchronization is not active
19:01:57.463 [main] DEBUG org.mybatis.spring.transaction.SpringManagedTransaction - JDBC Connection [com.mysql.jdbc.JDBC4Connection@3971f0fe] will not be managed by Spring
19:01:57.465 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==>  Preparing: SELECT * FROM user WHERE id = ? 
19:01:57.483 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==> Parameters: 1(Integer)
19:01:57.498 [main] DEBUG com.hong.spring.dao.UserMapper.findById - <==      Total: 1
19:01:57.500 [main] DEBUG com.alibaba.druid.pool.PreparedStatementPool - stmt enter cache
19:01:57.501 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1706a5c9]
19:01:57.517 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从数据库中获取用户信息{"age":100,"id":1,"username":"333"}
19:01:57.521 [main] INFO  com.hong.spring.service.UserServiceTest - 第0次,获取结果{"age":100,"id":1,"username":"333"}
19:01:57.522 [main] INFO  com.hong.spring.service.UserServiceTest - 第1次,获取结果{"age":100,"id":1,"username":"333"}
19:01:57.522 [main] INFO  com.hong.spring.service.UserServiceTest - 第2次,获取结果{"age":100,"id":1,"username":"333"}
19:01:57.523 [main] INFO  com.hong.spring.service.UserServiceTest - 第3次,获取结果{"age":100,"id":1,"username":"333"}
19:01:57.524 [main] INFO  com.hong.spring.service.UserServiceTest - 第4次,获取结果{"age":100,"id":1,"username":"333"}
19:01:57.524 [main] INFO  com.hong.spring.service.UserServiceTest - 第5次,获取结果{"age":100,"id":1,"username":"333"}
19:01:57.525 [main] INFO  com.hong.spring.service.UserServiceTest - 第6次,获取结果{"age":100,"id":1,"username":"333"}
19:01:57.525 [main] INFO  com.hong.spring.service.UserServiceTest - 第7次,获取结果{"age":100,"id":1,"username":"333"}
19:01:57.526 [main] INFO  com.hong.spring.service.UserServiceTest - 第8次,获取结果{"age":100,"id":1,"username":"333"}
19:01:57.526 [main] INFO  com.hong.spring.service.UserServiceTest - 第9次,获取结果{"age":100,"id":1,"username":"333"}
19:01:57.526 [main] INFO  com.hong.spring.service.UserServiceTest - 睡上10秒再拿!
19:02:08.527 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
19:02:08.527 [main] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d2dc9d2] was not registered for synchronization because synchronization is not active
19:02:08.528 [main] DEBUG org.mybatis.spring.transaction.SpringManagedTransaction - JDBC Connection [com.mysql.jdbc.JDBC4Connection@3971f0fe] will not be managed by Spring
19:02:08.528 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==>  Preparing: SELECT * FROM user WHERE id = ? 
19:02:08.528 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==> Parameters: 1(Integer)
19:02:08.551 [main] DEBUG com.hong.spring.dao.UserMapper.findById - <==      Total: 1
19:02:08.552 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6d2dc9d2]
19:02:08.552 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从数据库中获取用户信息{"age":100,"id":1,"username":"333"}
19:02:08.553 [main] INFO  com.hong.spring.service.UserServiceTest - 重新获取结果{"age":100,"id":1,"username":"333"}
19:02:08.557 [Thread-1] INFO  com.alibaba.druid.pool.DruidDataSource - {dataSource-1} closing ...
19:02:08.559 [Thread-1] DEBUG com.alibaba.druid.pool.PreparedStatementPool - stmt exit cache
19:02:08.559 [Thread-1] INFO  com.alibaba.druid.pool.DruidDataSource - {dataSource-1} closed

总结:通过spring的cache以guava实现缓存,是spring cache的一种实现,可以完成缓存效果,但是该情况存在一个问题就是增加和修改,比较麻烦需要特定方法实现,比较麻烦。所以通过缓存过期时间来实现更好。关于api请看下文。

参考文章:

https://www.cnblogs.com/fingerboy/p/9549937.html

spring集成spring cache

类图

思维导图

spring 3.1开始支持声名式缓存到4.0后就全面支持JSR-107。其实spring cahce本质不是一个具体的缓存实现方案,只是作为门面来使用类似于事务的注解,具体实现的还是其他缓存框架比如:ehcache、guava、redis等。该功能十分强大,并且非常方便的支持了缓存的使用。相关功能可以参照上图。

相关文章:

官方:https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/cache.html

官方:https://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/aop.html#aop-aj-ltw-spring

简单的spring cache配置

applicationContext-mybatis_plus_cache.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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
      xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:cache="http://www.springframework.org/schema/cache"
      xmlns:p="http://www.springframework.org/schema/p"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">


   <!-- 配置组件扫描 -->
   <context:component-scan base-package="com.hong.spring"></context:component-scan>
   <!--加载配置文件-->
   <context:property-placeholder location="classpath:jdbc.properties"/>

   <!-- 开启注解 -->
   <context:annotation-config />
   <!--开启注解事务-->
   <tx:annotation-driven transaction-manager="transactionManager" />
   <!--放行静态资源-->
   <mvc:default-servlet-handler />


   <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
        id="internalResourceViewResolver">
      <!-- 前缀 -->
      <property name="prefix" value="/WEB-INF/pages/" />
      <!-- 后缀 -->
      <property name="suffix" value=".html" />
      <property name="contentType" value="text/html"/>

   </bean>

   <!--开启mvc注解事务-->
   <!-- 定义注解驱动 -->
   <mvc:annotation-driven>
      <mvc:message-converters>
         <!-- 设置支持中文 -->
         <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes">
               <list>
                  <value>text/plain;charset=UTF-8</value>
                  <value>text/html;charset=UTF-8</value>
               </list>
            </property>
         </bean>
         <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/>
      </mvc:message-converters>
   </mvc:annotation-driven>


   <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
      <!-- 基础配置 -->
      <property name="url" value="${jdbc.url}"></property>
      <property name="driverClassName" value="${jdbc.driver}"></property>
      <property name="username" value="${jdbc.user}"></property>
      <property name="password" value="${jdbc.password}"></property>

      <!-- 关键配置 -->
      <!-- 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 -->
      <property name="initialSize" value="3" />
      <!-- 最小连接池数量 -->
      <property name="minIdle" value="2" />
      <!-- 最大连接池数量 -->
      <property name="maxActive" value="15" />
      <!-- 配置获取连接等待超时的时间 -->
      <property name="maxWait" value="10000" />

      <!-- 性能配置 -->
      <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
      <property name="poolPreparedStatements" value="true" />
      <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />

      <!-- 其他配置 -->
      <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
      <property name="timeBetweenEvictionRunsMillis" value="60000" />
      <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
      <property name="minEvictableIdleTimeMillis" value="300000" />
      <!--   建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,
                  执行validationQuery检测连接是否有效。-->
      <property name="testWhileIdle" value="true" />
      <!-- 这里建议配置为TRUE,防止取到的连接不可用 ,申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。-->
      <property name="testOnBorrow" value="true" />
      <!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 -->
      <property name="testOnReturn" value="false" />
   </bean>

   <!--事务管理器-->
   <!-- sqlSessionFactory -->
   <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
      <!-- 加载 MyBatis 的配置文件 -->
      <property name="configLocation" value="classpath:mybatis-plus.xml"/>
      <!-- 数据源 -->
      <property name="dataSource" ref="dataSource"/>
      <!-- 所有配置的mapper文件 -->
      <property name="mapperLocations" value="classpath*:com/hong/spring/mapper/*.xml" />
   </bean>

   <!-- Mapper 扫描器 -->
   <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
      <!-- 扫描 包下的组件 -->
      <property name="basePackage" value="com.hong.spring.dao" />
      <!-- 关联mapper扫描器 与 sqlsession管理器 -->
      <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
   </bean>
   <!--事务配置-->
   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource" />
   </bean>

   <!--开启缓存注解-->
   <cache:annotation-driven  />

   <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
      <property name="caches">
         <set>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
                  p:name="default" />

            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"
                  p:name="questionCreatedTrack" />
         </set>
      </property>
   </bean>
</beans>

com.hong.spring.service.UserServiceCaceTest#findSpringGuavaCacheById

package com.hong.spring.service;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hong.spring.entity.User;
import com.hong.spring.utils.DataResponse;
import lombok.extern.log4j.Log4j2;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

/**
 * @Auther: csh
 * @Date: 2020/8/24 11:17
 * @Description:
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-mybatis_plus_cache.xml")
@Log4j2
public class UserServiceCaceTest {
    @Autowired
    private IUserService userService;


    /**
     *
     * 功能描述:通过spring获取缓存
     *
     * @param:
     * @return:
     * @auther: csh
     * @date: 2020/8/27 19:04
     */
    @Test
    public void findSpringGuavaCacheById() throws InterruptedException {
        for(int i=0;i<10;i++){
            log.info("第{}次,获取结果{}",i,JSONObject.toJSONString(userService.findSpringGuavaCacheById(1)));
        }
        log.info("睡上10秒再拿!");
        Thread.sleep(11000);
        log.info("重新获取结果{}",JSONObject.toJSONString(userService.findSpringGuavaCacheById(1)));
    }

    @Test
    public void deleteCacheTest(){
        log.info("获取结果{}",JSONObject.toJSONString(userService.findSpringGuavaCacheById(1)));
        userService.deleteCache(1);
        log.info("重新获取结果{}",JSONObject.toJSONString(userService.findSpringGuavaCacheById(1)));
    }

}

结果

11:39:11.866 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从数据库中获取用户信息{"age":100,"id":1,"username":"333"}
11:39:11.868 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 第0次,获取结果{"age":100,"id":1,"username":"333"}
11:39:11.869 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 第1次,获取结果{"age":100,"id":1,"username":"333"}
11:39:11.870 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 第2次,获取结果{"age":100,"id":1,"username":"333"}
11:39:11.870 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 第3次,获取结果{"age":100,"id":1,"username":"333"}
11:39:11.870 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 第4次,获取结果{"age":100,"id":1,"username":"333"}
11:39:11.871 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 第5次,获取结果{"age":100,"id":1,"username":"333"}
11:39:11.871 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 第6次,获取结果{"age":100,"id":1,"username":"333"}
11:39:11.871 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 第7次,获取结果{"age":100,"id":1,"username":"333"}
11:39:11.871 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 第8次,获取结果{"age":100,"id":1,"username":"333"}
11:39:11.872 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 第9次,获取结果{"age":100,"id":1,"username":"333"}
11:39:11.872 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 睡上10秒再拿!

八月 28, 2020 11:39:22 上午 org.springframework.context.support.GenericApplicationContext doClose
信息: Closing org.springframework.context.support.GenericApplicationContext@759d26fb: startup date [Fri Aug 28 11:39:09 CST 2020]; root of context hierarchy
11:39:22.872 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 重新获取结果{"age":100,"id":1,"username":"333"}

com.hong.spring.service.UserServiceCaceTest#deleteCacheTest清除缓存:结果

11:53:15.903 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从数据库中获取用户信息{"age":100,"id":1,"username":"333"}
11:53:15.905 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 获取结果{"age":100,"id":1,"username":"333"}
11:53:15.907 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 调用了清除缓存:1
11:53:15.908 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
11:53:15.909 [main] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6fd5717c] was not registered for synchronization because synchronization is not active
11:53:15.910 [main] DEBUG org.mybatis.spring.transaction.SpringManagedTransaction - JDBC Connection [com.mysql.jdbc.JDBC4Connection@5e1218b4] will not be managed by Spring
11:53:15.910 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==>  Preparing: SELECT * FROM user WHERE id = ? 
11:53:15.911 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==> Parameters: 1(Integer)
11:53:15.912 [main] DEBUG com.hong.spring.dao.UserMapper.findById - <==      Total: 1
11:53:15.912 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6fd5717c]
11:53:15.913 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从数据库中获取用户信息{"age":100,"id":1,"username":"333"}
11:53:15.913 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 重新获取结果{"age":100,"id":1,"username":"333"}

验证:若没有开启 mode="aspectj" 同一类下的方法互调是没有缓存效果,因为aop管理不到。

com.hong.spring.service.IUserService#findByUserId

/**
 *
 * 功能描述:通过userId
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2020/8/28 14:18
 */
User findByUserId(Integer id);

com.hong.spring.service.impl.UserServiceImpl#findByUserId

@Override
public User findByUserId(Integer id) {
    return this.findSpringGuavaCacheById(id);
}

结果如下:

14:20:21.264 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==>  Preparing: SELECT * FROM user WHERE id = ? 
14:20:21.290 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==> Parameters: 1(Integer)
14:20:21.305 [main] DEBUG com.hong.spring.dao.UserMapper.findById - <==      Total: 1
14:20:21.307 [main] DEBUG com.alibaba.druid.pool.PreparedStatementPool - stmt enter cache
14:20:21.308 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@73044cdf]
14:20:21.323 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从数据库中获取用户信息{"age":100,"id":1,"username":"333"}
14:20:21.324 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 第一次拿:{"age":100,"id":1,"username":"333"}
14:20:21.325 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
14:20:21.325 [main] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1d96d872] was not registered for synchronization because synchronization is not active
14:20:21.326 [main] DEBUG org.mybatis.spring.transaction.SpringManagedTransaction - JDBC Connection [com.mysql.jdbc.JDBC4Connection@58294867] will not be managed by Spring
14:20:21.327 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==>  Preparing: SELECT * FROM user WHERE id = ? 
14:20:21.327 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==> Parameters: 1(Integer)
14:20:21.328 [main] DEBUG com.hong.spring.dao.UserMapper.findById - <==      Total: 1
14:20:21.329 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1d96d872]
14:20:21.329 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从数据库中获取用户信息{"age":100,"id":1,"username":"333"}
14:20:21.329 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 第二次拿:{"age":100,"id":1,"username":"333"}

可以发现:第二次查询还是从库里面查询的导致缓存失效,所以默认的proxy是同类下面aop管理不到。

applicationContext-mybatis_plus_cache.xml添加mode="aspectj"尝试

<!--开启缓存注解-->
<cache:annotation-driven cache-manager="cacheManager"  mode="aspectj"   />

新增

<cache:advice id="cacheAdvice" cache-manager="cacheManager">
    <cache:caching cache="questionCreatedTrack">
        <cache:cacheable method="findById" key="#p0"/>
        <cache:cacheable method="find*" key="#p0"/>
        <cache:cache-evict method="deleteAll" all-entries="true"/>
    </cache:caching>
</cache:advice>

<!--proxy-target-class="false" false基础jdk方法代理 true基于类代理-->
<aop:config>
    <aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.hong.spring.service.*.*(..))" />
</aop:config>

结果

16:38:45.912 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从数据库中获取用户信息{"age":100,"id":1,"username":"333"}
16:38:45.913 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 第一次拿:{"age":100,"id":1,"username":"333"}
16:38:45.914 [main] INFO  com.hong.spring.service.UserServiceCaceTest - 第二次拿:{"age":100,"id":1,"username":"333"}

参考文章:

中文翻译:https://www.docs4dev.com/docs/zh/spring-framework/4.3.21.RELEASE/reference/cache.html

官方:https://docs.spring.io/spring/docs/4.3.11.RELEASE/spring-framework-reference/html/cache.html

缺点:

  • 若没有开启 mode="aspectj" 默认是proxy @Cacheable必须是public才能生效,会导致如果私有方法想有缓存,只能手动实现;
  • 没有锁的概念,会导致脏读;
  • 不针对多进程应用环境进行管理;
  • 无法支持多级缓存;
  • 若没有开启 mode="aspectj" 同一类下的方法互调是没有缓存效果,因为aop管理不到。

spring集成ehcache

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点。

在pom中引入jar包

<!--引入ehcache-->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.2</version>
</dependency>

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">

    <!-- 磁盘缓存位置 -->
    <diskStore path="java.io.tmpdir/ehcache"/>

    <!-- 默认缓存 -->
    <defaultCache
            maxEntriesLocalHeap="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxEntriesLocalDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"/>

    <!-- questionCreatedTrack缓存 -->
    <cache name="questionCreatedTrack"
           maxElementsInMemory="1000"
           eternal="false"
           timeToIdleSeconds="5"
           timeToLiveSeconds="5"
           overflowToDisk="false"
           memoryStoreEvictionPolicy="LRU"/>
</ehcache>

配置说明:

name

cache的唯一标识

maxElementsInMemory

最大创建条数

eternal

缓存是否是永久的,默认false

timeToLiveSeconds

缓存的存活时间

timeToIdleSeconds

多长时间不访问就清楚该缓存

overflowToDisk

内存不足是否写入磁盘,默认False

maxElementsOnDisk

持久化到硬盘最大条数

memoryStoreEvictionPolicy

缓存满了后,清除缓存的规则,自带三种:FIFO(先进先出),LFU(最少使用),LRU(最近最少使用)

diskSpoolBufferSizeMB

磁盘缓存的缓存区大小,默认30M

diskExpiryThreadIntervalSeconds

磁盘失效线程时间间隔

applicationContext-mybatis_plus_ehcache.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:context="http://www.springframework.org/schema/context"
      xmlns:tx="http://www.springframework.org/schema/tx"
      xmlns:mvc="http://www.springframework.org/schema/mvc"
      xmlns:cache="http://www.springframework.org/schema/cache"
      xmlns:p="http://www.springframework.org/schema/p"
      xmlns:aop="http://www.springframework.org/schema/aop"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


   <!-- 配置组件扫描 -->
   <context:component-scan base-package="com.hong.spring"></context:component-scan>
   <!--加载配置文件-->
   <context:property-placeholder location="classpath:jdbc.properties"/>

   <!-- 开启注解 -->
   <context:annotation-config />
   <!--开启注解事务-->
   <tx:annotation-driven  mode="aspectj" />
   <!--放行静态资源-->
   <mvc:default-servlet-handler />

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
        id="internalResourceViewResolver">
      <!-- 前缀 -->
      <property name="prefix" value="/WEB-INF/pages/" />
      <!-- 后缀 -->
      <property name="suffix" value=".html" />
      <property name="contentType" value="text/html"/>

   </bean>

   <!--开启mvc注解事务-->
   <!-- 定义注解驱动 -->
   <mvc:annotation-driven>
      <mvc:message-converters>
         <!-- 设置支持中文 -->
         <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="supportedMediaTypes">
               <list>
                  <value>text/plain;charset=UTF-8</value>
                  <value>text/html;charset=UTF-8</value>
               </list>
            </property>
         </bean>
         <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/>
      </mvc:message-converters>
   </mvc:annotation-driven>


   <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
      <!-- 基础配置 -->
      <property name="url" value="${jdbc.url}"></property>
      <property name="driverClassName" value="${jdbc.driver}"></property>
      <property name="username" value="${jdbc.user}"></property>
      <property name="password" value="${jdbc.password}"></property>

      <!-- 关键配置 -->
      <!-- 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 -->
      <property name="initialSize" value="3" />
      <!-- 最小连接池数量 -->
      <property name="minIdle" value="2" />
      <!-- 最大连接池数量 -->
      <property name="maxActive" value="15" />
      <!-- 配置获取连接等待超时的时间 -->
      <property name="maxWait" value="10000" />

      <!-- 性能配置 -->
      <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
      <property name="poolPreparedStatements" value="true" />
      <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />

      <!-- 其他配置 -->
      <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
      <property name="timeBetweenEvictionRunsMillis" value="60000" />
      <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
      <property name="minEvictableIdleTimeMillis" value="300000" />
      <!--   建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,
                  执行validationQuery检测连接是否有效。-->
      <property name="testWhileIdle" value="true" />
      <!-- 这里建议配置为TRUE,防止取到的连接不可用 ,申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。-->
      <property name="testOnBorrow" value="true" />
      <!-- 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 -->
      <property name="testOnReturn" value="false" />
   </bean>

   <!--事务管理器-->
   <!-- sqlSessionFactory -->
   <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
      <!-- 加载 MyBatis 的配置文件 -->
      <property name="configLocation" value="classpath:mybatis-plus.xml"/>
      <!-- 数据源 -->
      <property name="dataSource" ref="dataSource"/>
      <!-- 所有配置的mapper文件 -->
      <property name="mapperLocations" value="classpath*:com/hong/spring/mapper/*.xml" />
   </bean>

   <!-- Mapper 扫描器 -->
   <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
      <!-- 扫描 包下的组件 -->
      <property name="basePackage" value="com.hong.spring.dao" />
      <!-- 关联mapper扫描器 与 sqlsession管理器 -->
      <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
   </bean>
   <!--事务配置-->
   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="dataSource" />
   </bean>


   <!--开启缓存注解-->
   <cache:annotation-driven  mode="aspectj"   />

   <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>

   <!-- EhCache library setup -->
   <bean id="ehcache"
        class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="classpath:ehcache.xml"/>

    <cache:advice id="cacheAdvice" cache-manager="cacheManager">
        <cache:caching cache="questionCreatedTrack">
            <cache:cacheable method="findById" key="#p0"/>
            <cache:cacheable method="find*" key="#p0"/>
            <cache:cache-evict method="deleteAll" all-entries="true"/>
        </cache:caching>
    </cache:advice>

    <!--proxy-target-class="false" false基础jdk方法代理 true基于类代理-->
    <aop:config>
        <aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.hong.spring.service.*.*(..))" />
    </aop:config>
</beans>

com.hong.spring.service.UserServiceEhCaceTest#testThis

package com.hong.spring.service;

import com.alibaba.fastjson.JSONObject;
import com.hong.spring.entity.User;
import lombok.extern.log4j.Log4j2;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @Auther: csh
 * @Date: 2020/8/24 11:17
 * @Description:
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-mybatis_plus_ehcache.xml")
@Log4j2
public class UserServiceEhCaceTest {
    @Autowired
    private IUserService userService;


    /**
     *
     * 功能描述:通过spring获取缓存
     *
     * @param:
     * @return:
     * @auther: csh
     * @date: 2020/8/27 19:04
     */
    @Test
    public void findSpringGuavaCacheById() throws InterruptedException {
        for(int i=0;i<10;i++){
            log.info("第{}次,获取结果{}",i,JSONObject.toJSONString(userService.findSpringGuavaCacheById(1)));
        }
        log.info("睡上10秒再拿!");
        Thread.sleep(11000);
        log.info("重新获取结果{}",JSONObject.toJSONString(userService.findSpringGuavaCacheById(1)));
    }

    @Test
    public void deleteCacheTest(){
        log.info("获取结果{}",JSONObject.toJSONString(userService.findSpringGuavaCacheById(1)));
        userService.deleteCache(1);
        log.info("重新获取结果{}",JSONObject.toJSONString(userService.findSpringGuavaCacheById(1)));
    }

    @Test
    public void testThis(){
        User user = userService.findByUserId(1);
        log.info("第一次拿:"+JSONObject.toJSONString(user));
        User user2 = userService.findByUserId(1);
        log.info("第二次拿:"+JSONObject.toJSONString(user2));
    }

}

结果

17:22:56.557 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
17:22:56.563 [main] DEBUG org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5b5e7036] was not registered for synchronization because synchronization is not active
17:22:56.570 [main] DEBUG org.mybatis.spring.transaction.SpringManagedTransaction - JDBC Connection [com.mysql.jdbc.JDBC4Connection@712cfb63] will not be managed by Spring
17:22:56.573 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==>  Preparing: SELECT * FROM user WHERE id = ? 
17:22:56.594 [main] DEBUG com.hong.spring.dao.UserMapper.findById - ==> Parameters: 1(Integer)
17:22:56.605 [main] DEBUG com.hong.spring.dao.UserMapper.findById - <==      Total: 1
17:22:56.607 [main] DEBUG com.alibaba.druid.pool.PreparedStatementPool - stmt enter cache
17:22:56.608 [main] DEBUG org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5b5e7036]
17:22:56.620 [main] INFO  com.hong.spring.service.impl.UserServiceImpl - 从数据库中获取用户信息{"age":100,"id":1,"username":"333"}
17:22:56.621 [main] INFO  com.hong.spring.service.UserServiceEhCaceTest - 第一次拿:{"age":100,"id":1,"username":"333"}
17:22:56.622 [main] INFO  com.hong.spring.service.UserServiceEhCaceTest - 第二次拿:{"age":100,"id":1,"username":"333"}

总结:ehcache的性能的确也是非常优越,同样也是支持集群的,所以这点区别于springCache与guava两种。但是ehcache与java绑得太死了,不太建议作为集群方案,并且实现起来非常麻烦。

参考文章:

官网:http://www.ehcache.org/documentation/configuration/configuration

相关说明:https://blog.csdn.net/angjunqiang/article/details/43155437

源码学习:https://www.iteye.com/blog/distantlight1-2253447

最后

本地缓存中,建议还是以guava来做并且通过手动方式来实现,这样的话有利于维护,也不需要去配置spring或xml,并且可以作为一级缓存,像api层面缓存。而spring cache可以单 独来使用,也可以结合其他缓存来使用,但一般项目中较少使用到这样的结合,毕竟,如果一级缓存可以直接考虑用guava手动来配置,而分布式只直接用redis,关于分布式缓存考虑篇幅,下文完善。