Spring Boot 缓存的基本用法

目录

  • 一、目的
  • 二、JSR-107 缓存规范
  • 三、Spring 缓存抽象
  • 四、Demo
    • 1、使用 IDEA 创建 Spring Boot 项目
    • 2、创建相应的数据表
    • 3、创建 Java Bean 封装数据
    • 4、整合 MyBatis
      • 1.配置数据源信息
      • 2.使用注解版 MyBatis
    • 5、实现 Web 访问
    • 6、缓存初体验
    • 7、使用 redis 缓存中间件
      • 1.使用 docker 安装 redis(阿里云服务器)
      • 2.使用 Redis Desktop Manager 连接阿里云服务器
      • 3.引入 redis starter 启动器
      • 4.自定义 RedisCacheManager
  • 五、参考资料

一、目的

? 缓存是用于提升系统的性能,加速系统的访问,降低成本的一种技术。可以将一些高频、热点信息放入缓存中,避免直接从数据库中查询,如商品的页面信息这种经常被访问的数据。

二、JSR-107 缓存规范

为了统一缓存的开发规范、提高系统的扩展性和最小化开发成本等,J2EE 发布了 JSR-107 缓存规范。

Java Caching 定义了 5 个核心接口,分别是CachingProvider, CacheManager, Cache, Entry
Expiry

  • CachingProvider定义了创建、配置、获取、管理和控制多个 CacheManager。一个应用可
    以在运行期访问多个CachingProvider
  • CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache
    存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
  • Cache是一个类似 Map 的数据结构并临时存储以 Key 为索引的值。一个Cache仅被一个CacheManager所拥有。
  • Entry是一个存储在Cache中的 key-value 对。
  • Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期
    的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过 ExpiryPolicy 设置。

三、Spring 缓存抽象

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

几个重要概念&注解:

Cache 缓存接口,定义缓存操作。实现有:RedisCacheEhCacheCacheConcurrentMapCache
CacheManager 缓存管理器,管理各种缓存(Cache)组件
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存。
@EnableCaching 开启基于注解的缓存
keyGenerator 缓存数据时key生成策略
serialize 缓存数据时value序列化策略

四、Demo

1、使用 IDEA 创建 Spring Boot 项目

2、创建相应的数据表

SQL 文件:


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、创建 Java Bean 封装数据

package com.yunche.bean;

public class Department {

   private Integer id;
   private String departmentName;

   public Department() {
      super();
      // TODO Auto-generated constructor stub
   }
   public Department(Integer id, String departmentName) {
      super();
      this.id = id;
      this.departmentName = departmentName;
   }
   public Integer getId() {
      return id;
   }
   public void setId(Integer id) {
      this.id = id;
   }
   public String getDepartmentName() {
      return departmentName;
   }
   public void setDepartmentName(String departmentName) {
      this.departmentName = departmentName;
   }
   @Override
   public String toString() {
      return "Department [id=" + id + ", departmentName=" + departmentName + "]";
   }

}
package com.yunche.bean;

public class Employee {

   private Integer id;
   private String lastName;
   private String email;
   private Integer gender; //性别 1 男  0 女
   private Integer dId;

   public Employee() {
      super();
   }

   public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) {
      super();
      this.id = id;
      this.lastName = lastName;
      this.email = email;
      this.gender = gender;
      this.dId = dId;
   }

   public Integer getId() {
      return id;
   }
   public void setId(Integer id) {
      this.id = id;
   }
   public String getLastName() {
      return lastName;
   }
   public void setLastName(String lastName) {
      this.lastName = lastName;
   }
   public String getEmail() {
      return email;
   }
   public void setEmail(String email) {
      this.email = email;
   }
   public Integer getGender() {
      return gender;
   }
   public void setGender(Integer gender) {
      this.gender = gender;
   }
   public Integer getdId() {
      return dId;
   }
   public void setdId(Integer dId) {
      this.dId = dId;
   }
   @Override
   public String toString() {
      return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + ", dId="
            + dId + "]";
   }
}

4、整合 MyBatis

1.配置数据源信息

application.properties:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/spring_cache?useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456

#开启驼峰转换规则
mybatis.configuration.map-underscore-to-camel-case=true

2.使用注解版 MyBatis

1)、@MapperScan 指定需要扫描 Mapper 接口所在的包

package com.yunche;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan("com.yunche.mapper")
@SpringBootApplication
public class SpringbootDemoCacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootDemoCacheApplication.class, args);
    }

}

2)、定义 Mapper 接口中的方法

package com.yunche.mapper;

import com.yunche.bean.Employee;
import org.apache.ibatis.annotations.*;

/**
 * @ClassName: EmployeeMapper
 * @Description:
 * @author: yunche
 * @date: 2019/02/01
 */
