MyBatis二级缓存解密:深入探究缓存机制与应用场景

本文涉及的产品
全局流量管理 GTM,标准版 1个月
日志服务 SLS,月写入数据量 50GB 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: MyBatis二级缓存解密:深入探究缓存机制与应用场景

在上文《探秘MyBatis缓存原理:Cache接口与实现类源码分析》中,我们已经介绍了 MyBatis 的 Cache 接口以及对应的实现类。其中的 PerpetualCache 是 MyBatis 缓存的最基础的实现类,底层通过 HashMap 存储数据,其他的实现类都属于装饰器,基于 PerpetualCache 的各个方面进行增强,各个实现类的理论和实现我们学习过后,本文我们就来探究一下,MyBatis 真正的缓存机制是怎么样的!

MyBatis 缓存机制

MyBatis 为提高其数据库查询性能,提供了两层缓存机制(只针对查询做缓存),包括一级缓存和二级缓存。

  1. ⼀级缓存:将查询到的数据存储到 SqlSession 中,所以只对本 SqlSession 有效。范围比较小,只对于一次 SQL 会话。
  2. ⼆级缓存:将查询到的数据存储到 SqlSessionFactory 中。范围比较大,针对于整个数据库级别。

MyBatis 也可以集成其它第三方缓存:比如基于 Java 开发的 EhCache、基于 C 语言开发的 Memcache等。

二级缓存

二级缓存也叫全局缓存。数据存放在 SqlSessionFactory 的 Configuration 中,只要是同一个工厂中对象创建的 SqlSession,在进行查询时都能共享数据。一般在项目中只有一个 SqlSessionFactory 对象,所以二级缓存的数据是全项目共享的,它可以在多个会话之间共享缓存数据,有效减少数据库访问次数,提高系统性能和响应速度。

默认情况下,MyBatis 在解析 SQL XML 文件的时候,会为每个 XML 文件创建一个二级缓存,这个 XML 中的多个查询结果都共用这同一个 Cache,当然也可以通过  标签来配置多个 XML 复用同一个 Cache,通常会在多表查询的时候会指定同一个 Cache,这是为了避免脏数据。

因为在对数据库进行更新的时候会清理当前 MappedStatement 引用的 Cache,但是如果别的 XML 文件中的 MappedStatement 的查询也使用到了这个表,那么别的 XML 的 Cache 中就会出现脏数据了。

但是如果都指定同一个 Cache,那么在更新的时候会频繁的清理当前的 Cache,而且是全部清理,这样显然不是很好,所以我们有必要更改 MyBatis 底层的缓存清除策略。

  1. 作用范围:跨会话缓存,二级缓存是跨多个 SqlSession 的缓存机制,可以在不同的会话之间共享缓存数据。
  2. 生命周期:应用级别缓存,二级缓存的生命周期与应用程序的生命周期相同,可以在整个应用程序中共享数据。
  3. 默认开启:二级缓存可以通过 MyBatis 配置文件中的控制,默认就是 true,可以理解为默认开启,但是还是需要对应的 Mapper XML 配置标签后才能生效。
  4. 缓存策略:二级缓存的配置通常配置在映射文件(Mapper XML 文件)的  标签内部。配置 元素,可用于配置二级缓存的属性,如缓存类型、缓存的大小、刷新间隔等。在映射文件添加  标签,该映射文件下的所有方法都支持二级缓存。该标签有 size 属性,可以设置缓存中的对象数量,默认是 1024 个。
  5. 缓存命中:当执行查询时,MyBatis 会先检查二级缓存中是否存在相应的结果。如果存在,则直接从缓存中返回结果,而不需要访问数据库。
  6. 缓存失效:对于更新、插入或删除操作,会导致相应的缓存失效,需要重新查询更新后的结果,同一个 XML 中的 SQL 是共用同一个缓存的,失效的时候是指这个 XML 中的多个查询 SQL 都会失效,多个 MappedStatement 引用的同一个 Cache。
  7. 数据同步问题:由于二级缓存是跨会话的,可能会出现数据不一致的情况。在更新或删除数据时,需要及时清除相应的缓存,以保持数据的一致性。

二级缓存开启后,同一个 namespace 下的所有操作语句,都影响着同一个 Cache,即二级缓存被多个 SqlSession 共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是:二级缓存 -> 一级缓存 -> 数据库。

验证二级缓存

