[springboot学习笔记]SpringBoot缓存机制

SpringBoot缓存机制

一、SpringBoot缓存机制概念

1.JSR107的五个核心接口

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

2.Spring的抽象缓存

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

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
  • Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
  • 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

3.几个重要概念及缓存注解

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

二、SpringBoot缓存机制操作

1.环境搭建

在SpringBoot快速初始化环境

2.创建数据库及建表

1 建表

/*
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

Date: 2018-04-27 14:54:04
*/

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;

2 建立实体类

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 + "]";
	}
	
	

}


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 + "]";
	}
	
	
	
	

}

3.配置mybatis

1 配置数据库参数

spring:
  datasource:
    #url: jdbc:mysql://localhost:3306/spring_cache,需要添加时区,否则会出现乱码
    url: jdbc:mysql://localhost:3306/spring_cache?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
#开启驼峰命名
mybatis:
  configuration:
    map-underscore-to-camel-case: true

2 编写mapper

@Mapper
public interface EmpolyeeMapper {


    @Insert("INSERT INTO employee (`lastName`,`email`,`gender`,`d_id`) values (#{lastName},#{email},#{gender},#{dId})")
    public void save(Employee employee);

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

    @Update("UPDATE employee SET `lastName`=#{lastName},`email`=#{email},`gender`=#{gender},`d_id`=#{dId} WHERE id = #{id}")
    public void update(Employee employee);

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

3 编写service

@Service
public class EmployeeService {
    @Autowired
    private EmpolyeeMapper empolyeeMapper;

    public void save(Employee employee) {
        empolyeeMapper.save(employee);
    }

    public void deleteById(Integer id) {
        System.out.println("删除"+id+"号员工");
        empolyeeMapper.deleteById(id);
    }

    public void update(Employee employee) {
        empolyeeMapper.update(employee);
    }

    public Employee findById(Integer id) {
        System.out.println("查询"+id+"号员工");
        return empolyeeMapper.findById(id);
    }


}

4 编写Controller

@RestController
public class EmployeeController {
    @Autowired
    private EmployeeService service;

    @GetMapping("/emp/{id}")
    public Employee findById(@PathVariable Integer id) {
        Employee emp = service.findById(id);
        System.out.println(emp);
        return emp;
    }
    @GetMapping("/emp")
    public Employee save(Employee employee) {
        service.save(employee);
        return employee;
    }
    @GetMapping("/emp/del/{id}")
    public String deleteById(@PathVariable Integer id) {
        service.deleteById(id);
        return "success";
    }

    @GetMapping("/emp/update")
    public String update(Employee employee) {
        service.update(employee);

        return "redirect:/emp/#{id}";
    }
}

4.使用Cache

1 在main方法上配置一个方法

@SpringBootApplication
@MapperScan("com.xiaoxiao.springboot.mapper")
//配置后自动开启缓存
@EnableCaching
public class SpringBootCache01Application {

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

}


在yaml文件配置一下日志debug
logging:
  level:
    com.xiaoxiao.springboot.mapper: debug

2 在Service层配置缓存注解

  •     @Cacheable(value = "emp",key = "#id")
        public Employee findById(Integer id) {
            System.out.println("查询"+id+"号员工");
            return empolyeeMapper.findById(id);
        }
    
    
    
名字位置描述示例
methodNameroot object当前被调用的方法名#root.methodName
methodroot object当前被调用的方法#root.method.name
targetroot object当前被调用的目标对象#root.target
targetClassroot object当前被调用的目标对象类#root.targetClass
argsroot object当前被调用的方法的参数列表#root.args[0]
cachesroot object当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”, “cache2”})),则有两个cache#root.caches[0].name
argument nameevaluation context方法参数的名字. 可以直接#参数名,也可以使用#p0或#a0 的形式,0代表参数的索引;#iban、#a0 、#p0
resultevaluation context方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式’cache evict’的表达式beforeInvocation=false)#result

3 Cache工作原理

@Cacheable
用法:将方法的结果进行返回,以map的形式存储,如果还要该数据就不用查库,直接从缓存中调用
属性:
	cacheNames 数组,指定缓存名字,将返回值存入该缓存中
	value 与cacheNames作用相同
	key	缓存数据用的key,默认使用方法参数的值,这里默认参数是1.当然,可以指定key使用Spel,如下表所示
    keyGenerator key生成器,可以自己设置key生成器的组件ID key和keyGenerator选一个使用
    cacheManager cacheResolver选择一个使用
    condition 符合条件的情况下进行缓存
    unless 满足该条件就不缓存
    sync 使用异步模式

