SpringBoot与缓存

关于Spring整合Redis我们之前已经有过介绍,这里对相关注解的使用我们就不再介绍太多,可以查看Spring整合Redis注解实现了解

JSR107缓存规范

Java Caching定义了5个核心接口

  • CachingProvider

    定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期间访问多个CachingProvider

  • CacheManager

    定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManage的上下文中,一个CacheManage只被一个CachingProvider拥有

  • Cache

    类似于Map的数据结构并临时储存以key为索引的值,一个Cache仅仅被一个CacheManage所拥有

  • Entry

    存储在Cache中的key-value对

  • Expiry

    存储在Cache的条目有一个定义的有效期,一旦超过这个时间,就会设置过期的状态,过期无法被访问,更新,删除。缓存的有效期可以通过ExpiryPolicy设置。

在这里插入图片描述

Spring的缓存抽象

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

SpringBoot整合缓存

1. 关于数据库相关配置以及Mapper、数据源的配置与编写我这里就不在赘述,注意这里的model我们要实现Serializable接口
 	 import java.io.Serializable;
    
    public class Department implements Serializable{
    
        private Integer id;
        private String deptName;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getDeptName() {
            return deptName;
        }
    
        public void setDeptName(String deptName) {
            this.deptName = deptName;
        }
    
        @Override
        public String toString() {
            return "Department{" +
                    "id=" + id +
                    ", deptName='" + deptName + '\'' +
                    '}';
        }
    }
2. EnableCaching

在我们的项目启动类上加上@EnableCaching 注解,开启缓存

    @MapperScan("com.example.cache.dao")
    @SpringBootApplication
    @EnableCaching
    public class CacheApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(CacheApplication.class, args);
    	}
    
    }

3. DepartmentServiceImpl

将方法的运行结果进行缓存,以后要是再有相同的数据,直接从缓存中获取,不用调用方法

CacheManager中管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每个缓存组件都有自己的唯一名字;

属性:

  • CacheName/value:指定存储缓存组件的名字
  • key:缓存数据使用的key,可以使用它来指定。默认是使用方法参数的值,1-方法的返回值
  • 编写Spel表达式:#id 参数id的值, #a0/#p0 #root.args[0]
  • keyGenerator:key的生成器,自己可以指定key的生成器的组件id
  • key/keyGendertor二选一使用
  • cacheManager指定Cache管理器,或者cacheReslover指定获取解析器
  • condition:指定符合条件的情况下,才缓存;
  • unless:否定缓存,unless指定的条件为true,方法的返回值就不会被缓存,可以获取到结果进行判断
  • sync:是否使用异步模式,unless不支持

    @Service
    public class DepartmentServiceImpl implements IDepartmentService{
    
        @Autowired
        private IDepartmentDao departmentDao;
    
        @Cacheable(key="'depts'+#root.methodName") //cacheable的key是不能使用result的参数的
        @Override
        public List<Department> getAll() throws Exception{
            System.out.println("进来了.............");
            return departmentDao.getAll();
        }
    }

@Cacheable

针对方法配置,根据方法的请求参数对其结果进行缓存,将方法的运行结果进行缓存,以后要是再有相同的数据,直接从缓存中获取,不用调用方法,因此cacheable的key是不能使用result的参数的(如果直接取缓存,此时是没有result的)

我们第一次运行getAll 方法,发现方法是被执行的

在这里插入图片描述

我们第二次运行getAll 方法,发现方法没有执行的,说明从缓存中取数据

那么问题来了,我们并没有使用redis,mogodb这种缓存中间件,springBoot是如何做缓存的呢?存在哪里呢?

原理

SpringBoot为我们自动配置了缓存相关CacheAutoConfiguration,并且引入了很多的缓存组件

在这里插入图片描述

在我们没有配置任何缓存中间件的时候也就是没有导入任何相关缓存启动器,默认SimpleCacheConfiguration生效

给容器注册一个CacheManager:ConcurrentMapCacheManager

可以获取和创建ConcurrentMapCache,作用是将数据保存在ConcurrentMap

这也就是我们没有配置任何缓存中间件就可以使用缓存的原因了!

运行流程

1、方法运行之前,先查Cache(缓存组件),按照cacheName的指定名字获取;

​ (CacheManager先获取相应的缓存),第一次获取缓存如果没有cache组件会自己创建

2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;

​ key是按照某种策略生成的,默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key

​ 没有参数 key=new SimpleKey()

​ 如果有一个参数 key=参数值

​ 如果多个参数 key=new SimpleKey(params);

3、没有查到缓存就调用目标方法

4、将目标方法返回的结果,放回缓存中

​ 方法执行之前,@Cacheable先来检查缓存中是否有数据,按照参数的值作为key去查询缓存,如果没有,就运行方法,存入缓存,如果有数据,就取出map的值。

SpringBoot整合Redis

关于Redis这里就不做过多的介绍了,我们直接开始整合

  1. 加入依赖
 	<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
  1. 配置文件配置主机地址
 spring.redis.host=127.0.0.1

