springboot高级篇
- 1,cache
- 1.1 JSR-1.7
- 1.2 缓存抽象
- 1.2.1,概念
- 1.2.2,项目实战
- 5.1,开启基于注解的缓存:@EnableCaching
- 5.2,properties
- 5.3,缓存方法的使用,必须指定@Cacheable的参数:
- 5.3.1,参数:cacheNames
- 5.3.2,key="#id":代表拿到下面方法中id的值,这里也可以使用spel表达式。可以用root.methodName
- 5.3.3,value=""就是方法返回值
- 5.3.4,keyGenerator:当指定了key就不用指定它。它是用来生成组件id的
- 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,
- 5.5,@Cacheable的运行流程
- 5.6,simplekeyGenerator生成key的策略
- 1.3,redis
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;
}