Spring系列之缓存技术的应用

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保存到缓存中

切换缓存技术

  1. 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>

其他的缓存技术与此类似

总结

缓存技术是现在开发中不可或缺的一块,他可以让我们的系统在高并发的环境下保持高性能。同时降低我们数据库的压力。 

人们总是在经历一些事情之后就会悄无声息的换了种性格。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

挚爱妲己~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值