Spring Boot的初体验和自动配置的探究 :
简介
JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP (Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
2012年10月26日JSR规范委员会发布了JSR 107(JCache API)的首个早期草案。自该JSR启动以来,已经过去近12年时间,因此该规范颇为Java社区所诟病,但由于目前对缓存需求越来越多,因此专家组加快了这一进度。
JCache规范定义了一种对Java对象临时在内存中进行缓存的方法,包括对象的创建、共享访问、假脱机(spooling)、失效、各JVM的一致性等,可被用于缓存JSP内最经常读取的数据,如产品目录和价格列表。利用JCACHE,多数查询的反应时间会因为有缓存的数据而加快(内部测试表明反应时间大约快15倍)。
内容
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设置。
重要概念
环境搭建
SQL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 SET FOREIGN_KEY_CHECKS=0 ;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; 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;
Bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.hph.cache.bean;public class Department { private Integer id; private String departmentName; public Department () { super (); } 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 + "]" ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package com.hph.cache.bean;public class Employee { private Integer id; private String lastName; private String email; private Integer gender; 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 + "]" ; } }
整合Mybatis
配置数据源
1 2 3 4 spring.datasource.url=jdbc:mysql://192.168.1.110:3306/spring_cache spring.datasource.data-username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver
数据准备
注解版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.hph.cache.mapper;import com.hph.cache.bean.Employee;import org.apache.ibatis.annotations.*;@Mapper public interface EmployeeMapper { @Select ("SELECT * FROM employee WHERE id = #{id}" ) public Employee getEmpById (Integer id) ; @Update ("UPDATE employee SET lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dID} WHERE id = #{id} " ) public void updateEmp (Employee employee) ; @Delete ("DELETE FROM employee WHERE id=#{id} " ) public void deleteEmp (Integer id) ; @Insert ("INSERT INTO employee(lastName,email,gender,d_id) VALUESE(#{lastName},#{email},#{gender},#{dId}) " ) public void insertEmployee (Employee employee) ; }
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.hph.cache;import com.hph.cache.bean.Employee;import com.hph.cache.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 SpringbootCacheApplicationTests { @Autowired EmployeeMapper employeeMapper; @Test public void getEmpById () { Employee emp = employeeMapper.getEmpById(1 ); System.out.println(emp); } }
因为没有开启驼峰命名法,数据库中的字段和JavaBean中的字段未完全对应,在application.properties中配置驼峰命名。
1 2 3 4 5 spring.datasource.url=jdbc:mysql://192.168.1.110:3306/spring_cache spring.datasource.username=root spring.datasource.password=123456 #驼峰命名开启 mybatis.configuration.map-underscore-to-camel-case=true
使用缓存
添加@EnableCaching的注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.hph.cache;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cache.annotation.EnableCaching;@MapperScan ("com.hph.cache.mapper" )@SpringBootApplication @EnableCaching public class SpringbootCacheApplication { public static void main (String[] args) { SpringApplication.run(SpringbootCacheApplication.class, args); } }
开启DEBUG
1 2 3 4 5 6 7 spring.datasource.url=jdbc:mysql://192.168.1.110:3306/spring_cache spring.datasource.username=root spring.datasource.password=123456 #驼峰命名开启 mybatis.configuration.map-underscore-to-camel-case=true #开启debug logging.level.com.hph.cache.mapper=debug
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package org.springframework.cache.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Inherited;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.util.concurrent.Callable;import org.springframework.core.annotation.AliasFor;@Target ({ElementType.METHOD, ElementType.TYPE})@Retention (RetentionPolicy.RUNTIME)@Inherited @Documented public @interface Cacheable { @AliasFor ("cacheNames" ) String[] value() default {}; @AliasFor ("value" ) String[] cacheNames() default {}; String key () default "" ; String keyGenerator () default "" ; String cacheManager () default "" ; String cacheResolver () default "" ; String condition () default "" ; String unless () default "" ; boolean sync () default false ; }
名字 位置 描述 示例 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.hph.cache.service;import com.hph.cache.bean.Employee;import com.hph.cache.mapper.EmployeeMapper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;@Service public class EmployeeService { @Autowired EmployeeMapper employeeMapper; @Cacheable (cacheNames = {"emp" }) public Employee getEmp (Integer id) { System.out.println("查询" + id + "号员工" ); Employee emp = employeeMapper.getEmpById(id); return emp; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.hph.cache;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cache.annotation.EnableCaching;@MapperScan ("com.hph.cache.mapper" )@SpringBootApplication @EnableCaching public class SpringbootCacheApplication { public static void main (String[] args) { SpringApplication.run(SpringbootCacheApplication.class, args); } }
缓存生效了。
流程
在CacheAutoConfiguration中
1 2 3 4 5 6 7 8 9 10 11 12 13 static class CacheConfigurationImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { CacheType[] types = CacheType.values(); String[] imports = new String[types.length]; for (int i = 0 ; i < types.length; i++) { imports[i] = CacheConfigurations.getConfigurationClass(types[i]); } return imports; } }
1 2 3 4 5 public interface ImportSelector { String[] selectImports(AnnotationMetadata importingClassMetadata); }
我们试着在CacheAutoConfiguration中打断点。看其加载的配置类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class SimpleCacheConfiguration { private final CacheProperties cacheProperties; private final CacheManagerCustomizers customizerInvoker; SimpleCacheConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizerInvoker) { this .cacheProperties = cacheProperties; this .customizerInvoker = customizerInvoker; } @Bean public ConcurrentMapCacheManager cacheManager () { ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager(); List<String> cacheNames = this .cacheProperties.getCacheNames(); if (!cacheNames.isEmpty()) { cacheManager.setCacheNames(cacheNames); } return this .customizerInvoker.customize(cacheManager); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public Cache getCache (String name) { Cache cache = this .cacheMap.get(name); if (cache == null && this .dynamic) { synchronized (this .cacheMap) { cache = this .cacheMap.get(name); if (cache == null ) { cache = createConcurrentMapCache(name); this .cacheMap.put(name, cache); } } } return cache; } protected Cache createConcurrentMapCache (String name) { SerializationDelegate actualSerialization = (isStoreByValue() ? this .serialization : null ); return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256 ), isAllowNullValues(), actualSerialization); }
1 private final ConcurrentMap<Object, Object> store;
@Cacheable:
1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
SimpleKeyGenerator生成key的默认策略;
如果没有参数;key=new SimpleKey();
如果有一个参数:key=参数的值
如果有多个参数:key=new SimpleKey(params);
3、没有查到缓存就调用目标方法;
4、将目标方法返回的结果,放进缓存中
@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
核心:
1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator
1 2 3 4 5 6 7 8 9 10 11 12 public class EmployeeService { @Autowired EmployeeMapper employeeMapper; @Cacheable (cacheNames = {"emp" },key = "#root.methodName+'['+#id+']'" ) public Employee getEmp (Integer id) { System.out.println("查询" + id + "号员工" ); Employee emp = employeeMapper.getEmpById(id); return emp; } }
自定义myKeyGenerator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.hph.cache.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 target, Method method, Object... params) { return method.getName()+"[" + Arrays.asList(params).toString()+"]" ; } }; } }
注意要关闭debug=true
1 2 3 4 5 6 7 8 9 10 11 12 13 @Service public class EmployeeService { @Autowired EmployeeMapper employeeMapper; @Cacheable (cacheNames = {"emp" },keyGenerator = "myKeyGenerator" ,condition = "#a0>0" ,unless = "#a0==2" ) public Employee getEmp (Integer id) { System.out.println("查询" + id + "号员工" ); Employee emp = employeeMapper.getEmpById(id); return emp; } }
CachePut
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Target ({ElementType.METHOD, ElementType.TYPE})@Retention (RetentionPolicy.RUNTIME)@Inherited @Documented public @interface CachePut { @AliasFor ("cacheNames" ) String[] value() default {}; @AliasFor ("value" ) String[] cacheNames() default {}; String key () default "" ; String keyGenerator () default "" ; String cacheManager () default "" ; String cacheResolver () default "" ; String condition () default "" ; String unless () default "" ; }
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.hph.cache.service;import com.hph.cache.bean.Employee;import com.hph.cache.mapper.EmployeeMapper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;@Service public class EmployeeService { @Autowired EmployeeMapper employeeMapper; @Cacheable (cacheNames = {"emp" }) public Employee getEmp (Integer id) { System.out.println("查询" + id + "号员工" ); Employee emp = employeeMapper.getEmpById(id); return emp; } @CachePut (value = "emp" ) public Employee updateEmp (Employee employee) { System.out.println("updateEmp" +employee); employeeMapper.updateEmp(employee); return employee; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.hph.cache.service;import com.hph.cache.bean.Employee;import com.hph.cache.mapper.EmployeeMapper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;@Service public class EmployeeService { @Autowired EmployeeMapper employeeMapper; @Cacheable (cacheNames = {"emp" }) public Employee getEmp (Integer id) { System.out.println("查询" + id + "号员工" ); Employee emp = employeeMapper.getEmpById(id); return emp; } @CachePut (value = "emp" ) public Employee updateEmp (Employee employee) { System.out.println("updateEmp" +employee); employeeMapper.updateEmp(employee); return employee; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.hph.cache.controller;import com.hph.cache.bean.Employee;import com.hph.cache.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 EmployeeService employeeService; @GetMapping ("/emp/{id}" ) public Employee getEmployee (@PathVariable("id" ) Integer id) { Employee emp = employeeService.getEmp(id); return emp; } @GetMapping ("/emp" ) public Employee update (Employee employee) { Employee emp = employeeService.updateEmp(employee); return emp; } }
测试步骤
查询1号员工查到的j结果会放在缓存中。
key:1 value:LastName=清风丶
2.以后查询还是之前的结果
3.更新员工信息1号员工信息【LastName=清风丶;email=qingfeng@gmail.com
】
将方法的返回值也放进了缓存
key:传入的employee对象 value:返回的employee对象
4.查询员工
1号员工没有在缓存中更新。
key=“#employee。id”使用传入参数员工的id;
key=“result.id”使用返回后的id
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.hph.cache.service;import com.hph.cache.bean.Employee;import com.hph.cache.mapper.EmployeeMapper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;@Service public class EmployeeService { @Autowired EmployeeMapper employeeMapper; @Cacheable (cacheNames = {"emp" }) public Employee getEmp (Integer id) { System.out.println("查询" + id + "号员工" ); Employee emp = employeeMapper.getEmpById(id); return emp; } @CachePut (value = "emp" ,key = "#result.id " ) public Employee updateEmp (Employee employee) { System.out.println("updateEmp" +employee); employeeMapper.updateEmp(employee); return employee; } }
查找
已经更新过来了
CacheEvict
EmployeeService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.hph.cache.service;import com.hph.cache.bean.Employee;import com.hph.cache.mapper.EmployeeMapper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;@Service public class EmployeeService { @Autowired EmployeeMapper employeeMapper; @Cacheable (cacheNames = {"emp" }) public Employee getEmp (Integer id) { System.out.println("查询" + id + "号员工" ); Employee emp = employeeMapper.getEmpById(id); return emp; } @CachePut (value = "emp" ,key = "#result.id " ) public Employee updateEmp (Employee employee) { System.out.println("updateEmp" +employee); employeeMapper.updateEmp(employee); return employee; } @CacheEvict (value = "emp" ,key = "#id" ) public void deleteEmp (Integer id) { System.out.println("deletEmp" +id); employeeMapper.deleteEmpById(id); } }
EmployeeController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.hph.cache.controller;import com.hph.cache.bean.Employee;import com.hph.cache.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 EmployeeService employeeService; @GetMapping ("/emp/{id}" ) public Employee getEmployee (@PathVariable("id" ) Integer id) { Employee emp = employeeService.getEmp(id); return emp; } @GetMapping ("/emp" ) public Employee update (Employee employee) { Employee emp = employeeService.updateEmp(employee); return emp; } @GetMapping ("/delemp" ) public String deleteEmp (Integer id) { employeeService.deleteEmp(id); return "删除成功" ; } }
(⊙﹏⊙)出现乱码了 先忽略吧
1 2 3 4 5 6 @CacheEvict (value = "emp" ,key = "#id" ,allEntries = true )public void deleteEmp (Integer id) { System.out.println("deletEmp" +id); employeeMapper.deleteEmpById(id); }
数据被全部清空
1 2 3 4 5 6 7 @CacheEvict (value = "emp" )public void deleteEmp (Integer id) { System.out.println("deletEmp" +id); int i = 10 /0 ; }
2号数据库缓存也被清除掉了