Spring缓存支持

------------------------------------------------------------------------------

开门见代码:

[git地址走起](https://github.com/lamymay/ray.git)


内存的速度远远大于硬盘的速度,当我们需要重复获取相同的数据的时候,一次又一次的请求数据库或远程服务,导致大量时间都消耗在数据库查询或远程方法调用上面,性能下降,这时候就需要使用到缓存技术了。

本文介绍SpringBoot 如何使用redis做缓存,如何对redis缓存进行定制化配置(如key的有效期)以及初始化redis做缓存。

使用具体的代码介绍相关注解及其属性的用法。

@Cacheable,

@CacheEvict,

@CachePut,

@CacheConfig


子项目 cache 即是 Spring缓存的演示项目,相关sql在cache子项目的根目录,

Spring定义了 org.springframework.cache.CacheManager 和 org.springframework.cache.Cache 接口来统一不同缓存技术。其中CacheManager是Spring提供的各种缓存技术抽象接口,内部使用Cache接口进行缓存的增删改查操作,我们一般不会直接和Cache打交道。

针对不同的缓存技术,Spring有不同的CacheManager实现类,定义如下表:

CacheManager描述

SimpleCacheManager使用简单的Collection存储缓存数据,用来做测试用

ConcurrentMapCacheManager使用ConcurrentMap存储缓存数据

EhCacheCacheManager使用EhCache作为缓存技术

GuavaCacheManager使用Google Guava的GuavaCache作为缓存技术

JCacheCacheManager使用JCache(JSR-107)标准的实现作为缓存技术,比如Apache Commons JCS

RedisCacheManager使用Redis作为缓存技术

----------------------------------------------------------------------------

1. 在我们使用任意一个实现的CacheManager的时候,需要注册实现Bean:

/**

* EhCache的配置

*/

@Bean

public EhCacheCacheManagercacheManager(CacheManager cacheManager) {

    return new EhCacheCacheManager(cacheManager);

}

当然,各种缓存技术都有很多其他配置,但是配置cacheManager是必不可少的。

声明式缓存注解

Spring提供4个注解来声明缓存规则,如下表所示:

注解   说明

@Cacheable方法执行前先看缓存中是否有数据,如果有直接返回。如果没有就调用方法,并将方法返回值放入缓存

@CachePut无论怎样都会执行方法,并将方法返回值放入缓存

@CacheEvict将数据从缓存中删除

@Caching可通过此注解组合多个注解策略在一个方法上面

@Cacheable 、@CachePut 、@CacheEvict都有value属性,指定要使用的缓存名称,而key属性指定缓存中存储的键。

2. 集成Redis缓存

接下来将讲解如何集成redis来实现缓存。

2.1 安装redis

安装和配置redis服务器网上很多教程,这里就不多讲了。在linux服务器上面安装一个redis,启动后端口号为默认的6379。

2.2 添加maven依赖

    org.springframework.boot

    spring-boot-starter-data-redis

2.3 配置application.yml

指定缓存的类型

配置redis的服务器信息

spring:

  profiles: dev

cache:

type: REDIS

  redis:

    host: 123.207.66.156

    port: 6379

timeout: 0

database: 0

    pool:

max-active: 100

max-wait: -1

max-idle: 8

min-idle: 0

3. 缓存配置类

重新配置 RedisCacheManager ,使用新的自定义配置值:

@Configuration

@EnableCaching

public class RedisCacheConfig {

    /**

    * 重新配置RedisCacheManager

    */

    @Autowired

    public voidconfigRedisCacheManger(RedisCacheManager rd) {

        rd.setDefaultExpiration(100L);

    }

}

keyGenerator

一般来讲我们使用key属性就可以满足大部分要求,但是如果你还想更好的自定义key,可以实现keyGenerator。

这个属性为定义key生成的类,和key属性不能同时存在。

在 RedisCacheConfig 配置类中添加我自定义的KeyGenerator:

/**

* 自定义缓存key的生成类实现

*/

@Bean(name = "myKeyGenerator")

public KeyGeneratormyKeyGenerator() {

    return new KeyGenerator() {

        @Override

        public Objectgenerate(Object o, Method method, Object... params) {

            logger.info("自定义缓存,使用第一参数作为缓存key,params = " + Arrays.toString(params));

            // 仅仅用于测试,实际不可能这么写

            return params[0];

        }

    };

}

经过以上配置后,redis缓存管理对象已经生成。下面简单介绍如何使用。

4. 使用

在service中定义增删改的几个常见方法,通过注解实现缓存:

@Service

@CacheConfig(cacheNames="users")

public class UserService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource

    private UserMapper userMapper;

    /**

    * cacheNames 设置缓存的值

    * key:指定缓存的key,这是指参数id值。 key可以使用spEl表达式

*@paramid

*@return

    */

    @Cacheable(cacheNames="user1", key="#id")

    public UsergetById(int id) {

        logger.info("获取用户start...");

        return userMapper.selectById(id);

    }

    /***

    * 如果设置sync=true,

    * 如果缓存中没有数据,多个线程同时访问这个方法,则只有一个方法会执行到方法,其它方法需要等待

    * 如果缓存中已经有数据,则多个线程可以同时从缓存中获取数据

*@paramid

*@return

    */

    @Cacheable(cacheNames="user1", key="#id", sync = true)

    public UsergetById2(int id) {

        logger.info("获取用户start...");

        return userMapper.selectById(id);

    }


    /**

    * 以上我们使用默认的keyGenerator,对应spring的SimpleKeyGenerator

    * 如果你的使用很复杂,我们也可以自定义myKeyGenerator的生成key

    * <p>

    * key和keyGenerator是互斥,如果同时制定会出异常

    * The key and keyGenerator parameters are mutually exclusive and an operation specifying both will result in an exception.

    *

*@paramid

*@return

    */

    @Cacheable(cacheNames = "user1", keyGenerator = "myKeyGenerator")

    public UserqueryUserById(int id) {

        logger.info("queryUserById,id={}", id);

        return userMapper.selectById(id);

    }

    /**

    * 每次执行都会执行方法,同时使用新的返回值的替换缓存中的值

*@paramuser

    */

    @CachePut(cacheNames="user1", key="#user.id")

    public voidcreateUser(User user) {

        logger.info("创建用户start...");

        userMapper.insert(user);

    }

    /**

    * 每次执行都会执行方法,同时使用新的返回值的替换缓存中的值

*@paramuser

    */

    @CachePut(cacheNames="user1", key="#user.id")

    public voidupdateUser(User user) {

        logger.info("更新用户start...");

        userMapper.updateById(user);

    }

    /**

    * 对符合key条件的记录从缓存中user1移除

    */

    @CacheEvict(cacheNames="user1", key="#id")

    public voiddeleteById(int id) {

        logger.info("删除用户start...");

        userMapper.deleteById(id);

    }

    /**

    * allEntries = true: 清空user1里的所有缓存

    */

    @CacheEvict(cacheNames="user1", allEntries=true)

    public voidclearUser1All(){

        logger.info("clearAll");

    }

}