MyBatis 配置文件(可省略)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- 此项配置可以省略,因为默认就是 true -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    <typeAliases>
        <typeAlias type="world.xuewei.mybatis.entity.Account" alias="Account"/>
    </typeAliases>
    <environments default="default">
        <environment id="default">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://**.**.**/learn?useSSL=false&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/AccountMapper.xml"/>
    </mappers>
</configuration>
Mapper 映射文件
<?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">
<!-- 配置 namespace -->
<mapper namespace="world.xuewei.mybatis.dao.AccountDao">
    <!-- 需要明确指定此标签,表示开启二级缓存 -->
    <cache/>
    
    <!-- useCache="true" 可以省略 -->
    <select id="getAll" resultType="Account" useCache="true">
        select * from account
    </select>
</mapper>

在 MyBatis 的映射 XML 中配置 cache 或者 cache-ref 都可以。

  1. 标签用于声明这个 namespace 使用二级缓存,并且可以自定义配置。
  • type:cache 使用的类型,默认是PerpetualCache,这在一级缓存中提到过。
  • eviction:定义回收的策略,常见的有 FIFO,LRU。
  • flushInterval:配置一定时间自动刷新缓存,单位是毫秒。
  • size:最多缓存对象的个数,默认 1024 个。
  • readOnly:是否只读,若配置可读写,则需要对应的实体类能够序列化。
  • blocking:若缓存中找不到对应的 key,是否会一直 blocking,直到有对应的数据进入缓存。
  1. 代表引用别的命名空间的 Cache 配置,两个命名空间的操作使用的是同一个 Cache。
    例如:
测试实体(Serializable)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account implements Serializable {
    private Integer id;
    private String name;
    private String password;
}

数据库实体必须要实现 Serializable 接口,否则会抛出异常:Error committing transaction. Cause: org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException: world.xuewei.mybatis.entity.Account

测试类
public class Cache2Test {
    private SqlSession sqlSession1;
    private SqlSession sqlSession2;
    @Before
    public void before() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession1 = sessionFactory.openSession();
        sqlSession2 = sessionFactory.openSession();
    }
    /**
     * 验证二级缓存,多个 SqlSession 有效
     */
    @Test
    public void testGetAll() {
        AccountDao accountDao1 = sqlSession1.getMapper(AccountDao.class);
        AccountDao accountDao2 = sqlSession2.getMapper(AccountDao.class);
        accountDao1.getAll().forEach(System.out::println);
        // 处理这里很关键,SqlSession 提交或关闭的时候才会将数据保存到二级缓存
        sqlSession1.commit();
        System.out.println("======================================");
        accountDao2.getAll().forEach(System.out::println);
        sqlSession2.commit();
    }
}

通过观察日志的打印情况,可以看出,在第二次执行相同的查询方法时,就已经是从缓存中直接拿到的数据了,并且打印出了缓存的命中率 0.5。

清理二级缓存

执行 update、insert 或 delete 操作
@Test
public void testGetAll() {
    AccountDao accountDao1 = sqlSession1.getMapper(AccountDao.class);
    AccountDao accountDao2 = sqlSession2.getMapper(AccountDao.class);
    accountDao1.getAll().forEach(System.out::println);
    accountDao1.delete(1);
    sqlSession1.commit();
    System.out.println("======================================");
    accountDao2.getAll().forEach(System.out::println);
    sqlSession2.commit();
}
配置 flushCache=“true”
<select id="getAll" resultType="Account" flushCache="true">
    select * from account
</select>

关闭二级缓存

<settings>
    <!-- 默认是 true -->
    <setting name="cacheEnabled" value="false"/>
</settings>

实现原理

CachingExecutor 创建时机

开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询,具体的工作流程如下所示:

这是一个典型的装饰器模式,通过 CachingExecutor 为 Executor 增强缓存的功能。在源码中如何提现的呢?首先根据我们原有的知识,Configuration 类是 MyBatis 的第一大核心类,这个类有一个职能就是可以创建其他的核心对象,包括 Executor:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
 executorType = executorType == null ? defaultExecutorType : executorType;
 executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
 Executor executor;
 // 创建一个原始的 Executor,类型可能为 BatchExecutor、ReuseExecutor、SimpleExecutor,默认为 SimpleExecutor。
 if (ExecutorType.BATCH == executorType) {
     executor = new BatchExecutor(this, transaction);
 } else if (ExecutorType.REUSE == executorType) {
     executor = new ReuseExecutor(this, transaction);
 } else {
     executor = new SimpleExecutor(this, transaction);
 }
 // cacheEnabled 默认就是 true
 if (cacheEnabled) {
     // 通过 CachingExecutor 来装饰原始的 Executor
     executor = new CachingExecutor(executor);
 }
 executor = (Executor) interceptorChain.pluginAll(executor);
 return executor;
}
二级缓存实现流程