SpringBoot会自动帮我们配置RedisTemplate 和 StringRedisTemplate

 		@Autowired
    	RedisTemplate redisTemplate;
    
    	@Autowired
    	StringRedisTemplate stringRedisTemplate;
    
    	@Test
    	public void testRedis() throws Exception{
    		stringRedisTemplate.opsForList().leftPush("testString","hello");
    		stringRedisTemplate.opsForList().leftPush("testString","world");
    
    		System.out.println(stringRedisTemplate.opsForList().leftPop("testString"));
    		System.out.println(stringRedisTemplate.opsForList().leftPop("testString"));
    
    		Department department = departmentDao.getOne(19);
    		redisTemplate.opsForList().leftPush("departments",department);
    
    
    	}

在这里插入图片描述

在这里插入图片描述

可以看到字符串已经正常出队,而部门数据也缓存在Redis

接下来我们使用Redis完成我们部门的增删改差功能(一个List的缓存以及每个部门的缓存)

    /**
     * CacheConfig抽取缓存的公共配置
     * 然后下面的value=department就不用写了
     */
    @CacheConfig(cacheNames="department")
    @Service
    public class DepartmentServiceImpl implements IDepartmentService{
    
        @Autowired
        private IDepartmentDao departmentDao;
    
        /**
         * List查询
         * @return
         * @throws Exception
         */
        @Cacheable(key="'depts'+#root.methodName") //cacheable的key是不能使用result的参数的
        @Override
        public List<Department> getAll() throws Exception{
            System.out.println("进来了.............");
            return departmentDao.getAll();
        }
    
    
        /**
         * 在添加的时候我们要清除list缓存并更新改记录缓存
         * 注意因为我们key使用id作为其中一部分,所以我们添加之后要把主键映射到dept对象上
         * @param dept
         * @return
         * @throws Exception
         */
        @Caching(put={
                @CachePut(key = "'yangzhao-'+#dept.id")
        },evict = {
                @CacheEvict(key="'deptsgetAll'")//可以指定先删除缓存还是先删除数据库
        })
        @Override
        public Department insert(Department dept) throws Exception {
            return 1 == departmentDao.insert(dept)?dept:null;
        }
    
        /**
         * 在修改的时候我们要清除list缓存并更新改记录缓存
         * @param dept
         * @return
         * @throws Exception
         */
        @Caching(put={
                @CachePut(key = "'yangzhao-'+#dept.id")//先执行方法 再缓存
        },evict = {
                @CacheEvict(key="'deptsgetAll'")
        })
        @Override
        public Department update(Department dept) throws Exception {
            departmentDao.update(dept);
            return dept;
        }
    
        /**
         * 在删除的时候我们要清空list缓存以及该对象缓存,对此项目可以使用allEntries
         * 可以使用自定义keyGenerator
         * @param id
         * @throws Exception
         */
        @Caching(evict={
                @CacheEvict(keyGenerator = "myKeyGenerator",beforeInvocation = true),//方法执行前清空缓存 allEntries = true清空所有
                @CacheEvict(key="'depts'+'getAll'")
        })
    //    @CacheEvict(allEntries = true)
        @Override
        public void delete(Integer id) throws Exception {
            departmentDao.delete(id);
        }
    
        /**
         * Id查询(如果Id大于16才缓存)
         * @param id
         * @return
         * @throws Exception
         */
        @Cacheable(keyGenerator = "myKeyGenerator",condition = "#id > 16") //满足条件才缓存 与 unless 相反 有缓存走缓存 没有走方法
        @Override
        public Department get(Integer id) throws Exception {
            System.out.println("get............");
            return departmentDao.getOne(id);
        }
    
    }

MyKeyGenerator

  @Component
    public class MyKeyGenerator implements KeyGenerator
    {
        @Override
        public Object generate(Object target, Method method, Object... params) {
            System.out.println("参数="+params[0]);
            System.out.println("参数="+ Arrays.asList(params));
            return "yangzhao-"+params[0];
        }
    }

IDepartmentDao


        @Insert({"insert into department (dept_name) values(#{deptName})"})
        @SelectKey(keyProperty="id",before=false,statement="SELECT LAST_INSERT_ID()",resultType=int.class,keyColumn="id")
        public int insert(Department department) throws Exception;

测试添加

        @Test
    	public void add() throws Exception{
    		Department dept = new Department();
    		dept.setDeptName("测试添加");
    		departmentService.insert(dept);
    	}

在这里插入图片描述

可以看到 Redis中已经多了一个缓存,并且List缓存已经被清空

        @Test
    	public void getOne() throws Exception{
    		System.out.println(departmentService.get(32));
    	}

运行get方法并没有打印get…说明从缓存读取数据

在这里插入图片描述

但是确是十六进制不方便查看

以json方式传输对象

