springboot高级篇(缓存)

1,cache

1.1 JSR-1.7

不怎么使用的一种方式

要使用它

 <!--缓存相关jsr-1.7-->
        <dependency>
            <groupId>javax.cache</groupId>
            <artifactId>cache-api</artifactId>
        </dependency>

1.2 缓存抽象

1.2.1,概念

@cacheable
在方法上标注这个注解。方法返回值存入缓存中。下次还执行相同方法入参的时候。不用再执行方法。直接去缓存中取

@cacheEvict
删除方法上增加的注解。清空缓存。因为我们删除一个用户,就必须删除缓存中存在的这个用户

#编码
service
 //删除缓存
    @CacheEvict(cacheNames = "emp" ,key="#id")
    public void deleteTep(Integer id){
        System.out.println("deleteemp"+id);
    }

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

@cacheEvict中属性有一个allEntries=true是删除cacheName下的所有缓存。

@cacheEvict中指定beforeInvocation属性是在方法执行前就清除缓存。可以避免异常导致清除不了缓存

@cachePut
更新缓存,我们更新数据库某个用户数据的时候,又想同时更新缓存。用此注解

#编码

service
  //更新缓存
    @CachePut(cacheNames = "emp",key = "#result.id")
    public Employee update(Employee employee){
      int count= employeeMapper.updateEmp(employee);
      if(count>0){
          return employee;
      }else{
          return null;
      }
    }

controller
@GetMapping("/update")
    public Employee update(Employee employee){
        Employee employee1=employeeService.update(employee);
        return employee1;
    }


@enableCaching
在主启动类上,添加此注解可开启缓存编程


@caching
指定多个缓存规则

#编码
mapper
 @Select("SELECT * FROM employee where lastName= #{lastName}")
    Employee getEMPByName(String lastName);


service
 //根据lastName查询员工信息
    @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){
       return employeeMapper.getEMPByName(lastName);
    }

controller
  @GetMapping("/emp/lastName/{lastName}")
    public Employee getEmpBylastName(@PathVariable("lastName")String lastName){
        return employeeService.getEmpBylastName(lastName);
    }

测试接口规则:
1,访问lastName的getmapping映射,发现查数据库
2,再访问之前根据id查询的,发现不走数据库了
3,再访问lastName发现又走数据库。说明put机制刚才走了方法

@cacheConfig

@Service
@CacheConfig(cacheNames = "emp")
public class EmployeeService {
    @Autowired
    private EmployeeMapper employeeMapper;
   @Cacheable(/*cacheNames = "emp",*/ key = "#id")
    public Employee getEMP(Integer id){
        System.out.println("查询一号员工");
        Employee emp = employeeMapper.getEMP(id);
        return emp;
    }

在类上加cacheConfig所有的方法将会被指定

1.2.2,项目实战

1.5.12版本,模板选择:core-》cache,没有的自行依赖。导入web和mysql,mybatis的yom文件

2,导入,执行数据库文件:springboot_cache.sql(课件提供)

3,创建javaBean封装数据:employee和Department

4,整合mybatis

properties下配置
spring.datasource.url=jdbc:mysql://localhost:3306/spring_cache
spring.datasource.username=root
spring.datasource.password=ljs
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

@MapperScan写在主启动类上。 扫描mapper接口所在的类和包

EmployeeMapper

package com.atguigu.testcache.demo;

import com.atguigu.testcache.demo.bean.Employee;
import com.atguigu.testcache.demo.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;

@SpringBootTest
@RunWith(SpringRunner.class)
public class DemoApplicationTests {
    @Autowired
    private EmployeeMapper employeeMapper;
    @Test
    public void contextLoads() {
        //查询员工
        Employee employee=employeeMapper.getEMP(1);
        System.out.println(employee.toString());

    }

}

spring.datasource.url=jdbc:mysql://localhost:3306/spring_cache
spring.datasource.username=root
spring.datasource.password=ljs
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#开启驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true

package com.atguigu.testcache.demo.mapper;


import com.atguigu.testcache.demo.bean.Employee;
import org.apache.ibatis.annotations.*;

@Mapper
public interface EmployeeMapper {
    @Select("SELECT * FROM employee where id= #{id}")
    Employee getEMP(Integer id);