@Mapper
public interface EmployeeMapper {

    @Select("SELECT * FROM employee WHERE id = #{id}")
    Employee getEmpById(Integer id);

    @Update("UPDATE employee set lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} WHERE id=#{id}")
    void updateEmp(Employee employee);

    @Delete("DELETE FROM employee WHERE id=#{id}")
    void deleteEmp(Integer id);

    @Insert("INSERT INTO employee(lastName,email,gender,d_id) VALUES(#{lastName},#{email},#{gender},#{d_id})")
    void insertEmp(Employee employee);
}

单元测试 Mapper:

package com.yunche;

import com.yunche.bean.Employee;
import com.yunche.mapper.EmployeeMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootDemoCacheApplicationTests {

    @Autowired
    EmployeeMapper employeeMapper;

    @Test
    public void contextLoads() {
    }

    @Test
    public void testMapper() {
        Employee employee = employeeMapper.getEmpById(1);
        System.out.println(employee);
    }

}

5、实现 Web 访问

1)、添加 service 包

package com.yunche.service;

import com.yunche.bean.Employee;
import com.yunche.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @ClassName: EmployeeService
 * @Description:
 * @author: yunche
 * @date: 2019/02/01
 */
@Service
public class EmployeeService {
    @Autowired
    EmployeeMapper employeeMapper;

    public Employee getEmp(Integer id){
        System.out.println("查询"+id+"号员工");
        Employee emp = employeeMapper.getEmpById(id);
        return emp;
    }
}

2)、添加 controller 包

package com.yunche.controller;

import com.yunche.bean.Employee;
import com.yunche.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName: EmployeeController
 * @Description:
 * @author: yunche
 * @date: 2019/02/01
 */
@RestController
public class EmployeeController {
    @Autowired
    EmployeeService employeeService;

    @GetMapping("/emp/{id}")
    public Employee getEmployee(@PathVariable("id") Integer id) {
        Employee employee = employeeService.getEmp(id);
        return employee;
    }
}

测试:

6、缓存初体验

EmployeeService:

/**
 * 将方法的运行结果进行缓存,再次运行该方法时从缓存中返回结果
 * CacheManager 管理多个 Cache 组件,Cache 组件进行缓存的 CRUD,每一个缓存组件都有唯一一个名字。
 * 属性:
 *      cacheNames/value:指定缓存组件的名字
 *      key:缓存数据键值对的 key,默认是方法的参数的值
 *      keyGenerator:key 的生成器,可以自己指定 key 的生成器的组件 id 与 key 二选一
 *      cacheManager:指定缓存管理器 cacheResolver:缓存解析器,二者二选一
 *      condition/unless(否定条件):符合指定条件的情况下才缓存
 *
 * @param id
 * @return
 */
@Cacheable(cacheNames = {"emp"}, condition = "#id % 2 == 1")
public Employee getEmp(Integer id){
    System.out.println("查询"+id+"号员工");
    Employee emp = employeeMapper.getEmpById(id);
    return emp;
}

Cache SpEL available metadata

名字 位置 描述 示例
methodName root object 当前被调用的方法名 #root.methodName
method root object 当前被调用的方法 #root.method.name
target root object 当前被调用的目标对象 #root.target
targetClass root object 当前被调用的目标对象类 #root.targetClass
args root object 当前被调用的方法的参数列表 #root.args[0]
caches root object 当前方法调用使用的缓存列表(如@Cacheable(value={"cache1", "cache2"})),则有两个 cache #root.caches[0].name
argument name evaluation context 方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0 或#a0 的形式,0 代表参数的索引; #iban 、 #a0 、 #p0
result evaluation context 方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式 ’cache evict’的表达式 beforeInvocation=false) #result

EmployeeService:

/**
 * 更新缓存,既调用方法 (更新数据库),又更新缓存
 * 测试步骤:1、查询 1 号员工,将其纳入缓存
 *          2、修改 1 号员工
 *          3、再次查询 1 号员工,若结果是从缓存中查询数据,且数据为更新后的缓存则测试通过
 * @param employee
 * @return
 */
@CachePut(cacheNames = {"emp"}, key = "#result.id")
public Employee updateEmp(Employee employee) {
    System.out.println("更新" + employee.getdId() + "号员工");
    employeeMapper.updateEmp(employee);
    return employee;
}

EmployeeController:

@GetMapping("/emp")
public Employee updateEmployee(Employee employee) {
    Employee emp = employeeService.updateEmp(employee);
    return emp;
}

测试:

EmployeeService:

/**
 * 清空缓存
 *      beforeInvocation:默认为 false 表示在方法调用之后清空缓存,
 *                        若为 true,则表示在方法调用之前清空缓存
 * @param id
 */
