spring boot整合mybatis+开启spring boot缓存

spring boot的缓存说白了就是底层依赖map结构,每个key可以由自己生成,也可以由spring boot中spring cache abstract框架包中的simple key general生成。不过这些都是小问题,主要的是理解一下spring boot中的缓存设计结构是怎样的。
在spring boot中,其缓存结构如下:

一个spring boot下面管着很多的cachemanager,而每个cachemanager管理着多个cache,每个cache存放着需要的数据。在spring boot中,cache的底层结构就是map结构,每个要缓存的数据需要一个key值作为标记,缓存的数据作为object对象存入在map中。

环境准备

spring boot项目环境准备

创建一个新的spring boot项目,其中必须引入spring cache abstract包就可以了,如果是spring boot2.x以前的版本,没记错的话应该引入的是spring cache这个包。
第一步:

右侧红框里面就是引入的所有包,其中mybatis framword是我想用mybatis作为dao层技术,MySQL驱动这个就不说了,不同的数据库不同的驱动就OK了,spring cache abstraction是我们要用的框架包,spring boot所有关于缓存的东西都在这个包中。
一路next,直到项目创建完毕,其pom.xml文件的坐标如下所示:

    <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.4</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

OK一个项目已经创建完毕了。

MySQL数据库环境准备

创建一个数据库为cache,再创建一个department表,其SQL脚本如下:

create database cache;
use cache;
create table department(id int auto_increment primary key,departmentName varchar(255) null
)charset = utf-8;
INSERT INTO cache.department (id, departmentName) VALUES (1, 'test1');
INSERT INTO cache.department (id, departmentName) VALUES (2, 'test2');

完善项目目录结构

搭建如下目录结构:

employ*类的文件不要创建了,上面就只有department类的文件用的到。配置application.yml文件内容如下所示:

spring:datasource:username: rootpassword: 123456url: jdbc:mysql://192.168.43.37:3306/cache?serverTimezone=UTCdriver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration:# 开启mybatis的驼峰命名支持map-underscore-to-camel-case: true
# 设置日志级别
logging:level:com.lm.cache.dao: debug
debug: true

根据自己的信息进行配置,给出department.java实体类,如下:

package com.lm.cache.domain;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;@Data
@NoArgsConstructor
@AllArgsConstructor
public class Department  implements Serializable {private static final long serialVersionUID =  2657932609750655357L;private Long id;private String departmentName;}

给出departmentDao.java持久层操作类

package com.lm.cache.dao;import com.lm.cache.domain.Department;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;import java.util.List;@Repository
public interface DepartmentDao {@Select("select * from department")List<Department> findAll();@Select("select * from department where id = #{id}")List<Department> findById(Integer id);@Insert("insert into department(departmentName) values(#{departmentName})")int saveDepartment(Department department);@Update("update set d.departmentName = #{departmentName} from department d where d.id = #{id}")Department updateDepartment(Department department);@Delete("delete from department d where d.id = #{id}")int deleteDepartment(Department department);@Delete("delete from department d where d.id = #{id}")int deleteDepartmentById(Long id);
}

DepartmentService.java

package com.lm.cache.service;import com.lm.cache.dao.DepartmentDao;
import com.lm.cache.domain.Department;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;@CacheConfig(cacheNames = "department")
@Service
public class DepartmentService {@Autowiredprivate DepartmentDao departmentDao;@Cacheable(key = "#root.args[0]")public Department findOneDepartment(Integer id) {Department department = departmentDao.findById(id).get(0);System.out.println("service:running service to department");return department;}@CachePut(key = "#department.id")public Department updateDepartment(Department department) {return departmentDao.updateDepartment(department);}@CacheEvict(key = "#department.id")public int deleteDepartment(Department department) {return departmentDao.deleteDepartment(department);}
}

departmentController.java

package com.lm.cache.controller;import com.lm.cache.domain.Department;
import com.lm.cache.service.DepartmentService;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/dept")
public class DepartmentController {@Autowiredprivate DepartmentService departmentService;@GetMapping("/find/{id}")public Department findOneDepartment(@PathVariable("id") Integer id){return departmentService.findOneDepartment(1);}
}

出程序application.java

package com.lm.cache;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;@EnableCaching
@MapperScan("com.lm.cache.dao")
@SpringBootApplication
public class SpringbootCacheApplication {public static void main(String[] args) {SpringApplication.run(SpringbootCacheApplication.class, args);}}

