SpringBoot高级应用——SpringBoot与缓存

(一)、Spring缓存抽象

Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们的开发。

     1)、几个重要概念&缓存注解

image.png

(二)、基础环境的搭建

  1. 创建项目

    [图片上传失败...(image-fa9ada-1565021029707)]

    1. 导入数据库文件
    ```sql
    /*
    Navicat MySQL Data Transfer
    
    Source Server         : 本地
    Source Server Version : 50528
    Source Host           : 127.0.0.1:3306
    Source Database       : springboot_cache
    
    Target Server Type    : MYSQL
    Target Server Version : 50528
    File Encoding         : 65001
    
    */
    
    SET FOREIGN_KEY_CHECKS=0;
    
    -- ----------------------------
    -- Table structure for department
    -- ----------------------------
    DROP TABLE IF EXISTS `department`;
    CREATE TABLE `department` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `departmentName` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- Table structure for employee
    -- ----------------------------
    DROP TABLE IF EXISTS `employee`;
    CREATE TABLE `employee` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `lastName` varchar(255) DEFAULT NULL,
      `email` varchar(255) DEFAULT NULL,
      `gender` int(2) DEFAULT NULL,
      `d_id` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    ```
3. 创建javabean
![在这里插入图片描述](https://upload-images.jianshu.io/upload_images/18288748-74e1f92b3ce08199.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
4. 整合Mybatis操作数据库
    ```yml
    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/spring_cache?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
        username: root
        password: XHHP0913
        driver-class-name: com.mysql.jdbc.Driver
    mybatis:
      mapper-locations: classpath:mybatis/mapper/*.xml
      configuration:
        map-underscore-to-camel-case: true 
    ```
    ```java
    package com.crud.springboot.mapper;
    
    import com.crud.springboot.bean.Employee;
    import org.apache.ibatis.annotations.Mapper;
    import org.springframework.stereotype.Repository;
    
    @Mapper
    @Repository
    public interface EmployeeMapper {
    
        public Employee getEmpById(Integer id);
    
        public void updateEmp(Employee employee);
    
        public void deleteEmpById(Integer id);
    
        public void insertUser(Employee employee);
    
        public Employee getEmpByLastName(String lastName);
    }
    
    ```
    



    ```xml
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.crud.springboot.mapper.EmployeeMapper">
        <select id="getEmpById" resultType="com.crud.springboot.bean.Employee">
            select * from employee where id=#{id}
        </select>
    
        <update id="updateEmp">
            update employee set lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} where id=#{id}
        </update>
    
        <delete id="deleteEmpById">
            delete from employee where id=#{id}
        </delete>
    
        <insert id="insertUser">
            insert into employee(lastName,email,gender,d_id) values(#{lastName},#{email},#{gender},,#{dId})
        </insert>
    
        <select id="getEmpByLastName" resultType="com.crud.springboot.bean.Employee">
            select * from employee where lastName=#{lastName}
        </select>
    </mapper>
    ```
  1. 搭建简单的Service层和Controller层


    在这里插入图片描述

(三)、快速体验缓存(@Cacheable)

  1. 开启基于注解的缓存
```java
@MapperScan("com.crud.springboot.mapper")
@SpringBootApplication
@EnableCaching
public class Springboot01CacheApplication
```
  1. 标注缓存注解即可

        @Cacheable(cacheNames = {"emp","temp"})
        public Employee getEmp(Integer id){
            System.out.println("查询2号员工");
            Employee emp = employeeMapper.getEmpById(id);
            return emp;
        }
    

    备注:
    1. 将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
    2.CacheMananger管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件都有自己唯一一个名字
    3.@Cacheable有如下几个可配置属性

属性 配置内容
cacheNames/value 指定缓存的名字
key 缓存数据时使用的key;可以用它来指定。默认是使用方法参数的值
keyGenerator key的生成器:可以自己指定key的生成器的组件id
cacheMananger 指定缓存管理器;或者cacheResolver指定缓存解析器
condition 指定符合条件的情况下才缓存
unless 否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
sync 是否使用异步模式
备注 ==key/keyGenerator 二选一使用、cacheMananger/cacheResolver 二选一使用==

            4.支持SPEL表达式


在这里插入图片描述

(四)、@CachePut

@CachePut(value = {"emp"},key = "#result.id")
    public Employee updateEmp(Employee employee){
        System.out.println("update"+employee);
        employeeMapper.updateEmp(employee);
        return employee;
    }
  1. @CachePut:既调用方法,又更新缓存数据;
  2. 修改数据库的某个数据,同时更新缓存
  3. 运行时机:
    1、先调用目标方法
    2、将目标方法的结果缓存起来
  4. 测试样例:
    1、首先查询一号员工,查询数据库,会把结果放在缓存中
    2、再次查询一号员工,直接从缓存中获取结果
    3、跟新一号员工的数据,调用updateEmp的方法,会将更新的结果放入缓存中
    4、再次查询一号员工,发现结果更新了,但是并未查询数据库
    注意:@CachePut的key要和@Cacheable的key相同才能更新缓存

(五)、@CacheEvict

/**
     * @CacheEvict 清除缓存
     * 可以使用属性 allEntries = true  清空所有的缓存
     * 可以使用属性 beforeInvocation = true  在方法执行之前清空缓存(默认是false)
     * @param id
     */
    @CacheEvict(cacheNames = {"emp"},key = "#id")
    public void deleteEmp(Integer id){
        System.out.println("deleteEmp:"+id);
        employeeMapper.deleteEmpById(id);
    }
  1. @CacheEvict:用于清空缓存
  2. 修改数据库的某个数据,同时更新缓存
  3. 运行时机:
    1、用于删除数据时清空缓存;
  4. 测试样例:
    1、首先查询一号员工,查询数据库,会把结果放在缓存中
    2、再次查询一号员工,直接从缓存中获取结果
    3、删除一号员工
    4、再次查询一号员工,发现需要再次查询数据库才能获得结果
    注意:1、可以使用属性 allEntries = true 清空所有的缓存
               2、可以使用属性 beforeInvocation = true 在方法执行之前清空缓存(默认false)

(六)、@Caching

@Caching(
            cacheable = {
                @Cacheable(value="emp",key = "#lastName")
            },
            put = {
                    @CachePut(value="emp",key="#result.id"),
                    @CachePut(value = "emp",key = "#result.email")
            }
    )
    public Employee getEmpByLastName(String lastName){
        Employee emp = employeeMapper.getEmpByLastName(lastName);
        return emp;
    }
  1. @Caching:是一个组合注解,可以组合@Cacheable、@CachePut、@CacheEvict

(七)、@CacheConfig

@Service
@CacheConfig(cacheNames = "emp")
public class EmployeeService {
  1. @CacheConfig:抽取缓存的公共配置

(八)、整合Redis作为缓存

SpringBoot默认使用的是ConcurrentMapCacheManager=ConcurrentMapCache;
开发中使用缓存中间件:redis、memcached、ehcache;

(1)、在虚拟机上搭建redis环境
(2)、引入SpringBoot的Redis starter
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
(3)、配置Redis
spring:
  datasource:
    redis:
      host: 192.168.56.102
(4)、测试缓存

原理: CacheManager==Cache 缓存组件来实际给缓存中存取数据

  1. 引入redis的starter,容器中保存的是RedisCacheManager

  2. RedisCacheManager 帮我们创建RedisCache来作为缓存组件

  3. RedisCache通过操作Redis来缓存数据

  4. 默认保存数据 k-v 都是通过Object;利用序列化保存;如何保存为json:
    1、引入了redis的starter,cacheManager变为RedisCacheManager;
    2、默认创建的CacheManager操作Redis的时候使用的是RedisTemplate<Object, Object>(默认使用jdk的序列化机制)

  5. 自定义CacheManager:

        /**
         * 2.0版本RedisCacheManager
         * @param redisConnectionFactory
         * @param empRedisTemplate
         * @return
         */
        @Bean
        public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory){
            //初始化一个RedisCacheWriter
            RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
            //设置CacheManager的值序列化方式为json序列化
            RedisSerializer<Employee> jsonSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
            RedisSerializationContext.SerializationPair<Employee> pair = RedisSerializationContext.SerializationPair
                    .fromSerializer(jsonSerializer);
            RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig()
                    .serializeValuesWith(pair);
            //初始化RedisCacheManager
            return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
        }
    
    (5)、指定统一的CacheManager

    如果不指定不同的CacheManager会导致混乱,出现错误


    在这里插入图片描述

需要为不同方法指定对应的CacheManager,并且要选择一个Primary的CacheManager

    @Primary
    @Bean
    public RedisCacheManager deptRedisCacheManager(RedisConnectionFactory redisConnectionFactory){
        //初始化一个RedisCacheWriter
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        //设置CacheManager的值序列化方式为json序列化
        RedisSerializer<Department> jsonSerializer = new Jackson2JsonRedisSerializer<Department>(Department.class);
        RedisSerializationContext.SerializationPair<Department> pair = RedisSerializationContext.SerializationPair
                .fromSerializer(jsonSerializer);
        RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(pair);
        //初始化RedisCacheManager
        return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
    }
    @Cacheable(cacheNames = "dept",cacheManager = "deptRedisCacheManager")
    public Department getDeptById(Integer id){
        Department  department=departmentMapper.getDeptById(id);
        return department;
    }
(6)、如需在编码中使用缓存
    @Qualifier("deptCacheManager")
    RedisCacheManager  deptCacheManager;

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

推荐阅读更多精彩内容