@CacheEvict(cacheNames = {"emp"}, beforeInvocation = true/*, key = "#id"*/)
public void deleteEmp(Integer id) {
    System.out.println("删除" + id + "号员工");
    //employeeMapper.deleteEmp(id); 只测试缓存的删除效果
}

EmployeeController:

@GetMapping("/delemp")
public String deleteEmp(Integer id){
    employeeService.deleteEmp(id);
    return "success";
}

7、使用 redis 缓存中间件

Spring 的缓存默认使用的是 ConcurrentMapCacheManager 下的 ConcurrentMapCache,是将数据保存在 ConcurrentMap<Object, Object> 中。而在开发中是使用缓存中间件:redis、memcached、ehcache 等。

1.使用 docker 安装 redis(阿里云服务器)

[[email protected] ~]# docker run -d -p 6379:6379 --name redis_cache docker.io/redis
44f9e905a7db0c0933f3d35ce65dd7041fd985d2da00895713d9765b20781011

2.使用 Redis Desktop Manager 连接阿里云服务器

注意:要在阿里云服务器控制台添加安全规则,确认开放 6379 端口。

3.引入 redis starter 启动器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-redis</artifactId>
    <version>1.3.2.RELEASE</version>
</dependency>

application.properties:

spring.redis.host=x.x.x.x #redis 服务器地址

单元测试 redis 键值是字符串类型 :

/**
 * 用于操作 key 和 value 都是字符串的键值
 */
@Autowired
StringRedisTemplate stringRedisTemplate;

/**
 * 用于操作 key 和 value 都是对象的键值
 */
@Autowired
RedisTemplate redisTemplate;

/**
 * 测试保存字符串
 * Redis 常见的五大数据类型
 *  String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
 *  stringRedisTemplate.opsForValue()[String(字符串)]
 *  stringRedisTemplate.opsForList()[List(列表)]
 *  stringRedisTemplate.opsForSet()[Set(集合)]
 *  stringRedisTemplate.opsForHash()[Hash(散列)]
 *  stringRedisTemplate.opsForZSet()[ZSet(有序集合)]
 */
@Test
public void testStringRedis() {
    // 追加一个字符串类型的 value
    stringRedisTemplate.opsForValue().append("msg", "hello");
    //读取一个字符串类型
    String value = stringRedisTemplate.opsForValue().get("msg");
    System.out.println(value);
}

单元测试 redis 存储对象:

package com.yunche.config;

import com.yunche.bean.Employee;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

import java.net.UnknownHostException;

/**
 * @ClassName: MyRedisConfig
 * @Description:
 * @author: yunche
 * @date: 2019/02/02
 */
@Configuration
public class MyRedisConfig {

    @Bean
    public RedisTemplate<Object, Employee> empRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Employee> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        //改变默认的序列化器
        template.setDefaultSerializer(new Jackson2JsonRedisSerializer(Employee.class));
        return template;
    }
}
@Autowired
RedisTemplate<Object, Employee> empRedisTemplate;
/**
 * 测试保存对象
 *  1、使对象的类实现 Serializable 接口,表示该类的对象可以序列化
 *  2、使用自定义 RedisTemplate 改变默认的序列化机制(jdk)方便观察
 */
@Test
public void testObjectRedis() {
    Employee employee = employeeMapper.getEmpById(1);
    empRedisTemplate.opsForValue().set("emp-01", employee);
}

4.自定义 RedisCacheManager

此时使用的缓存管理器为 RedisCacheManager,为了使缓存到 Redis 里面的数据达到如上图所示的效果,我们就需要自定义 RedisCacheManager 改变 RedisTemplate 的默认序列化机制(jdk)。

MyRedisConfig:

    // Spring Boot 1.x
//    @Bean
//    public RedisCacheManager employeeCacheManager(RedisTemplate<Object, Employee> empRedisTemplate){
//        RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate);
//        //key 多了一个前缀
//
//        //使用前缀,默认会将 CacheName 作为 key 的前缀
//        cacheManager.setUsePrefix(true);
//
//        return cacheManager;
//    }

    /**
     * Spring Boot 2.x 以后 RedisCacheManager 构造函数不再接受 RedisTemplate 参数
     * @param factory
     * @return
     */
    @Bean
    public RedisCacheManager empRedisCacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer(Employee.class))); //使用 Jackson2JsonRedisSerialize
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .transactionAware()
                .build();
            return redisCacheManager;
    }

访问 http://localhost:8080/emp/1,Redis 缓存结果如下:

五、参考资料

尚硅谷.Spring Boot 高级篇

原文地址:https://www.cnblogs.com/yunche/p/10349214.html

时间: 02-02