新建一个Redis的配置类MyRedisConfig


    @Configuration
    public class MyRedisConfig {
    
        @Bean
        RedisTemplate<Object,Department> deptRedisTemplate(RedisConnectionFactory redisConnectionFactory){
            RedisTemplate<Object,Department> redisTemplate = new RedisTemplate<>();
            redisTemplate.setConnectionFactory(redisConnectionFactory);
            Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Department.class);
            redisTemplate.setDefaultSerializer(serializer);
            return redisTemplate;
        }
    
        @Primary  // 有多个cacheManager需要制定一个默认的一般将jdk的cacheManager作为默认
        @Bean
        RedisCacheManager deptRedisCacheManager(RedisTemplate<Object,Department> deptRedisTemplate){
            RedisCacheManager manager = new RedisCacheManager(deptRedisTemplate);
            manager.setUsePrefix(true);// 使用前缀,默认将CacheName作为前缀
            return manager;
        }
    
        @Bean
        RedisTemplate<Object,Employee> employeeRedisTemplate(RedisConnectionFactory redisConnectionFactory){
            RedisTemplate<Object,Employee> employeeRedisTemplate = new RedisTemplate<>();
            employeeRedisTemplate.setConnectionFactory(redisConnectionFactory);
            Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Employee.class);
            employeeRedisTemplate.setDefaultSerializer(serializer);
            return employeeRedisTemplate;
        }
    
        @Bean
        RedisCacheManager empRedisCacheManager(RedisTemplate<Object,Employee> employeeRedisTemplate){
            RedisCacheManager empRedisCacheManager = new RedisCacheManager(employeeRedisTemplate);
            empRedisCacheManager.setUsePrefix(true);
            return empRedisCacheManager;
        }
    
    }

然后在我们的DepartmentServiceImpl指定

    @CacheConfig(cacheNames="department",cacheManager = "deptRedisCacheManager")
    @Service
    public class DepartmentServiceImpl implements IDepartmentService{

再次运行添加方法,可以看已经是已json方式保存了

在这里插入图片描述
测试getAll

在这里插入图片描述

可以看到第一次访问数据库且Redis中也将查询结果做了缓存

在这里插入图片描述

测试删除

   @Test
    	public void del() throws Exception{
    		departmentService.delete(32);
    	}

删除前缓存情况

在这里插入图片描述

删除后缓存情况

在这里插入图片描述

测试修改

  @Test
    	public void update() throws Exception{
    		Department dept = new Department();
    		dept.setDeptName("测试修改");
    		dept.setId(31);
    		departmentService.update(dept);
    	}

修改前

在这里插入图片描述

修改后

在这里插入图片描述

总结

  1. 使用缓存时,默认使用的是ConcurrentMapCache,将数据保存在ConcurrentMap中,开发中使用的是缓存中间件,redis、memcached、ehcache等
  2. starter启动时,有顺序,redis优先级比ConcurrentMapCache更高,CacheManager变为RedisCacheManager,所以使用的是redis缓存
  3. 传入的是RedisTemplate<Object, Object> 默认使用的是jdk的序列化保存, 我们可以自定义CacheManager,编写一个配置类
  4. XXXRedisTemplate只能用来反序列化XXX,而不能反序列化其他对象,因此要创建不同的XXXRedisTemplate 并且分别创建XXXManager
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
springboot整合ehcache+redis实现双缓存的过程如下[^1]: 1. 添加依赖:在项目的pom.xml文件中添加ehcache和redis的依赖。 2. 配置ehcache:在application.properties或application.yml文件中配置ehcache的相关属性,包括缓存的名称、最大缓存数量、缓存过期时间等。 3. 配置redis:在application.properties或application.yml文件中配置redis的相关属性,包括redis的地址、端口、密码等。 4. 创建缓存管理器:在Spring Boot的配置类中创建一个缓存管理器,用于管理ehcache和redis缓存。 5. 使用缓存注解:在需要缓存的方法上添加缓存注解,例如@Cacheable、@CachePut等,指定缓存的名称和缓存key。 6. 启动项目:运行Spring Boot的启动类,启动项目。 验证整个流程可以通过以下步骤进行: 1. 创建一个Controller类,在其中定义一个需要缓存的方法。 2. 在该方法上添加缓存注解,指定缓存的名称和缓存key。 3. 启动项目,访问该方法,观察缓存是否生效。 示例代码如下: ```java // 引入相关的包 @RestController public class MyController { @Autowired private MyService myService; @GetMapping("/getData") @Cacheable(value = "myCache", key = "#param") public String getData(@RequestParam String param) { // 从数据库或其他数据源获取数据的逻辑 String data = myService.getDataFromDB(param); return data; } } @Service public class MyService { public String getDataFromDB(String param) { // 从数据库获取数据的逻辑 return "data from database"; } } ``` 相关问题: 1. 如何在Spring Boot中配置ehcache和redis? 2. 如何使用@Cacheable注解进行缓存? 3. 如何在Spring Boot中使用多个缓存管理器?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值