查询源码之后可以发现

  1. CacheManager(ConcurrentMapCacheManager)按照名字获得Cache(ConcurrentMap)
  2. 默认生成key使用keyGenerator,默认使用方法参数作为key,如果没有参数,key=new SimpleKey();如果有一个参数那么key=这个参数,如果有多个参数,key=new SimpleKey(params)
  3. 如果用key查到缓存中有值,那就直接调用返回,否则就将key和方法返回值存入缓存中

4 CachePut注解

@CachePut
    用法:修改数据库的内容,同时更新缓存,通常用于更新数据库
    属性:
		value 指定缓存的名字
        key:查询用的Key,默认情况与Cacheable注解用法一样,所以更新时要注意用相同的key查询值,例如#emp.id或者#result.id

5 CacheEvict注解

@CacheEvict
	用法:删除缓存中的数据
    属性	和上面是一样的
    	allEntries:是否清空所有缓存内容,缺省为false,如果指定为true,则方法调用后将立即清空所有缓存
        beforeInvocation:是否在方法执行前就清空,缺省为false,如果指定为true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存.如果是true,无论有没有异常都会删掉缓存

6 Caching组合查询

@Caching
	用法:用来组合查询,多种注释组合

三、整合Redis作为缓存

1.docker运行redis

docker run -d -p 6379:6379 --name myredis docker.io/redis

2.在yaml文件中进行配置

spring:
    redis:
      host: 192.168.1.50

3.pom.xml中导入依赖文件

  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.3.1.RELEASE</version>
  </dependency>

4.常见的redis操作

1 操作模板

@Autowired
private StringRedisTemplate stringRedisTemplate;//用来操作字符串的

@Autowired
private RedisTemplate redisTemplate;//用来操作k-v的模板,k,v都可以是任意object

2 操作类型

redis中有五大数据类型

/**
 * redis有常见的五种数据类型:String(字符串),List(列表),Set(集合),Hash(散列),ZSet(有序集合)
 *        stringRedisTemplate.opsForValue();//操作字符串
 *        stringRedisTemplate.opsForList();//操作列表
 *        stringRedisTemplate.opsForSet();//操作集合
 *        stringRedisTemplate.opsForHash();//操作散列
 *        stringRedisTemplate.opsForZSet();//操作有序集合
 */

3 保存自定义对象

将对象进行序列化后,可以看到存入redis的数据并不是json格式,原因是因为传入的是object类,解析不了该格式,所以可以自定义转成json格式

改变redis序列化的默认规则

public void afterPropertiesSet() {
    super.afterPropertiesSet();
    boolean defaultUsed = false;
    //默认使用jdk的序列化规则
    if (this.defaultSerializer == null) {
        this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
    }

那么我们可以自定义序列化规则

@Configuration
public class MyRedisConfig {
    @Bean
    public RedisTemplate<Object, Employee> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Employee> template = new RedisTemplate();
        template.setDefaultSerializer(new Jackson2JsonRedisSerializer<Employee>(Employee.class));
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

4 redis作为cache

引入redis的starter以后,springboot会帮我们创建一个RedisManager

RedisManager通过RedisCache进行操作缓存存入redis中

都是通过序列化保存,序列化规则默认是用的jdk

那么如果想要转成json数据,就要自定义规则,自己制定一个RedisManager

    @Bean
    public RedisCacheManager employeeCacheManager(RedisConnectionFactory redisConnectionFactory) {
        //初始化一个RedisCacheWriter
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);

        //设置CacheManager的值序列化方式为json序列化
        RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();
        RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair
                .fromSerializer(jsonSerializer);
        RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(pair);

        //设置默认超过期时间是30秒
        defaultCacheConfig.entryTtl(Duration.ofSeconds(30));
        //初始化RedisCacheManager
        return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
    }
来源:https://www.jianshu.com/p/652f53bd9118

配置这个以后,无论是什么类型的对象都可以转换成json数据


版权声明:本文为qq_41522089原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。