Spring Boot 缓存的基本用法的相关文章

spring boot集成redis缓存

spring boot项目中使用redis作为缓存. 先创建spring boot的maven工程,在pom.xml中添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>1.5.3.RELEASE</version> </depen

spring boot应用启动原理分析

spring boot quick start 在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar/war是可以直接启动的,不需要另外配置一个Web Server. 如果之前没有使用过spring boot可以通过下面的demo来感受下. 下面以这个工程为例,演示如何启动Spring boot项目: git clone [email protected]:hengyunabc/spring-boot-demo.git mvn spring-b

Spring Boot之配置文件application.properties

Spring Boot默认的配置文件为application.properties,放在src/main/resources目录下或者类路径的/config目录下,作为Spring Boot的全局配置文件,对一些默认的配置进行修改. 接下来使用例子展示Spring Boot配置文件的用法: 首先在src/main/resources下新建application.properties文件,内容如下 author.name=微儿博客 author.sex=男 创建controller类 ackage

基于Spring Boot构建的Spring MVC快速入门

原文地址:http://tianmaying.com/tutorial/spring-mvc-quickstart 环境准备 一个称手的文本编辑器(例如Vim.Emacs.Sublime Text)或者IDE(Eclipse.Idea Intellij) Java环境(JDK 1.7或以上版本) Maven 3.0+(Eclipse和Idea IntelliJ内置,如果使用IDE并且不使用命令行工具可以不安装) 一个最简单的Web应用 使用Spring Boot框架可以大大加速Web应用的开发过

spring boot?Swagger2文档构建及单元测试

首先,回顾并详细说明一下在快速入门中使用的@Controller.@RestController.@RequestMapping注解.如果您对Spring MVC不熟悉并且还没有尝试过快速入门案例,建议先看一下快速入门的内容. @Controller:修饰class,用来创建处理http请求的对象 @RestController:Spring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Contro

Spring Boot干货系列:(一)优雅的入门篇

Spring Boot干货系列:(一)优雅的入门篇http://www.cnblogs.com/zheting/p/6707032.html  全篇参考:http://www.cnblogs.com/zheting/category/966890.html 前言 Spring一直是很火的一个开源框架,在过去的一段时间里,Spring Boot在社区中热度一直很高,所以决定花时间来了解和学习,为自己做技术储备.   正文 首先声明,Spring Boot不是一门新技术,所以不用紧张.从本质上来说,

Spring boot +Spring Security + Thymeleaf 认证失败返回错误信息

spring boot以其众多友谊的特性,如零配置.微服务等,吸引了很多的粉丝.而其与Spring Security安全框架的无缝结合,使其具备的安全的特性.在此基础上使用Thymeleaf模板引擎进行渲染,静动态结合,让页面开发更加简单.直观. 通过表单提交登录的用户名和密码是登录接口比较常见的一种设计.在初学的过程中,我也不例外的采用个这种方式.表单设计见下图. 登录成功,完成正常的主页面跳转,这个不存在问题.存在问题的是,登录失败了该咋办呢?我就在考虑,由于thymeleaf的局部刷新操作

Spring Data JPA例子[基于Spring Boot、Mysql]

关于Spring Data Spring社区的一个顶级工程,主要用于简化数据(关系型&非关系型)访问,如果我们使用Spring Data来开发程序的话,那么可以省去很多低级别的数据访问操作,如编写数据查询语句.DAO类等,我们仅需要编写一些抽象接口并定义相关操作即可,Spring会在运行期间的时候创建代理实例来实现我们接口中定义的操作. 关于Spring Data子项目 Spring Data拥有很多子项目,除了Spring Data Jpa外,还有如下子项目. Spring Data Comm

构建微服务:Spring boot

构建微服务:Spring boot 在上篇文章构建微服务:Spring boot 提高篇中简单介绍了一下spring data jpa的基础性使用,这篇文章将更加全面的介绍spring data jpa 常见用法以及注意事项 前几篇文章地址: 构建微服务:Spring boot 入门篇 构建微服务:Spring boot 提高篇 构建微服务:Spring boot中Redis的使用 构建微服务:thymeleaf使用详解 作者:纯洁的微笑出处:http://www.ityouknow.com/

(Spring Boot框架)快速入门

Spring Boot 系列文章推荐 Spring Boot 入门 Spring Boot 属性配置和使用 Spring Boot 集成MyBatis Spring Boot 静态资源处理 今天介绍一下如何利用Spring MVC快速的搭建一个简单的web应用. 环境准备 一个称手的文本编辑器(例如Vim.Emacs.Sublime Text)或者IDE(Eclipse.Idea Intellij) Java环境(JDK 1.7或以上版本) Maven 3.0+(Eclipse和Idea Int