SqlSession 将 SQL 语句的处理和执行交给 Executor 去处理,如果是查询语句,那么肯定会交给 Executor#query 方法处理,而这里的 Executor 就是刚刚得到的使用 CachingExecutor 装饰后的 Executor。所以我们可以将关注点放在 CachingExecutor#query 方法,以下是 CachingExecutor 的源码:

public class CachingExecutor implements Executor {
  
    // 非常典型的装饰器模式
    private final Executor delegate;
    // 存储缓存的工具,稍后我们会仔细分析
    private final TransactionalCacheManager tcm = new TransactionalCacheManager();
    public CachingExecutor(Executor delegate) {
        this.delegate = delegate;
        delegate.setExecutorWrapper(this);
    }
    @Override
    public int update(MappedStatement ms, Object parameterObject) throws SQLException {
        // 插入、更新、删除操作会清空当前 MappedStatement 的缓存
        flushCacheIfRequired(ms);
        return delegate.update(ms, parameterObject);
    }
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        // 创建缓存的 Key,其实就是调用 BaseExecutor 的方法,和一级缓存的 Key 一样。
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
    @Override
    public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
        // 这种查询方式也会清空 MappedStatement 缓存
        flushCacheIfRequired(ms);
        return delegate.queryCursor(ms, parameter, rowBounds);
    }
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
        throws SQLException {
        Cache cache = ms.getCache();
        // 判断 Mapper XML 中是否配置了 <cache/> 标签
        if (cache != null) {
            // 判断是否需要刷新缓存
            flushCacheIfRequired(ms);
            // 此查询 SQL 是否配置了 useCache="true" 属性
            if (ms.isUseCache() && resultHandler == null) {
                // 用来处理存储过程的,暂时不用考虑
                ensureNoOutParams(ms, boundSql);
                @SuppressWarnings("unchecked")
                // 从二级缓存中拿,会把获取值的职责一路传递,最终到 PerpetualCache。如果没有查到,会把 key 加入 Miss 集合,这个主要是为了统计命中率。
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) {
                    // 没拿到就查询数据库
                    list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    // 然后存放在二级缓存,等待提交(注意此时还没有保存到二级缓存,还需要一个提交的操作)
                    tcm.putObject(cache, key, list); // issue #578 and #116
                }
                return list;
            }
        }
        // 没有配置 <cache/> 则直接调用原始 Executor 查询数据库
        return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
    @Override
    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
    }
    private void flushCacheIfRequired(MappedStatement ms) {
        // 判断 Mapper XML 中是否配置了 <cache/> 标签
        Cache cache = ms.getCache();
        // 如果配置了 <cache/> 并且此查询语句配置了 flushCache="true" 就会清空缓存
        if (cache != null && ms.isFlushCacheRequired()) {      
            // 清空缓存
            tcm.clear(cache);
        }
    }
  // 省略其他方法...
}

在上面的 query 方法中的 Cache cache = ms.getCache();是获取当前 MappedStatement 配置的缓存对象。

本质上是装饰器模式的使用(无限套娃),具体的装饰链是:

SynchronizedCache -> LoggingCache -> SerializedCache -> LruCache -> PerpetualCache。

以下是具体这些 Cache 实现类的介绍,他们的组合为 Cache 赋予了不同的能力。

  • SynchronizedCache:同步 Cache,实现比较简单,直接使用 synchronized 修饰方法。
  • LoggingCache:日志功能,装饰类,用于记录缓存的命中率,如果开启了 DEBUG 模式,则会输出命中率日志。
  • SerializedCache:序列化功能,将值序列化后存到缓存中。该功能用于缓存返回一份实例的 Copy,用于保存线程安全。
  • LruCache:采用了 Lru 算法的 Cache 实现,移除最近最少使用的 Key/Value。
  • PerpetualCache:作为为最基础的缓存类,底层实现比较简单,直接使用了 HashMap。

在上面的 query 方法中,有这样的逻辑:

if (ms.isUseCache() && resultHandler == null) {
  // ...
    List<E> list = (List<E>) tcm.getObject(cache, key);
    if (list == null) {
        list = delegate.<E> query(...);
        tcm.putObject(cache, key, list); // issue #578 and #116
    }
    return list;
}

