《SpringBoot从入门到放弃》之第(九)篇——EhCache缓存

版权声明: https://blog.csdn.net/BiandanLoveyou/article/details/83176789

一个较大的项目,如果用户数量不断的增多,而程序里都是直接操作数据库的话,并定会造成数据库出现瓶颈,无法处理高并发的问题。此时使用缓存是解决问题的一个良好办法之一,读取缓存的数据的速度往往比连接数据库查询快很多。

在 pom.xml 配置文件加上 jar 依赖:

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>

在SpringBoot主类中增加 @EnableCaching 注解开启缓存功能:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

//@MapperScan("com.test.dao") //在启动类添加扫描 Mybatis 层的注解或者在 Dao 层添加 @Mapper 注解
@SpringBootApplication
@EnableCaching
public class MyTest02Application {

	public static void main(String[] args) {
		SpringApplication.run(MyTest02Application.class, args);
	}
}

我们在 Dao 层的类里添加注解 @CacheConfig(cacheNames = "userDaoCache"),在查询数据库的方法里添加注解:@Cacheable 完整代码:

package com.test.dao;

import com.test.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;

import java.util.List;

@CacheConfig(cacheNames = "userDaoCache")
@Mapper
public interface UserDao {

    /**
     * 新增用户
     * @param user
     * @return
     */
    Integer addUserMybatis(User user);

    /**
     * 根据id删除用户
     * @param id
     * @return
     */
    Integer deleteUserMybatis(Integer id);

    /**
     * 更新用户信息
     * @param user
     * @return
     */
    Integer updateUserMybatis(User user);

    /**
     * 查询所有用户信息
     * @return
     */
    @Cacheable
    List<User> getUserMybatis();
}

为了测试效果,我们在 Controller 层里查询方法打印一些标记:

    /**
     * 查询所有用户信息
     * @return
     */
    @RequestMapping(value = "/getUserMybatis")
    public List<User> getUserMybatis(){
        List<User> list = userServiceMybatis.getUserMybatis();
        System.out.println("执行查询完毕,时间戳="+System.currentTimeMillis());
        return list;
    }

O的K,启动测试类,用 postman 测试查询功能:

查看控制台信息:

JDBC Connection [HikariProxyConnection@827904177 wrapping com.mysql.jdbc.JDBC4Connection@58cf54c7] will not be managed by Spring
==>  Preparing: select * from t_user 
==> Parameters: 
<==    Columns: id, name, signature
<==        Row: 1, 流放深圳, 让天下没有难写的代码
<==        Row: 4, 腾讯NBA, 让我遇见你
<==        Row: 5, 农夫山泉, 大自然的搬运工
<==        Row: 7, 小米, 为发烧而生
<==      Total: 4
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@44993039]
执行查询完毕,时间戳=1539915266526

然后,我们使用更新用户的功能,把id=4的用户修改:

然后,再次点击查询,再查看控制台信息:

查看查询的数据,发现并不是我们更新后的数据,而是缓存的旧数据:

说明:

①@CacheConfig(cacheNames = "userDaoCache"):配置了该数据访问对象中返回的内容将存储于名为userDaoCache的缓存对象中。

②@Cacheable:配置了 getUserMybatis 方法的返回值将被加入缓存。在查询时,会先从缓存中获取,若不存在才去查询数据库获取数据。

按住 Ctrl + 鼠标左键,进入 @Cacheable 注解,可以看到如下的内容:

@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;
}

1、value、cacheNames:两个等同的参数,用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig
key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = "#A1"):使用函数第一个参数作为缓存的key值。
2、condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = "#A1", condition = "#A1.length() < 3"),表示只有当第一个参数的长度小于3的时候才会被缓存。
3、unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。
4、keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的
5、cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用
6、cacheResolver:用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。

在SpringBoot中通过启动类的 @EnableCaching 注解自动化配置合适的缓存管理器(CacheManager),SpringBoot根据下面的顺序去侦测缓存提供者:Generic、JCache (JSR-107)、EhCache 2.x、Hazelcast、Infinispan、Redis、Guava、Simple。

除了按顺序监测外,我们可以通过配置文件 spring.cache.type 来指定缓存的类型。比如我们在 application.properties 配置文件里添加如下配置,指定使用 ehcache 缓存:

spring.cache.type=ehcache
spring.cache.ehcache.config=classpath:cache/ehcache.xml

在SpringBoot中开启EhCache比较简单,只需要在工程中加入 ehcache.xml 配置文件并在 pom.xml 中增加ehcache依赖,框架只要发现该文件,就会创建EhCache的缓存管理器。