注意可以在类上面通过 @CacheConfig 配置全局缓存名称,方法上面如果也配置了就会覆盖。

然后写个测试类:

@RunWith(SpringRunner.class)

@SpringBootTest(classes = Application.class)

public class UserServiceTest {

    @Autowired

    private UserService userService;

    @Test

    public voidtestCache() {

        int id = new Random().nextInt(100);

        User user = new User(id, "admin", "admin");

        userService.createUser(user);

        User user1 = userService.getById(id); // 第1次访问

        assertEquals(user1.getPassword(), "admin");

        User user2 = userService.getById(id); // 第2次访问

        assertEquals(user2.getPassword(), "admin");

        User user3 = userService.queryUserById(id); // 第3次访问,使用自定义的KeyGenerator

        assertEquals(user3.getPassword(), "admin");

        user.setPassword("123456");

        userService.updateUser(user);

        User user4 = userService.getById(id); // 第4次访问

        assertEquals(user4.getPassword(), "123456");

        userService.deleteById(id);

        assertNull(userService.getById(id));

    }

}

下面是测试的打印日志一部分:

Started UserServiceTest in 12.919 seconds (JVM runni

创建用户start...

==>  Preparing:INSERTINTOt_user(id, username, `

==> Parameters: 14(Integer), admin(String), admin(St

<==    Updates: 1

获取用户start...

==>  Preparing: SELECT id AS id,username,`password`

==> Parameters: 14(Integer)

<==      Total: 1

自定义缓存,使用第一参数作为缓存key,params = [14]

更新用户start...

==>  Preparing: UPDATE t_user SET username=?, `passw

==> Parameters:admin(String), 123456(String), 14(In

<==    Updates: 1

获取用户start...

==>  Preparing:SELECTidASid,username,`password`

==> Parameters: 14(Integer)

<==      Total: 1

删除用户start...

==>  Preparing:DELETEFROMt_userWHEREid=?

==> Parameters: 14(Integer)

<==    Updates: 1

获取用户start...

==>  Preparing:SELECTidASid,username,`password`

==> Parameters: 14(Integer)

<==      Total: 0

可以看到,第2次、第3次获取的时候并没有执行方法,说明缓存生效了。后面更新会同时更新缓存,取出来的也是更新后的数据。

切换缓存技术

得益于SpringBoot的自动配置机制,切换缓存技术除了替换相关maven依赖包和配置Bean外,使用方式和实例中一样,不需要修改业务代码。如果你要切换到其他缓存技术非常简单。

EhCache

当我们需要使用EhCache作为缓存技术的时候,只需要在pom.xml中添加EhCache的依赖:

    net.sf.ehcache

    ehcahe

EhCache的配置文件ehcache.xml只需要放到类路径下面,SpringBoot会自动扫描,例如:

<?xml version="1.0" encoding="UTF-8"?>

        xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"

        updateCheck="false" monitoring="autodetect"

        dynamicConfig="true">



            maxElementsInMemory="50000"

            eternal="false"

            timeToIdleSeconds="3600"

            timeToLiveSeconds="3600"

            overflowToDisk="true"

            diskPersistent="false"

            diskExpiryThreadIntervalSeconds="120"

    />


          maxEntriesLocalHeap="2000"

          eternal="false"

          timeToIdleSeconds="3600"

          timeToLiveSeconds="3600"

          overflowToDisk="false"

          statistics="true">


SpringBoot会为我们自动配置 EhCacheCacheManager 这个Bean,不过你也可以自己定义。

Guava

当我们需要Guava作为缓存技术的时候,只需要在pom.xml中增加Guava的依赖即可:

    com.google.guava

    guava

    18.0

SpringBoot会为我们自动配置 GuavaCacheManager 这个Bean。

Redis

最后还提一点,本篇采用Redis作为缓存技术,添加了依赖:

    org.springframework.boot

    spring-boot-starter-data-redis

SpringBoot会为我们自动配置 RedisCacheManager 这个Bean,同时还会配置 RedisTemplate 这个Bean。后面这个Bean就是下一篇要讲解的操作Redis数据库用,这个就比单纯注解缓存强大和灵活的多了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容