实测有效,读者自测。。。
实测有效,读者自测。。。
实测有效,读者自测。。。


上面用到了四个关于spring cache abstraction的注解,其实这些注解呢,最终都会反映到所用的cache框架底层,每个cache框架底层用的配置也是不一样的。spring做的好的一点,就是把所有的缓存框架整合了起来,每个人都只用一种cache注解语法,就能实现底层缓存之间的切换。

关于cache的几个常用的注解:

  1. @EnableCaching: 这个注解标注在spring boot入口类上,表示自动缓存,即开启spring boot缓存支持。
  2. @Cacheable:这个注解表示给指定的方法或者类开启缓存,执行时机为,先在缓存中查询需要的数据,如果查到了,返回结果,且不执行注解的方法;如果没有发现,则执行注解的方法,并将注解的方法的返回结果缓存到缓存组件中。其底层代码如下所示(我会将每个属性的作用在属性上方用注释解释):
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 org.springframework.core.annotation.AliasFor;
// target是一个元注解,变送下面的注解可以用在类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
// retention表示此注解的有效时间,retentionPolicy.runtime表示此注解贯穿整个程序运行
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {// aliasfor表示别名,下面这个注解的意思就是value和chchename的作用一样,只不过名称不一致而已。// value和cachename的作用就是指定一个或者多个cache的名称,最终缓存数据都会放到每个对应cachename中去。@AliasFor("cacheNames")String[] value() default {};@AliasFor("value")String[] cacheNames() default {};// cache前面说过,底层是concurrenthashmap,这里的key就是用来标记此次缓存数据;/* key如果没有指定,默认是方法的第一个参数,如果第一个参数没有,则由spring boot中的simplekeygeneral类中的getkey生成一个key;如果注解中用属性指定了,则用指定的key属性作为标识;*/String key() default "";// 指定key的生成器,此生成器可以编写一个,只需要实现相应的方法并把其注册为bean就可以了String keyGenerator() default "";// 指定cachemanager,如果没有指定则使用SimpleCacheConfiguration来进行管理;当然如果引入别的缓存框架,或者编写了自己的cachemanager,可以在这里指定,不过建议不要写这个属性,因为我相信没有几个写的cachemanager能比spring boot或者其他框架自配的好String cacheManager() default "";// 和cachemanager的作用一样,cachemanager中会调用cacheresolver来解析cache的,也是建议不要自己写String cacheResolver() default "";// condition条件语句,表示只有符合condition条件的方法才会被缓存,支持SPEL,即spring表达式语言。// 例如:condition="#root.args[0] < 1 and #root.args[1]>2",表示方法中的参数第一个参数只有小于1,第二个参数只有大于2才会启动缓存来缓存此数据String condition() default "";// unless和condition的作用正好相反,把unless立即为除非就可以了。// 例如:unless = "#root.args[0]<1",表示除非第一个参数小于1,否则全部缓存 String unless() default "";// 是否开启同步缓存boolean sync() default false;
}
  1. @cacheput:此源码与cacheable几乎一样,无非就是少了一个Boolean sync() default false;属性而已。不过此注解的意义为,不管缓存中有没有数据,我都希望执行此注解注解的方法,且将方法结果缓存。与cacheable的不同在于,cacheable注解的方法在执行之前会检查缓存,如果缓存中有数据,就将缓存中的数据返回,而不执行方法,此注解多用于update方法。
  2. @cacheevict:此注解的作用是用来清除指定缓存数据的,多用于delete方法。
  3. @caching:此注解是一个复合注解,其内部可用多个@cacheevict,@cacheable,@cacheput,其内部注解的属性和用法与外部无意,不过读者应该注意语法冲突。
  4. @cacheconfig:此注解的作用在于将上述2到4注解中通用的属性提取出来在类上配置,在缓存加载时先读取cacheconfig类中配置的属性,然后再访问2-4中的配置属性,如果有相同的属性配置,2-4注解上配置的属性会覆盖cacheconfig上配置的属性。

给出一个key的生成器,此配置类需要配置在主入口类的同级目录中的config目录中。

package com.atguigu.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(){// 实现这个匿名类就可以实现key的生成。return new KeyGenerator(){@Overridepublic Object generate(Object target, Method method, Object... params) {return method.getName()+"["+ Arrays.asList(params).toString()+"]";}};}
}