    @Update("UPDATE employee SET lastName= #{lastName}," +
            "email = #{email}, gender = #{gender}, d_Id= #{dId}" +
            " WHERE id = #{id}")
    int updateEmp(Employee employee);

    @Insert("INSERT INTO employee (lastName,email,gender,d_Id) " +
            "values = (#{lastName} ,#{email}, #{gender}, #{dId})")
    int InsertEmp(Employee employee);

    @Delete("DELETE FROM employee where id = # {id}")
    int DelEmp(Integer id);

}
package com.atguigu.testcache.demo.service;

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

@Service
public class EmployeeService {
    @Autowired
    private EmployeeMapper employeeMapper;
    public Employee getEMP(Integer id){
System.out.println("查询一号员工");
        Employee emp = employeeMapper.getEMP(id);
        return emp;
    }
}

package com.atguigu.testcache.demo.controller;

import com.atguigu.testcache.demo.bean.Employee;
import com.atguigu.testcache.demo.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

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

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

5,缓存编程

5.1,开启基于注解的缓存:@EnableCaching
5.2,properties

#开启dao层sql语句打印
logging.level.com.atguigu.testcache.demo.mapper=debug

5.3,缓存方法的使用,必须指定@Cacheable的参数:
5.3.1,参数:cacheNames

指定方法返回值放在哪个缓存之中。数组方式,可以指定多个缓存

5.3.2,key="#id":代表拿到下面方法中id的值,这里也可以使用spel表达式。可以用root.methodName
#编码
 @Cacheable(cacheNames = "emp",key = "root.methodName+'['+#id+']'")

5.3.3,value=""就是方法返回值
5.3.4,keyGenerator:当指定了key就不用指定它。它是用来生成组件id的
#编码
package com.atguigu.testcache.demo.config;

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;
import java.util.Arrays;


@Configuration
public class MyCacheConfig {
    @Bean("mykeyGenerator")
    public KeyGenerator keyGenerator() {
            return new KeyGenerator() {
                @Override
                public Object generate(Object o, Method method, Object... objects) {
                    return method.getName()+"["+ Arrays.asList(objects).toString()+"]";
                }
            };
        }
    }


service
@Cacheable(cacheNames = "emp", keyGenerator = "mykeyGenerator")

5.3.5,key和keyResolver二选一,缓存管理器
5.3.6,condition条件 例如condition="#id>0" 当下面参数id>0才放进缓存
5.3.7,unless 条件 当unless指定条件为true,则不被缓存,经常这样用: unless="#result==null"
5.3.8,sync 是否使用异步模式
5.4,

启动就生效的缓存配置类:SimpleCacheConfiguration
启动方法:在properties上加debug=true

默认生效的缓存配置:SimpleCacheConfiguration它的作用是给容器中注册了一个CacheManager。。平时debug调试断点不要打在方法上,要打在行内。不会影响效率

5.5,@Cacheable的运行流程

1,方法运行前,先去查询Cache,按照CacheNames指定的名字进行获取。(cacheManager县获取对应的缓存),第一次从缓存中取,如果没有Cache组件会自动创建。
2,去cache中查找缓存内容,使用一个key,默认为方法的参数,key是按照某种策略生成。默认使用KeyGenerator生成的。默认使用simplekeyGenerator生成key
3,没有查到缓存就调用方法
4,将目标方法的返回值放进缓存中

@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key取查询缓存。如果没有就运行方法将结果放入缓存。以后再来调用就可以直接使用缓存中的数据

###@Cacheable的核心
1,使用CacheManager按照名字得到Cache组件
2,key使用keyGenerator生成,默认是simplekeyGenerator

5.6,simplekeyGenerator生成key的策略

如果没有参数,key=new Simplekey();
如果有一个参数,key=参数的值
如果有多个参数:key=new Simplekey(params)

1.3,redis

1.3.1,在以前的开发中,我们默认使用的concurrentMapCacheManager生成ConcurrentMapCache,将数据保存在concurrentMap中,实际开发用redis

1.3.2,redis的docker下安装

####国内镜像加速

docker pull registry.docker-cn.com/library/镜像名

1.3.3,docker run -d -p 6379:6379 --name redis 镜像名

1.3.4,使用redis的工具Desktop manager

右键点击服务器名。打开控制台直接可以在里面输入命令

1.3.4.1

1,队列命令:左插入 lpush mylist 1 2 3 4 5 6
2,左弹出命令:lpop mylist(一次弹出一个)
3,右弹出命令:rpop mylist
4,集合操作:sadd myset zhangsan lisi(插入两个元素到myset集合)
5,查询集合元素:smembers myset
6,查询集合中是否有这个元素:sismember myset wangwu

1.3.5,项目实战

1,redisTemplate
   <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
spring.redis.host=47.115.93.213

测试类

@Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate redisTemplate;