表达的含义是先调用 tcm 的 getObject 查询缓存,如果能查询到数据直接返回,否则调用 delegate 的查询,并将结果保存在 tcm。那么这个 tcm 可以说就是用来存储数据的。找到 tcm 的定义如下:

private final TransactionalCacheManager tcm = new TransactionalCacheManager();

查询源码后可以看到:TransactionalCacheManager 维护了一个 transactionalCaches 这个 HashMap,键就是每个 MappedStatement 的 Cache,值就是 TransactionalCache。这个 TransactionalCache 也是 PerpetualCache 的装饰器,提供了事务性缓存的功能。事务性缓存确保缓存中的数据与数据库中的数据保持一致,只有在事务成功提交后,缓存中的数据才会被更新或者删除。

关于 TransactionalCache 的介绍可以查询这篇文章:《探秘MyBatis缓存原理:Cache接口与实现类源码分析

public class TransactionalCacheManager {
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }
  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }
  
  public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }
  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }
  public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }
  private TransactionalCache getTransactionalCache(Cache cache) {
    TransactionalCache txCache = transactionalCaches.get(cache);
    if (txCache == null) {
      // 注意这里!当第一次添加数据的时候,会用 TransactionalCache 再次装饰一下 MappedStatement 的 Cache 当做 Map 的 value
      // 这也就是 TransactionalCache 是怎么和 MappedStatement 的 Cache 产生联系的原因
      txCache = new TransactionalCache(cache);
      transactionalCaches.put(cache, txCache);
    }
    return txCache;
  }
}
二级缓存创建过程

刚刚我们获取到了一个套娃的 PerpetualCache,那么这个 PerpetualCache 是什么时候创建的呢?这个就要联系到我们之前学到的知识《MyBatis初探:揭示初始化阶段的核心流程与内部机制》。

