MyBatisCache原理分析及案例实战——快速理解MyBatisCache机制

作者:禅与计算机程序设计艺术

1.简介

MyBatis Cache 是 MyBatis 框架的一个重要的扩展插件,它提供了一整套缓存解决方案,可有效地提升系统的运行效率。 MyBatis Cache 通过设置缓存配置并通过注解的方式来将数据查询结果进行缓存,缓存数据包括增删改查的数据和对象,提供强大的查询缓存功能。由于 MyBatis Cache 具有良好的扩展性、通用性、灵活性、可定制化能力等特点,能够很好地满足不同应用场景下的缓存需求。
本文将会详细阐述 MyBatis Cache 的内部机制,重点介绍其工作原理、配置参数、高级特性以及相关案例,力争打造一个易于理解、实用的 MyBatis Cache 技术分享。
阅读本文,您将了解到:

  • 为什么需要 MyBatis Cache?
  • MyBatis Cache 基本概念、术语和配置参数介绍
  • MyBatis Cache 内部机制解析
  • MyBatis Cache 高级特性介绍
  • 实战:基于 SpringBoot + MyBatis + MyBatis Cache 的缓存案例

2.为什么需要 MyBatis Cache?

2.1 查询性能优化

在实际业务开发中,数据库查询操作是应用最频繁的操作之一,因此优化数据库查询操作的性能至关重要。而 MyBatis Cache 就是为了提升数据库查询性能而产生的一种解决方案。

2.2 数据一致性保障

在分布式环境下,对于同一份数据在不同的节点上可能存在不一致的情况。如果两个节点都查询到了过期的数据,就可能出现数据不一致的现象。这时候就需要引入缓存机制来保证数据的一致性。

3. MyBatis Cache 基本概念、术语和配置参数介绍

3.1 缓存分类

Mybatis Cache 有四种缓存类型:

  • LOCAL: 只对当前 Session 中的数据生效,当 Session 关闭后失效。
  • SESSION: 对当前 Session 中的所有数据生效,只要不是过期就永远不会失效。
  • STATEMENT: 对整个过程中的所有语句生效,只要语句没有变化,就会命中缓存,否则就重新执行查询。
  • FULL: 对所有的查询结果都进行缓存。

3.1.1 默认缓存策略

默认情况下, MyBatis 会采用 STATEMENT 缓存策略,即只针对每个 SQL 语句的结果进行缓存。

3.1.2 XML 配置 cache 属性

<cache type="org.mybatis.caches.ehcache.EhcacheCache" />
<!-- 
     设置本地缓存 (Session) 
     默认缓存级别 
     可选级别:
     NONE = 不缓存
     BASIC = 缓存方法输入参数值
     DEFAULT = 基本+历史结果缓存(DEFAULT_2NDLEVEL_CACHE)
     INFINITE = 一直缓存 
     使用自定义缓存实现类时需要同时指定自定义缓存器 class 
       eg:<cache type="com.company.YourCustomCachingProvider"/> 
-->
<cache type="org.apache.ibatis.cache.decorators.LruCache" eviction="LRU">
      <!-- 
           最大缓存元素个数 (Optional),默认为 1024 
           0 表示无限制,建议设定合理大小 
            
           可以通过如下参数进行设置: 
           size = "1024" | "unlimited"
     -->
      </cache>
      <!-- 
           指定装饰器顺序 (Optional),默认为 FIFO(先进先出) 
           可选顺序:FIFO(先进先出),LRU(最近最少使用),SOFT(软引用),WEAK (弱引用)。 
           注意:只有 LRU 和 SOFT 支持统计缓存命中次数。 
     -->
<cache-eviction/>
<!-- 
     设置缓存回收策略 
     淘汰算法 
     如果容器满了之后该如何处理,默认是 LRU(最近最少使用) 
     
     参数: 
     type = “LeastRecentlyUsed” 
          | “FirstInFirstOut” 
          | “LeastFrequentlyUsed” 
          | “MostFrequentlyUsed” 
          | “RRWQueue”
          | “RandomAccess”
     maxEntries = “1024” //设置最大缓存条目数量
     timeToLiveSeconds = “0”//缓存超时时间,0代表永不过期
-->

3.1.3 Annotation 配置 @CacheNamespace 和 @CacheNamespaceRef

package org.apache.ibatis.annotations;

import java.lang.annotation.*;

/**
 * The Cache annotation is used to specify the name of a cache to be used for caching queries and/or results.
 */
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Cache {
    String value() default "";
}

