第14章 Springboot集成EhCache实现缓存

1,268 阅读7分钟

任务描述

任务要求

使用IDEA开发工具构建一个项目多模块工程。study-springboot-chapter08学习关于Springboot集成Ehcache缓存知识点

  1. 基于study-springboot工程,复制study-springboot-chapter00标准项目,坐标groupId(com.cbitedu)、artifactId(study-springboot-chapter08),其他默认
  2. 继承study-springboot工程依赖
  3. 详细学习Spring Mybatis。
  4. 引入缓存框架ehcache实现数据缓存

任务收获

  1. 如何集成第三方持久化技术Spring Mybatis
  2. 如何引入MySQL数据库依赖
  3. Spring Boot中整合ehcache
  4. 学会使用JUnit完成单元测试
  5. Ehcache缓存的使用

任务准备

环境要求

  1. JDK1.8+
  2. MySQL8.0.27+
  3. Maven 3.6.1+
  4. IDEA/VSCode

数据库准备

创建数据库platform,并创建用户表和初始化用户表数据。

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES (1, 'tellsea', '654321');
INSERT INTO `tb_user` VALUES (2, 'susan', '123456');
INSERT INTO `tb_user` VALUES (3, 'super', '565656');

SET FOREIGN_KEY_CHECKS = 1;

工程目录要求

study-springboot-chapter08

任务实施

在Spring Boot中通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager),Spring Boot根据下面的顺序去侦测缓存提供者:

  • Generic
  • JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)
  • EhCache 2.x
  • Hazelcast
  • Infinispan
  • Couchbase
  • Redis
  • Caffeine
  • Simple

除了按顺序侦测外,我们也可以通过配置属性spring.cache.type来强制指定。我们也可以通过debug调试查看cacheManager对象的实例来判断当前使用了什么缓存。

当我们不指定具体其他第三方实现的时候,Spring Boot的Cache模块会使用ConcurrentHashMap来存储。而实际生产使用的时候,因为我们可能需要更多其他特性,往往就会采用其他缓存框架,所以接下来我们会分几篇分别介绍几个常用优秀缓存的整合与使用。

使用EhCache

介绍如何在Spring Boot中使用EhCache进程内缓存,测试验证用例(涵盖了CacheManager的注入,可用来观察使用的缓存管理类)

接下来我们通过下面的几步操作,就可以轻松的把上面的缓存应用改成使用ehcache缓存管理。

第一步:在pom.xml中引入ehcache依赖

  <!--开启 cache 缓存-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <!-- ehcache 缓存 -->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- mybatis -->
        <!-- 实现对 MyBatis 的自动化配置 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <!-- 通用Mapper启动器 -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

在Spring Boot的parent管理下,不需要指定具体版本,会自动采用Spring Boot中指定的版本号。

第二步:在src/main/resources目录下创建:ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <defaultCache
            eternal="false"
            maxElementsInMemory="1000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="0"
            timeToLiveSeconds="600"
            memoryStoreEvictionPolicy="LRU"/>

    <!-- 这里的 users 缓存空间是为了下面的 demo 做准备 -->
    <cache
            name="users"
            eternal="false"
            maxElementsInMemory="100"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="0"
            timeToLiveSeconds="300"
            memoryStoreEvictionPolicy="LRU"/>
</ehcache>
<!--
diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。
defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统当机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。

FIFO,first in first out,先进先出。
LFU, Less Frequently Used,一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->

第三步:完成任务准备中的数据库工作,创建表

第四步:分别在com.cbitedu.springboot下创建软件包

  • 实体类:entity
  • 接口层:mapper
  • 服务层:service
  • 控制台:controller

创建tb_user表的映射对象User

com.cbitedu.springboot.entity

package com.cbitedu.springboot.entity;

import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;

import javax.persistence.Id;
import javax.persistence.Table;

@Data
@Table(name = "tb_user")
public class User {

    @Id
    @KeySql(useGeneratedKeys = true)
    private Long id;
    private String name;
    private String password;
}