 @Test
    public void Test01() {
   /*     //给redis中保存数据
        stringRedisTemplate.opsForValue().append("msg","hello");
        //读取redis中的数据
      String str=  stringRedisTemplate.opsForValue().get("msg");
        System.out.println(str);*/
        //list列表操作
        stringRedisTemplate.opsForList().leftPush("mylist", "1");
        stringRedisTemplate.opsForList().leftPush("mylist", "2");
    }

redisTemplate的用法
1,实体类序列化
public class Employee implements Serializable

2,test写方法
@Test
public void Test02() {
//保存对象操作 :redisTemplate
//1,实体类实例化
Employee employee=employeeMapper.getEMP(1);
redisTemplate.opsForValue().set(“emp-01”,employee);
}

3,但是一执行它会以序列化的方式将数据保存到redis中。怎么以json格式保存呢?

4,redis默认的序列化规则(底层源码):

if(defaultSerializer==null){
defaultSerializer=new JdkSerializationRedisSerializer{
.....
}
默认用的是jdk序列化器

5,因为默认用jdk序列化器所以我们自定义序列化器

1,新建config配置类
package com.atguigu.testcache.demo.config;

import com.atguigu.testcache.demo.bean.Employee;
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;

@Configuration
public class MyRedisConfig {
    @Bean
    public RedisTemplate<Object, Employee>empredisTemplate(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException{
        RedisTemplate<Object,Employee>template=new RedisTemplate<Object, Employee>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Employee>ser=new Jackson2JsonRedisSerializer<Employee>(Employee.class);
        template.setDefaultSerializer(ser);
        return template;
    }
        
    
}
2,test
 @Autowired
    private RedisTemplate<Object,Employee>employeeRedisTemplate;


  @Test
    public void Test02() {
        //保存对象操作 :redisTemplate
        //1,实体类实例化
        Employee employee=employeeMapper.getEMP(1);
       // redisTemplate.opsForValue().set("emp-01",employee);
        employeeRedisTemplate.opsForValue().set("emp-01",employee);
    }

6,RedisTemplate缓存定理:启动启动类发现RedisCacheConfiguration已开启,SimpleCacheConfiguration已关闭。之前是CacheManager–>cache缓存组件来从缓存中读取数据

6.1,

引入redis starter 容器保存的是RedisCacheManager

6.2,

RedisCacheManager 帮我们创建RedisCache作为缓存组件,来操作redis缓存数据

6.3,

默认存储k-v,对象,序列化保存

6.3.1,引入了redis的starter,cacheManager变为RedisCacheManager
6.3.2,默认创建的RedisCacheManager操作redis使用的是RedisTemplate<Object,Object>
6.3.3,RedisTemplate<Object,Object>是默认使用jdk的序列化机制
6.3.4,自定义CacheManager:
 @Bean
    public RedisCacheManager employeeCacheManager(RedisTemplate<Object,Employee>employeeRedisTemplate){
        RedisCacheManager cacheManager=new RedisCacheManager(employeeRedisTemplate);
        //使用前缀,默认将CacheName作为key的前缀
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }
6.3.5,再访问test的02方法就实现了序列化存储
2,Department部门类编码
2.1
package com.atguigu.testcache.demo.mapper;

import com.atguigu.testcache.demo.bean.Department;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface DepartmentMapper {
    @Select("SELECT * FROM department WHERE id=#{id}")
    Department getDeptById(Integer id);
}
2.2
package com.atguigu.testcache.demo.service;

import com.atguigu.testcache.demo.bean.Department;
import com.atguigu.testcache.demo.mapper.DepartmentMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class DeptService {
    @Autowired
    DepartmentMapper departmentMapper;
    
    @Cacheable(cacheNames = "dept")
    public Department getDeptById(Integer id){
        System.out.println("查询部门id"+id);
        Department deptById = departmentMapper.getDeptById(id);
        return deptById;
    }
}

2.3
package com.atguigu.testcache.demo.controller;

import com.atguigu.testcache.demo.bean.Department;
import com.atguigu.testcache.demo.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DeptController {
    @Autowired
    DeptService deptService;
    @GetMapping("/dept/{id}")
    public Department getDept(@PathVariable("id")Integer id){
        return deptService.getDeptById(id);
    }
}

2.4,启动项目,发现缓存的数据能存入redis,但是反序列化出现500错误。因为我们存的是Employee的而不是Department的
2.5,修改
2.5.1,回到config,复制两个bean将里面的Employee改成Department
package com.atguigu.testcache.demo.config;

import com.atguigu.testcache.demo.bean.Department;
import com.atguigu.testcache.demo.bean.Employee;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheManager;
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;

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

    @Bean
    public RedisCacheManager employeeCacheManager(RedisTemplate<Object,Employee>employeeRedisTemplate){
        RedisCacheManager cacheManager=new RedisCacheManager(employeeRedisTemplate);
        //使用前缀,默认将CacheName作为key的前缀
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }

    @Bean
    public RedisTemplate<Object, Department>empredisTemplate2(RedisConnectionFactory redisConnectionFactory)throws UnknownHostException{
        RedisTemplate<Object,Department>template=new RedisTemplate<Object, Department>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Department>ser=new Jackson2JsonRedisSerializer<Department>(Department.class);
        template.setDefaultSerializer(ser);
        return template;
    }
    @Primary
    @Bean
    public RedisCacheManager departmentCacheManager(RedisTemplate<Object,Department>employeeRedisTemplate){
        RedisCacheManager cacheManager=new RedisCacheManager(employeeRedisTemplate);
        //使用前缀,默认将CacheName作为key的前缀
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }


}

2.5.2,service
package com.atguigu.testcache.demo.service;

import com.atguigu.testcache.demo.bean.Department;
import com.atguigu.testcache.demo.mapper.DepartmentMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class DeptService {
    @Autowired
    DepartmentMapper departmentMapper;

    @Cacheable(cacheNames = "dept",cacheManager = "departmentCacheManager")
    public Department getDeptById(Integer id){
        System.out.println("查询部门id"+id);
        Department deptById = departmentMapper.getDeptById(id);
        return deptById;
    }
}

2.5.3,再启动。访问两次就不会保错。正常从缓存中读取了
2.6,第二种改法:编码级别的操作缓存

service

  @Qualifier("departmentCacheManager")
    @Autowired
    RedisCacheManager deptCacheManager;
    public Department getDemptById(Integer id){
        System.out.println("查询部门id"+id);
        Department department=departmentMapper.getDeptById(id);
        //获取某个缓存
        Cache dept=deptCacheManager.getCache("dept");
        //可以做更新,查询,删除等缓存操作
        dept.put("dept:1",department);
        return department;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值