/**
 * Specifies that all the methods in a class or interface should share the same namespace.
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface CacheNamespace {
    String value();
}

/**
 * Used with {@link CacheNamespace} to reference another namespace by its alias.
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface CacheNamespaceRef {
    String value();
}

3.2 高级特性

3.2.1 查询预防CacheKey

查询预防CacheKey 是指根据查询条件生成cacheKey 。有了cacheKey后,系统可以判断查询缓存是否存在,如果存在则直接从缓存中取出数据,避免重复查询数据库,提高查询效率。

3.2.2 缓存共享

缓存共享 是指多个模块共用一个缓存,使得各个模块之间的缓存数据一致。缓存共享是MyBatis Cache 提供的高级特性,通过在注解或者xml文件中定义namespace ,然后使用@CacheNamespaceRef注解或者cache-ref标签即可实现。

3.2.3 分布式缓存

分布式缓存 是指缓存服务器部署在不同的服务器上,用于缓解单机缓存服务器的访问压力。分布式缓存使得缓存的生命周期更长,并提供了容错恢复机制,降低缓存服务的单点故障风险。

4. MyBatis Cache 内部机制解析

MyBatis Cache 内部实现主要由三大模块构成:缓存管理器、缓存存储、缓存键生成器。缓存管理器负责管理缓存的各种配置信息和缓存实例;缓存存储负责存储缓存的数据;缓存键生成器负责根据用户请求的参数生成CacheKey。下面我们逐一来看一下这些模块的实现原理和流程。

4.1 缓存管理器

4.1.1 创建缓存管理器
// createCache 方法创建缓存管理器实例
private static final CacheManager cacheManager = new CacheManager(settings);
4.1.2 设置缓存配置
private static final Configuration config = new Configuration();

private static void setCacheConfig() {
Properties settings = new Properties();
settings.setProperty("defaultCache", "true");
settings.setProperty("cache.type", "org.apache.ibatis.cache.ehcache.EhcacheCache");
settings.setProperty("cache.ehcache.configLocation", "/path/to/ehcache.xml");
EhcacheCacheFactory factory = new EhcacheCacheFactory(settings);
DefaultSqlSession sqlSession = new DefaultSqlSession(config, null);
LocalCacheScope localCacheScope = new LocalCacheScope(sqlSession, factory);
config.setLocalCacheScope(localCacheScope);
}

4.2 缓存存储

4.2.1 创建缓存存储
// 通过缓存配置信息获取缓存的实现类
final Ehcache cache = (Ehcache)cacheFactory.createCache(properties.getProperty("cache.id"));
4.2.2 添加、更新、删除缓存数据
if (!keys.isEmpty()) {
// 添加缓存数据
List<String> addKeyList = new ArrayList<>();
for (Object key : keys) {
  Object value = entry.getValue().get(key);
  try {
      cache.put(new Element(generateKey(key), serializer.serialize(value)));
      addKeyList.add((String) key);
  } catch (IOException ex) {
      throw new CacheException("Add failed.", ex);
  }
}
// 从列表中删除已经成功添加到缓存中的key
removeFromList(entry.getKey(), addKeyList, true);
} else {
// 删除缓存数据
try {
  cache.remove(generateKey(key));
} catch (IllegalStateException ise) {
  // Ignore this exception as it means there was no element found with that key.
}
}

4.3 缓存键生成器

4.3.1 生成CacheKey
return generateKey(statementId, parameterObject, rowBounds, resultHandler, boundSql);
4.3.2 根据请求参数生成CacheKey
private String generateKey(String statementId, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StringBuilder keyBuilder = new StringBuilder(statementId);
ParameterHandler handler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
keyBuilder.append(":")
      .append(handler.getParameterMapping());
if (rowBounds!= RowBounds.DEFAULT) {
  keyBuilder.append(":").append(rowBounds);
}
if (resultHandler!= null) {
  keyBuilder.append(":").append(System.identityHashCode(resultHandler));
}
return keyBuilder.toString();
}

5. MyBatis Cache 高级特性介绍

MyBatis Cache 提供的高级特性:查询预防CacheKey、缓存共享以及分布式缓存。下面我们来详细介绍一下这三个特性的内部机制。

5.1 查询预防CacheKey

5.1.1 原理

查询预防CacheKey 是MyBatis Cache 的一个高级特性,通过计算请求参数生成CacheKey ,来判断查询缓存是否存在。

5.1.2 操作步骤

  1. 在配置文件中启用查询预防CacheKey功能

    <setting name="cacheEnabled" value="true"/> 
    
  2. 在XML或注解中指定 @CacheNamespaceRef 属性

    <cache-ref namespace="myNS"/> 
    
  3. 生成CacheKey

    private String generateCacheKey() {
    

StringBuffer sb = new StringBuffer();
sb.append(“select “).append(”*”).append(" from myTable where id=‘“).append(userId).append(”’ “);
if(!condition.equals(”“)) {
sb.append(“and condition='”).append(condition).append(”’ ");
}
return sb.toString();
}


4. 通过CacheKey 查询缓存

```java
BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
CacheKey cacheKey = new CacheKey(ms.getId(), mappedStatement.getConfiguration(), parameterObject, RowBounds.DEFAULT, boundSql);
Object cachedObj = cache.getObject(cacheKey);

上面的代码通过CacheKey 查询缓存。

5.2 缓存共享

5.2.1 原理

缓存共享 是MyBatis Cache 的一个高级特性,允许多个模块共用一个缓存,使得各个模块之间的缓存数据一致。

5.2.2 操作步骤

  1. 在 XML 或注解 中定义 @CacheNamespace 和 @CacheNamespaceRef 属性

    <cache-namespace alias="ns1" />
    <cache-ref namespace="ns1" property="myCache"/>
    <cache-ref namespace="ns1" property="yourCache"/>
    
  2. 在使用缓存的地方注入缓存

    @Autowired
    private YourCache yourCache;
    
  3. 使用缓存

    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey cacheKey = new CacheKey(ms.getId(), ms.getConfiguration(), parameterObject, RowBounds.DEFAULT, boundSql);
    Object cachedObj = yourCache.getObject(cacheKey);
    

    上面的代码通过CacheKey 获取缓存数据。

5.3 分布式缓存

5.3.1 原理

分布式缓存 是MyBatis Cache 的一个高级特性,通过将缓存数据存放在分布式缓存服务器上,来缓解单机缓存服务器的访问压力。

5.3.2 操作步骤

  1. 安装分布式缓存服务器,如 Memcached

  2. 修改配置文件

    <cache-namespace alias="ns1" >
      <property name="distributed" value="true" /> 
      <property name="serialization" value="java"/>
    </cache-namespace>
    
  3. 在修改后的配置文件中,设置分布式缓存相关属性

    <setting name="cachePlugin" value="com.domain.yourplugin.YourPlugin"/> 
    <setting name="cacheRegistry" value="com.domain.yourregistry.YourRegistry"/> 
    <setting name="cacheSerializer" value="com.domain.yourserializer.YourSerializer"/> 
    
  4. 编写分布式缓存插件和注册中心

   public class YourPlugin implements CachePlugin {
 // Implementation goes here...
   }
 public class YourRegistry extends CacheRegistry {
 // Implementation goes here...
   }
 public class YourSerializer implements CacheSerializer {
 // Implementation goes here...
   }
  1. 使用缓存
   BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
   CacheKey cacheKey = new CacheKey(mappedStatement.getId(), mappedStatement.getConfiguration(), parameterObject, RowBounds.DEFAULT, boundSql);
   Object cachedObj = cache.getObject(cacheKey);

上面的代码通过CacheKey 获取缓存数据。

6. 实战:基于 SpringBoot + MyBatis + MyBatis Cache 的缓存案例

6.1 准备环境

6.1.1 数据库

本文使用的数据库为 MySQL。

首先创建一个空数据库,例如,命名为 mybatis_test。然后在 MySQL 命令行模式下,依次执行以下命令:

CREATE TABLE user (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    age INT NOT NULL
);
6.1.2 Maven

在项目的 pom.xml 文件中加入 MyBatis 和 MyBatis Cache 的依赖项。

<!-- MyBatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>${mybatis.version}</version>
</dependency>

<!-- MyBatis Cache -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-cachae</artifactId>
    <version>${mybatis.cache.version}</version>
</dependency>

其中 ${mybatis.version}${mybatis.cache.version} 需要替换为正确的版本号。

6.1.3 Spring Boot starter

在 pom.xml 文件中加入 Spring Boot Starter Web 的依赖项。

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

6.2 简单查询

首先,编写一个简单的查询接口 /users,返回数据库中的所有用户记录。

package com.example.demo.controller;

import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

    @Autowired
    private SqlSessionFactory sessionFactory;

    @Autowired
    private UserService userService;

    @GetMapping("/users")
    public List<User> getUsers() throws Exception {
  return userService.selectAll();
    }
}

接着,编写一个 UserService 来处理数据库的查询逻辑。

package com.example.demo.service;

import com.example.demo.entity.User;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.cache.CacheKey;
import org.mybatis.cache.defaults.DefaultCache;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Service
public class UserService {

    @Resource
    private SqlSessionFactory sessionFactory;

    public List<User> selectAll() throws Exception {
  // 获取 SqlSession 对象
  SqlSession session = sessionFactory.openSession();
   // 根据 ID 查询用户记录
  List<User> users = session.selectList("com.example.demo.mapper.UserMapper.selectAll");
   // 关闭 SqlSession 对象
  session.close();
   return users;
    }
}

最后,编写 UserMapper.xml 文件,用来映射 SQL 语句。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.demo.mapper.UserMapper">

    <select id="selectAll" resultType="com.example.demo.entity.User">
  SELECT id, name, age FROM user ORDER BY id DESC
    </select>

</mapper>

以上配置表示,启动 Spring Boot 时,自动扫描 @Service 注解的 Bean,并根据注解中指定的名称,将 Bean 注册到 Spring 容器中。并且,Spring Boot 将自动加载 MyBatis 配置文件。

6.3 加入 MyBatis Cache

前面配置了一个简单的查询接口,现在增加缓存支持,需要做如下几步:

6.3.1 启用 MyBatis Cache

打开 application.yml 文件,启用 MyBatis Cache 功能。

mybatis:
  configuration:
    cache-enabled: true
6.3.2 配置缓存

配置 MyBatis 的缓存插件,并注册缓存实例到 MyBatis 中。

package com.example.demo.config;

import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.MappedTransactionFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.apache.ibatis.type.TypeAliasRegistry;
import org.mybatis.cache.CacheManager;
import org.mybatis.cache.CachingExecutor;
import org.mybatis.cache.impl.PerpetualCacheAdapter;
import org.mybatis.logging.LogImpl;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

@Configuration
@MapperScan(basePackages = {"com.example.demo.mapper"})
public class DemoApplicationConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean() throws Exception {
  SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  sqlSessionFactoryBean.setDataSource(dataSource());
  sqlSessionFactoryBean.setTypeAliasesPackage("com.example.demo.entity");
  ClassPathResource[] mapperLocations = new ClassPathResource[]{new ClassPathResource("mybatis/UserMapper.xml")};
  sqlSessionFactoryBean.setMapperLocations(mapperLocations);
   Properties properties = new Properties();
  properties.load(Resources.getResourceAsStream("mybatis/mybatis-config.xml"));
  Environment environment = new Environment("development", new JdbcTransactionFactory(), dataSource());
  configuration(environment, properties);
  sqlSessionFactoryBean.setConfiguration(configuration(environment, properties));
   return sqlSessionFactoryBean;
    }

    private Configuration configuration(Environment environment, Properties properties) {
  Configuration configuration = new Configuration(environment);
  configuration.setDefaultFetchSize(Integer.parseInt(properties.getProperty("mybatis.default-fetch-size")));
  configuration.setLazyLoadingEnabled(Boolean.parseBoolean(properties.getProperty("mybatis.lazy-loading")));
  configuration.setUseColumnLabel(Boolean.parseBoolean(properties.getProperty("mybatis.use-column-label")));
  configuration.setCacheEnabled(Boolean.parseBoolean(properties.getProperty("mybatis.cache-enabled")));
  configuration.setLocalCacheScope(properties.getProperty("mybatis.cache.local.scope"));
  configuration.setImplementation(new PerpetualCacheAdapter(cache()));
  return configuration;
    }

    private Map<String, Cache> caches() {
  HashMap<String, Cache> map = new HashMap<>();
  map.put("local-cache", localCache());
  return map;
    }

    private Cache localCache() {
  return CachingExecutor.getLocalCache();
    }

    private Cache cache() {
  Caffeine<Object, Object> caffeine = Caffeine.<Object, Object>newBuilder()
         .maximumSize(Long.valueOf(10))
         .build();
  return new PerpetualCache("default-cache", caffeine);
    }
}

此处配置了一个名为 local-cache 的本地缓存,最大缓存条目数为 10。

6.3.3 增加 @CacheNamespace 和 @CacheNamespaceRef

按照上面文档所说的配置 @CacheNamespace@CacheNamespaceRef,这样 MyBatis 将把所有方法共享同一个缓存空间。

package com.example.demo.entity;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.decorators.LoggingCache;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.cache.jcache.JCacheCache;
import org.apache.ibatis.cache.jcache.JCacheRegionFactory;
import org.apache.ibatis.cache.redis.RedisCache;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.type.TypeAliasRegistry;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.hibernate.validator.internal.engine.ValidatorFactoryImpl;

import javax.cache.CacheManager;
import javax.cache.Caching;
import javax.cache.spi.CachingProvider;
import java.lang.reflect.Proxy;
import java.time.Duration;
import java.util.Locale;
import java.util.Properties;

@SuppressWarnings({"unchecked","rawtypes"})
public class UserEntity implements ValidatorFactoryImpl.Holder{
private JCacheRegionFactory regionFactory;
    private boolean useClassCache = false;

    private boolean useMinimalPuts = Boolean.FALSE;
    private int batchSize = Integer.MIN_VALUE;
    private long expirationMillis = Long.MAX_VALUE;
    private String jcacheName = "default";
    private boolean readThrough = false;
    private boolean writeThrough = false;
    private Locale locale = Locale.getDefault();

    private LoggingCache loggingCache;
    private Cache cache;
    private TypeAliasRegistry typeAliasRegistry;
    private TypeHandlerRegistry typeHandlerRegistry;

    public UserEntity(){
  initJCacheRegionFactory();
  MetaClass metaClass = SystemMetaObject.forObject(this).getMetaClass();
  Proxy proxy = (Proxy)metaClass.getConstructor(new Class[]{JCacheRegionFactory.class}).newInstance(regionFactory);
  cache = (Cache)proxy;
  loggingCache = new LoggingCache(cache);
    }

    private void initJCacheRegionFactory() {
  Properties p = new Properties();
  p.setProperty("org.apache.commons.logging.Log", LogImpl.class.getName());
   CachingProvider provider = Caching.getCachingProvider();
  CacheManager manager = provider.getCacheManager();
  regionFactory = new JCacheRegionFactory(manager, p);
    }

    public Cache getCache() {
  return cache;
    }

    public void setCache(Cache cache) {
  this.cache = cache;
    }

    public boolean isUseClassCache() {
  return useClassCache;
    }

    public void setUseClassCache(boolean useClassCache) {
  this.useClassCache = useClassCache;
    }

    public boolean isUseMinimalPuts() {
  return useMinimalPuts;
    }

    public void setUseMinimalPuts(boolean useMinimalPuts) {
  this.useMinimalPuts = useMinimalPuts;
    }

    public int getBatchSize() {
  return batchSize;
    }

    public void setBatchSize(int batchSize) {
  this.batchSize = batchSize;
    }

    public long getExpirationMillis() {
  return expirationMillis;
    }

    public void setExpirationMillis(long expirationMillis) {
  this.expirationMillis = expirationMillis;
    }

    public String getJcacheName() {
  return jcacheName;
    }

    public void setJcacheName(String jcacheName) {
  this.jcacheName = jcacheName;
    }

    public boolean isReadThrough() {
  return readThrough;
    }

    public void setReadThrough(boolean readThrough) {
  this.readThrough = readThrough;
    }

    public boolean isWriteThrough() {
  return writeThrough;
    }

    public void setWriteThrough(boolean writeThrough) {
  this.writeThrough = writeThrough;
    }

    public Locale getLocale() {
  return locale;
    }

    public void setLocale(Locale locale) {
  this.locale = locale;
    }

    protected void finalize() throws Throwable {
  regionFactory.destroy();
    }
}
6.3.4 测试

启动 Spring Boot 项目,测试缓存效果。

package com.example.demo.controller;

import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

    @Autowired
    private SqlSessionFactory sessionFactory;

    @Autowired
    private UserService userService;

    @GetMapping("/users")
    public List<User> getUsers() throws Exception {
  List<User> users = userService.selectAll();
  users.stream().forEach(u -> u.setName("name_" + Math.random()));
  return users;
    }
}

第一次调用接口/users,会先查询数据库,并放入缓存中,再返回给客户端。第二次调用接口/users,会直接从缓存中取出数据,无需再查询数据库。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

禅与计算机程序设计艺术

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

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

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

打赏作者

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

抵扣说明:

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

余额充值