在构建 MappedStatement 的时候是由 XMLMapperBuilder 负责处理,在其核心的配置解析的方法(XMLMapperBuilder#configurationElement)中就由一步是去解析和标签的。

稍微追踪一下源码即可发现,缓存的创建是由 MapperBuilderAssistant 类完成,这是一个典型的构建者模式:

public Cache useNewCache(Class<? extends Cache> typeClass,
                         Class<? extends Cache> evictionClass,
                         Long flushInterval,
                         Integer size,
                         boolean readWrite,
                         boolean blocking,
                         Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        // 默认底层是 PerpetualCache
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        // 默认换出策略是 LruCache
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        // 刷新时间
        .clearInterval(flushInterval)
        // 存储数据大小
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
}

这些属性其实就是对应的标签的属性。

所以说,缓存存储对象 cache 是在 MyBatis 初始化 MappedStatement 的时候就创建好了,创建好后存储在 MappedStatement 对象,由 XMLMapperBuilder 负责解析,MapperBuilderAssistant 负责创建。

接下来我们仔细看一下 CacheBuilder 的 build 方法是如何构建出这个 Cache 对象的:

public Cache build() {
    // 设置默认的缓存实现,如果 <cache> 标签没有指定 type,那么就用 PerpetualCache + LruCache
    setDefaultImplementations();
    // 通过反射来创建缓存对象
    Cache cache = newBaseCacheInstance(implementation, id);
    // 读取 <cache> 下的 <property> 增加额外的参数(内置缓存不用,通常用于搭配自定义缓存,例如:Redis、Ehcache 等)
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    if (PerpetualCache.class.equals(cache.getClass())) {
        for (Class<? extends Cache> decorator : decorators) {
            // 添加装饰器,可以通过 <cache> 标签的 eviction 属性指定
            cache = newCacheDecoratorInstance(decorator, cache);
            setCacheProperties(cache);
        }
        // 根据 <cache> 标签配置的属性,设置装饰器
        cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
        cache = new LoggingCache(cache);
    }
    return cache;
}
private void setDefaultImplementations() {
    if (implementation == null) {
        // 默认就是 PerpetualCache + LruCache
        implementation = PerpetualCache.class;
        if (decorators.isEmpty()) {
            decorators.add(LruCache.class);
        }
    }
}
private Cache setStandardDecorators(Cache cache) {
    try {
        MetaObject metaCache = SystemMetaObject.forObject(cache);
        if (size != null && metaCache.hasSetter("size")) {
            // 设置缓存大小,其实就是赋值给 PerpetualCache 的 size 属性,默认 1024
            metaCache.setValue("size", size);
        }
        if (clearInterval != null) {
            // 支持定时清理的缓存
            cache = new ScheduledCache(cache);
            ((ScheduledCache) cache).setClearInterval(clearInterval);
        }
        if (readWrite) {
            // 自动序列化的缓存
            cache = new SerializedCache(cache);
        }
        // 添加日志功能的缓存
        cache = new LoggingCache(cache);
        // 同步的缓存
        cache = new SynchronizedCache(cache);
        if (blocking) {
            // 阻塞式的缓存
            cache = new BlockingCache(cache);
        }
        return cache;
    } catch (Exception e) {
        throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
}
MyBatis 缓存的查询顺序

先说结论:查询的时候先查询二级缓存,查不到再去查询一级缓存,再查不到则会查询数据库。

再默认的情况下(开启二级缓存),我们得到的 Executor 是 CachingExecutor,而这个类就是二级缓存的负责类。当用户执行查询操作的时候,会先由 CachingExecutor 的 query 处理,处理过程就是先查缓存,然后没查到的话调用委托的 Executor delegate 来处理查询,而这个 delegate 就是 SimpleExecutor,一级缓存的负责类就是这个 SimpleExecutor 的父类,即 BaseExecutor,所以结合一级缓存和二级缓存的实现原理可以得出上面这个结论。

相关文章
|
1月前
|
存储 缓存 芯片
让星星⭐月亮告诉你,当我们在说CPU一级缓存二级缓存三级缓存的时候,我们到底在说什么?
本文介绍了CPU缓存的基本概念和作用,以及不同级别的缓存(L1、L2、L3)的特点和工作原理。CPU缓存是CPU内部的存储器,用于存储RAM中的数据和指令副本,以提高数据访问速度,减少CPU与RAM之间的速度差异。L1缓存位于处理器内部,速度最快;L2缓存容量更大,但速度稍慢;L3缓存容量最大,由所有CPU内核共享。文章还对比了DRAM和SRAM两种内存类型,解释了它们在计算机系统中的应用。
74 1
|
2月前
|
缓存 Java 数据库连接
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
文章介绍了MyBatis的缓存机制,包括一级缓存和二级缓存的配置和使用,以及如何整合第三方缓存EHCache。详细解释了一级缓存的生命周期、二级缓存的开启条件和配置属性,以及如何通过ehcache.xml配置文件和logback.xml日志配置文件来实现EHCache的整合。
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
|
3天前
|
SQL Java 数据库连接
Mybatis架构原理和机制,图文详解版,超详细!
MyBatis 是 Java 生态中非常著名的一款 ORM 框架,在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架。本文详细解析了MyBatis的架构原理与机制,帮助读者全面提升对MyBatis的理解和应用能力。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Mybatis架构原理和机制,图文详解版,超详细!
|
12天前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
12天前
|
SQL 缓存 Java
MyBatis如何关闭一级缓存(分注解和xml两种方式)
MyBatis如何关闭一级缓存(分注解和xml两种方式)
41 5
|
28天前
|
缓存 Java 数据库连接
使用MyBatis缓存的简单案例
MyBatis 是一种流行的持久层框架,支持自定义 SQL 执行、映射及复杂查询。本文介绍了如何在 Spring Boot 项目中集成 MyBatis 并实现一级和二级缓存,以提高查询性能,减少数据库访问。通过具体的电商系统案例,详细讲解了项目搭建、缓存配置、实体类创建、Mapper 编写、Service 层实现及缓存测试等步骤。
|
1月前
|
存储 缓存 负载均衡
Nginx代理缓存机制
【10月更文挑战第2天】
63 4
|
1月前
|
存储 缓存 NoSQL
深入理解后端缓存机制的重要性与实践
本文将探讨在后端开发中缓存机制的应用及其重要性。缓存,作为提高系统性能和用户体验的关键技术,对于后端开发来说至关重要。通过减少数据库访问次数和缩短响应时间,缓存可以显著提升应用程序的性能。本文将从缓存的基本概念入手,介绍常见的缓存策略和实现方式,并通过实例展示如何在后端开发中有效应用缓存技术。最后,我们将讨论缓存带来的一些挑战及其解决方案,帮助您在实际项目中更好地利用缓存机制。
|
2月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
66 8
|
2月前
|
缓存 Java Python
python垃圾回收&缓存机制
python垃圾回收&缓存机制