在 pom.xml 配置文件中添加 jar 依赖:

		<dependency>
			<groupId>net.sf.ehcache</groupId>
			<artifactId>ehcache</artifactId>
		</dependency>

然后在 resources 目录下创建 cache 文件夹,在cache 文件夹下创建 ehcache.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="defaultCache">
    <!-- 磁盘存储位置:会在对应的目录下生成相应的缓存文件 -->
    <diskStore path="src/main/resources/cache/temp"/>
    <!-- 默认缓存配置. -->
    <defaultCache maxEntriesLocalHeap="100" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600"
                  overflowToDisk="true" maxEntriesLocalDisk="100000"/>
    <!-- 用户系统缓存 -->
    <cache name="cacheUserName" maxEntriesLocalHeap="100" eternal="true" overflowToDisk="true"/>
</ehcache>

说明:在 <cache name="***"> </cache>  定义好全局缓存的名字,就不需要在 Dao 层方法再指定名称了,也无需在方法里增加什么注解。因此,UserDao 就可以解放:

package com.test.dao;

import com.test.entity.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserDao {

    /**
     * 新增用户
     * @param user
     * @return
     */
    Integer addUserMybatis(User user);

    /**
     * 根据id删除用户
     * @param id
     * @return
     */
    Integer deleteUserMybatis(Integer id);

    /**
     * 更新用户信息
     * @param user
     * @return
     */
    Integer updateUserMybatis(User user);

    /**
     * 查询所有用户信息
     * @return
     */
    List<User> getUserMybatis();
}

在类 MybatisController 里增加一个测试缓存的方法 cacheTest,并修改 getUserMybatis 方法:

    @Autowired(required = false)
    private CacheManager cacheManager;
    private final static String CACHE_NAME = "cacheUserName";
    private final static String GET_CACHE_KEY = "getCacheKey";

    /**
     * 测试缓存
     * @return
     */
    @RequestMapping(value = "/cacheTest")
    public List<User> cacheTest(){
        Cache cache = cacheManager.getCache(CACHE_NAME);
        if(cache.get(GET_CACHE_KEY) != null){
            List<User> list = (List)cache.get(GET_CACHE_KEY).get();
            return list;
        }else{
            return new ArrayList<>();
        }
    }

    /**
     * 查询所有用户信息
     * @return
     */
    @RequestMapping(value = "/getUserMybatis")
    public List<User> getUserMybatis()throws Exception{
        List<User> list;
        Cache cache = cacheManager.getCache(CACHE_NAME);
        //如果缓存里有数据,则取缓存的数据;如果缓存里没有数据,则查询数据库,并将结果放入缓存中。
        if(cache.get(GET_CACHE_KEY) == null){
            list = userServiceMybatis.getUserMybatis();
            cache.put(GET_CACHE_KEY,list);
        }else{
            list = (List)cache.get(GET_CACHE_KEY).get();
        }
        System.out.println("执行查询完毕,时间戳="+System.currentTimeMillis());
        return list;
    }

说明:cacheUserName 对应的是配置文件 ehcache.xml 里定义的缓存name。

启动服务,用 postman 测试,http://localhost:9090/getUserMybatis

发现有问题:

O的K,那就把 User 实体类序列化,很简单,就是实现序列化接口即可: implements Serializable

public class User implements Serializable

重启,再次测试 http://localhost:9090/getUserMybatis:完美无误。这时候在目录下也生成了缓存文件:

测试:http://localhost:9090/cacheTest,完美无误。

接下来学习一下,如何清空缓存:

    /**
     * 清空缓存
     * @return
     */
    @RequestMapping(value = "/clearCache")
    public Map<String,Boolean> clearCache(){
        Cache cache = cacheManager.getCache(CACHE_NAME);
        cache.clear();
        Map<String, Boolean> map = new HashMap<>();
        map.put("flag", true);
        return map;
    }

重启,测试,先运行 http://localhost:9090/getUserMybatis,缓存里有数据,再测试:http://localhost:9090/clearCache

再测试查询用户: http://localhost:9090/getUserMybatis,在 debug 模式下打断点,可以看到缓存里没有数据,重新去数据库查询数据了。

这样的设计适用于一种场景:把用户经常访问的内容加入到缓存,能加快数据的获取,提高用户的使用体验。当需要修改内容时,可以清空缓存的数据。第一个用户点击获取,查询缓存没有数据了,就去数据库查询数据,然后加入缓存。为了保证“顺时”问题,可以使用数据覆盖的形式,不一定需要清空,可以使用 put 方法进行覆盖。

猜你喜欢

转载自blog.csdn.net/BiandanLoveyou/article/details/83176789