创建映射接口mapper( com.cbitedu.springboot.dao

package com.cbitedu.springboot.dao;

import com.cbitedu.springboot.entity.User;
import tk.mybatis.mapper.common.Mapper;

public interface UserMapper extends Mapper<User> {
}

创建服务接口UserService com.cbitedu.springboot.service

package com.cbitedu.springboot.service;


import com.cbitedu.springboot.entity.User;

import java.util.List;

public interface UserService {

    void insert(User user);

    void update(User user);

    void delete(Long id);

    User get(Long id);

    List<User> list();
}

创建服务接口实现类UserServiceImpl com.cbitedu.springboot.service.impl

package com.cbitedu.springboot.service.impl;

import com.cbitedu.springboot.dao.UserMapper;
import com.cbitedu.springboot.entity.User;
import com.cbitedu.springboot.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
import org.springframework.util.CollectionUtils;

import java.util.List;

@Service
@CacheConfig(cacheNames = "users")//缓存名字
public class UserServiceImpl implements UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
    @Autowired
    private UserMapper userMapper;

    //这里的单引号不能少,否则会报错,被识别是一个对象
    private static final String CACHE_KEY = "'user'";

    //新增
    @Override
    @CachePut(key = CACHE_KEY)//先执行方法,然后将返回值存入缓存
    public void insert(User user) {
        int count = userMapper.insert(user);
        if (count != 1) {
            logger.info("新增失败");
        }
    }

    //更新
    @Override
    @CachePut(key = "'user_'+#user.getId()")//先执行方法,然后将返回值存入缓存
    public void update(User user) {
        int count = userMapper.updateByPrimaryKey(user);
        if (count != 1) {
            logger.info("更新失败");
        }
    }

    //删除
    @Override
    @CacheEvict(key = "'user_'+#id") //清除缓存
    public void delete(Long id) {
        int count = userMapper.deleteByPrimaryKey(id);
        if (count != 1) {
            logger.info("删除失败");
        }
    }

    //查找
    @Override
    @Cacheable(key = "'user_'+#id")//先查缓存,有就直接返回,没得就把返回值加入缓存
    public User get(Long id) {
        return userMapper.selectByPrimaryKey(id);
    }

    @Override
    @Cacheable(key = "'users'")
    public List<User> list() {
        List<User> list = userMapper.selectAll();
        if (!CollectionUtils.isEmpty(list)) {
            return list;
        }
        return null;
    }

    /*
     * @Cacheable : Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,
     *              而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。
     * @CacheEvict : 清除缓存。
     * @CachePut : 也可以声明一个方法支持缓存功能。使用@CachePut标注的方法在执行前不会去检查缓存
     *              中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
     * 这三个方法中都有两个主要的属性:value 指的是 ehcache.xml 中的缓存策略空间;key 指的是缓存的标识,同时可以用 # 来引用参数。
     * */
}

创建EhCacheConfig配置类EhCacheConfig,(com.cbitedu.springboot.config)

package com.cbitedu.springboot.config;

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;

/**
 * 启用EhCache缓存
 */
@Configuration
@EnableCaching
public class EhCacheConfig {

}

新建测试类SpringCacheTest

package com.cbitedu.springboot.web;

import com.cbitedu.springboot.entity.User;
import com.cbitedu.springboot.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringCacheTest {
    @Test
    public void contextLoads() {
    }
    private static final Logger logger = LoggerFactory.getLogger(SpringCacheTest.class);

    @Autowired
    private UserService userService;

    /**
     * 新增
     */
    @Test
    public void insert() {
        User user = new User();
        user.setName("insert");
        user.setPassword("123456");
        userService.insert(user);
    }

    /**
     * 更新
     */
    @Test
    public void update() {
        User user = new User();
        user.setId(1L);
        user.setName("tellsea");
        user.setPassword("654321");
        userService.update(user);
    }

    /**
     * 删除
     */
    @Test
    public void delete() {
        userService.delete(2L);
    }

    /**
     * 查询单个
     */
    @Test
    public void get() {
        User user = userService.get(1L);
        User user1 = userService.get(1L);
        logger.info("第一次:" + user);
        logger.info("第二次:" + user1);
    }

    /**
     * 查询所有
     */
    @Test
    public void list() {
        List<User> list = userService.list();
        list.forEach(user ->logger.info(user.toString()));
        logger.info("第二遍");
        List<User> list2 = userService.list();
        list2.forEach(user -> logger.info(user.toString()));
    }
}
  1. 第一行输出的打印了执行的SQL
  2. 第二次查询的时候,没有输出SQL语句,所以是走的缓存获取

实验实训

1、Ehcache缓存,如何清除缓存(插入、修改和删除数据)?

详细教程:www.cnblogs.com/hanease/p/1…

基本介绍: Ehcache 是一种基于标准的开源缓存,可提高性能,卸载数据库并简化可伸缩性。

它是使用最广泛的基于 Java 的缓存,因为它功能强大,经过验证,功能齐全,并与其他流行的库和框架集成。 

Ehcache 可以从进程内缓存扩展到使用 TB 级缓存的混合进程内/进程外部署。

Ehcache 应用场景如下图: