Spring缓存支持
Spring定义了CacheManager和Cache接口用来统一不同的缓存的技术,其中CacheManager是Spring提供的各种缓存技术抽象接口。Cache接口包含缓存的各种操作(增加,删除,获得缓存)。
Spring支持的CacheManager
针对不同的缓存技术,需要实现不同的CacheManager,Spring定义了如下图所示的CacheManager实现
CacheManager | 描述 |
---|---|
SimpleCacheManager | 使用简单的Collection来存储数据,主要用于测试用途 |
ConcurrentMapCacheManager | 使用ConcurrentMap来存储数据 |
NoOpCacheManager | 仅测试用途不会实际存储数据 |
EhCacheCacheManager | 使用EhCache作为缓存技术 |
GuavaCacheManager | 使用Google Guava 的 GuavaCache作为缓存技术 |
HazelcastCacheManager | 使用Hazelcast作为缓存技术 |
JCacheCacheManager | 支持JCache(JSR-107)标准的实现作为缓存技术,如Apache Commons JCS |
RedisCacheManager | 使用redis作为缓存技术 |
在我们使用任意一个实现的CacheManager的时候,需要注册实现的CacheManager的Bean
例如:
public EhCacheCacheManager cacheCacheManager(CacheManager cacheManager){
return new EhCacheCacheManager(cacheManager);
}
声明式缓存注解
Spring提供了四个注解来声明缓存规格
注解 | 解释 |
---|---|
@Cacheable | 在方法执行前Spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据,若没有数据,调用方法并将方法的返回值放进缓存 |
@CachePut | 无论怎样,都会将方法的返回值放到缓存中 ,@CachePut的属性与@Cacheable 保持一致 |
@CacheEvict | 将一条或者多条数据从缓存中删除 |
@Caching | 可以通过此注解组合多个注解策略在一个方法上 |
开启声明式缓存支持只需要在配置类上使用@EnableCaching注解即可
@Configuration
@EnableCaching
public class BeanConfig {
}
Springboot的支持
Springboot支持以"spring.cache"为前缀的属性来配置缓存,再不做任何额外的情况下,默认使用的是SimpleCacheConfiguration
spring.cache.type= #可选generic , ehcache ,hazelcast,infinispan,jcache,redis,guava,simple,none。
spring.cache.cache-name= #程序启动时创建的缓存名称
spring.cache.ehcache.config = #ehcache 配置文件的地址
spring.cache.hazelcast.config = #hazelcast 配置文件的地址
spring.cache.infinispan.config = #infinispan 配置文件的地址
spring.cache.jcache.config = #jcache 配置文件的地址
怎么做
本例将以SpringBoot默认的ConcurrentMapCacheManager作为缓存技术。
① 新建数据库表 菜单表
② 利用mybatis的逆向工程生成实体类,mapper文件,xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://127.0.0.1:3306/db_middleware"
userId="root"
password="root">
</jdbcConnection>
<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver"
connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg"
userId="yycg"
password="yycg">
</jdbcConnection> -->
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- targetProject:生成PO类的位置 -->
<javaModelGenerator targetPackage="com.dubug.middleware.entity"
targetProject="src/main/java">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false"/>
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="mapper"
targetProject="src/main/resources">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.dubug.middleware.mapper"
targetProject="src/main/java">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false"/>
</javaClientGenerator>
<table tableName="db_menu"
enableInsert="true"
enableDeleteByPrimaryKey="true"
enableSelectByPrimaryKey="true"
enableUpdateByPrimaryKey="true"
enableCountByExample="false"
enableDeleteByExample="false"
enableSelectByExample="false"
enableUpdateByExample="false"
></table>
<!-- 有些表的字段需要指定java类型
<table schema="" tableName="">
<columnOverride column="" javaType="" />
</table> -->
</context>
</generatorConfiguration>
在maven依赖中加入
<plugin>
<!--
用maven mybatis插件
如果不在plugin里面添加依赖包得引用的话,会找不到相关得jar包,
在plugin外部得jar包,他不会去找到并执行,
所以要把plugin运行依赖得jar配置都放在里面
-->
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
</dependencies>
</plugin>
package com.dubug.middleware.entity;
import com.dubug.middleware.constant.SystemConst;
import lombok.Builder;
import lombok.Data;
import java.util.Date;
import java.util.Objects;
@Data
public class DbMenu {
private String id;
private String name;
private Integer sort;
private String parentId;
private Integer isShow;
private Date createTime;
public static DbMenu saveMenu(DbMenu dbMenu) {
dbMenu.setCreateTime(new Date());
dbMenu.setIsShow(1);
dbMenu.setParentId(Objects.isNull(dbMenu.getParentId()) ? SystemConst.RootMenu : dbMenu.getParentId());
return dbMenu;
}
}
package com.dubug.middleware.mapper;
import com.dubug.middleware.entity.DbMenu;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
public interface DbMenuMapper {
int deleteByPrimaryKey(String id);
int insert(DbMenu record);
int insertSelective(DbMenu record);
DbMenu selectByPrimaryKey(String id);
int updateByPrimaryKeySelective(DbMenu record);
int updateByPrimaryKey(DbMenu record);
@Update("update db_menu set is_show =#{isShow} where id =#{id}")
int updateShow(@Param("id") String id,@Param("isShow") Integer isShow);
}
③ 快速编写三个接口类
package com.dubug.middleware.controller;
import com.dubug.middleware.entity.DbMenu;
import com.dubug.middleware.service.MenuService;
import com.dubug.middleware.util.IdGeneratorRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description
* @Author 菜单控制器
* @Version V1.0.0
* @Date 2021/6/10 0010
*/
@RestController
@RequestMapping("/api/menu")
public class MenuController {
@Autowired
private MenuService menuService;
/**
* 保存菜单
*/
@PostMapping("/save")
public void saveMenu(@RequestBody DbMenu dbMenu) {
dbMenu.setId(IdGeneratorRegistry.getId());
menuService.saveMenu(dbMenu);
}
/**
* 根据Id查询一个菜单
* @param id
* @return
*/
@GetMapping("/select_by_id")
public DbMenu selectById(@RequestParam("id") String id) {
return menuService.selectById(id);
}
/**
* 根据Id删除一个菜单
* @param id
*/
@GetMapping("/delete_by_id")
public void deleteById(@RequestParam("id") String id) {
menuService.deleteMenu(id);
}
}
package com.dubug.middleware.service;
import com.dubug.middleware.entity.DbMenu;
import com.dubug.middleware.mapper.DbMenuMapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@AllArgsConstructor
@Component
@Slf4j
public class MenuService {
private final DbMenuMapper dbMenuMapper;
/**
* 保存菜单数据
* value 是指我们在缓存的名称 需要在配置文件中配置
* key 是指缓存的key
*/
@CachePut(value = "menu", key = "#dbMenu.id")
@Transactional(rollbackFor = Exception.class)
public DbMenu saveMenu(DbMenu dbMenu) {
log.info("已缓存{}",dbMenu);
dbMenu= dbMenu.saveMenu(dbMenu);
dbMenuMapper.insertSelective(dbMenu);
return dbMenu; //一定要将返回值return出去 无论前端是否需要
}
/**
* 删除菜单数据
*/
@CacheEvict(value = "menu")
@Transactional(rollbackFor = Exception.class)
public void deleteMenu(String id) {
log.info("已删除缓存数据{}",id);
dbMenuMapper.updateShow(id, 2);
}
/**
* 查询缓存数据
*/
@Transactional(readOnly = true)
@Cacheable(value = "menu", key = "#id")
public DbMenu selectById(String id) {
log.info("查询缓存数据{}",id);
return dbMenuMapper.selectByPrimaryKey(id);
}
}
启动我们的项目 利用postman接口测试工具,测试我们的缓存是否生效
1:首选调用save方法保存一条数据,加入到我们的缓存中
2:调用select_by_id 方法查询缓存中的数据,看下是否走数据库 从日志中提取上一步保存的Id
可以看到
数据并没有从数据库获取,而是从缓存中获取的
3: 调用delete_by_id 删除方法 删除缓存中的数据
然后再调用查询方法看是否是走了数据库
可以看到这次是走了数据库, 接着我们再次调用查询方法 , 可以预测这次是走缓存
如果没有指定key , 则方法参数作为key保存到缓存中
切换缓存技术
- EhCache
当我们需要使用EhCache作为缓存技术时,我们只需要在pom.xml中添加EhCache的依赖即可
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
EhCache所需的配置文件ehcache.xml只需放在类路径下 SpringBoot会自动扫描 例如:
<ehcache>
<!--
磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存
path:指定在硬盘上存储对象的路径
path可以配置的目录有:
user.home(用户的家目录)
user.dir(用户当前的工作目录)
java.io.tmpdir(默认的临时目录)
ehcache.disk.store.dir(ehcache的配置目录)
绝对路径(如:d:\\ehcache)
查看路径方法:String tmpDir = System.getProperty("java.io.tmpdir");
-->
<diskStore path="java.io.tmpdir" />
<!--
defaultCache:默认的缓存配置信息,如果不加特殊说明,则所有对象按照此配置项处理
maxElementsInMemory:设置了缓存的上限,最多存储多少个记录对象
eternal:代表对象是否永不过期 (指定true则下面两项配置需为0无限期)
timeToIdleSeconds:最大的发呆时间 /秒
timeToLiveSeconds:最大的存活时间 /秒
overflowToDisk:是否允许对象被写入到磁盘
说明:下列配置自缓存建立起600秒(10分钟)有效 。
在有效的600秒(10分钟)内,如果连续120秒(2分钟)未访问缓存,则缓存失效。
就算有访问,也只会存活600秒。
-->
<defaultCache maxElementsInMemory="10000" eternal="false"
timeToIdleSeconds="600" timeToLiveSeconds="600" overflowToDisk="true" />
<cache name="menu" maxElementsInMemory="10000" eternal="false"
timeToIdleSeconds="120" timeToLiveSeconds="600" overflowToDisk="true" />
</ehcache>
其他的缓存技术与此类似
总结
缓存技术是现在开发中不可或缺的一块,他可以让我们的系统在高并发的环境下保持高性能。同时降低我们数据库的压力。
人们总是在经历一些事情之后就会悄无声息的换了种性格。