Java Cache 入门

何为内存缓存?java怎么去实现它? 内存缓存(Memory caching)是一种常见的缓存技术,它利用计算机的内存存储临时数据,以提高数据的读取和访问速度。内存缓存通常用于存储频繁访问的数据,以减少对慢速存储介质(如磁盘或数据库)的访问次数,从而加快系统的响应速度和性能。 阅读详情

什么是缓存

术语缓存在计算机中无处不在。在应用程序设计的上下文中,它经常被用来描述应用程序开发人员利用单独的内存或低延迟的数据结构。缓存,用于临时存储或缓存信息的副本或引用,应用程序可能会在稍后的某个时间点重复使用,从而减轻重新访问或重新创建它的成本。

在 JCache 的上下文中,术语缓存描述了 Java 的技术开发人员使用缓存供应商临时缓存 Java 对象。

什么时候引入缓存

引入缓存会提高系统复杂度,因为你要考虑缓存的失效、更新、一致性等问题。

冒着上述种种风险,仍能说服你引入缓存的理由,总结起来无外乎以下两种:

  • 为缓解 CPU 压力而做缓存:譬如把方法运行结果存储起来、把原本要实时计算的内容提前算好、把一些公用的数据进行复用,这可以节省 CPU 算力,顺带提升响应性能。
  • 为缓解 I/O 压力而做缓存:譬如把原本对网络、磁盘等较慢介质的读写访问变为对内存等较快介质的访问,将原本对单点部件(如数据库)的读写访问变为到可扩缩部件(如缓存中间件)的访问,顺带提升响应性能。

请注意,缓存虽然是典型以空间换时间来提升性能的手段,但它的出发点是缓解 CPU 和 I/O 资源在峰值流量下的压力,“顺带”而非“专门”地提升响应性能。这里的言外之意是如果可以通过增强 CPU、I/O 本身的性能(譬如扩展服务器的数量)来满足需要的话,那升级硬件往往是更好的解决方案,即使需要一些额外的投入成本,也通常要优于引入缓存后可能带来的风险。

缓存设计模式

Cache Aside

其中最简单、成本最低的 Cache Aside 模式是指:

  • 读数据时,先读缓存,缓存没有的话,再读数据源,然后将数据放入缓存,再响应请求。
  • 写数据时,先写数据源,然后失效(而不是更新)掉缓存。

读数据方面一般没什么出错的余地,但是写数据时,就有必要专门强调两点:一是先后顺序是先数据源后缓存。试想一下,如果采用先失效缓存后写数据源的顺序,那一定存在一段时间缓存已经删除完毕,但数据源还未修改完成,此时新的查询请求到来,缓存未能命中,就会直接流到真实数据源中。这样请求读到的数据依然是旧数据,随后又重新回填到缓存中。当数据源的修改完成后,结果就成了数据在数据源中是新的,在缓存中是老的,两者就会有不一致的情况。另一点是应当失效缓存,而不是去尝试更新缓存,这很容易理解,如果去更新缓存,更新过程中数据源又被其他请求再次修改的话,缓存又要面临处理多次赋值的复杂时序问题。所以直接失效缓存,等下次用到该数据时自动回填,期间无论数据源中的值被改了多少次都不会造成任何影响。

Cache Aside 模式依然是不能保证在一致性上绝对不出问题的,否则就无须设计出 Paxos 这样复杂的共识算法了。典型的出错场景是如果某个数据是从未被缓存过,请求会直接流到真实数据源中,如果数据源中的写操作发生在查询请求之后,结果回填到缓存之前,也会出现缓存中回填的内容与数据库的实际数据不一致的情况。但这种情况的概率是很低的,Cache Aside 模式仍然是以低成本更新缓存,并且获得相对可靠结果的解决方案。

Read/Write Through

Write Through 将数据同时写入高速缓存和相应的主内存位置。缓存的数据允许按需快速检索,而主存储器中的相同数据可确保在发生崩溃,电源故障或其他系统中断时不会丢失任何内容。

尽管直写可以最大程度地减少数据丢失的风险,但是每个写操作必须执行两次,并且这种冗余需要时间。活动的应用程序必须等待,直到将每个数据块都写入主内存和高速缓存中,然后才能开始下一个操作。因此,“数据保险”是以牺牲系统速度为代价的。

直写是无法容忍数据丢失的应用程序(例如银行和医疗设备控制)中首选的数据存储方法。

Write Behind Caching

回写在每次发生更改时,数据都会先写入高速缓存,但仅在指定的时间间隔或特定条件下,数据才会写入主存储中的相应位置。

JSR107 (JCache)

JCache 是 Java 的缓存 API。它由 JSR107 定义。它定义了供开发人员使用的标准 Java 缓存 API 和供实现者使用的标准 SPI(“服务提供者接口”)。

标准定义文档

JCache 核心概念

Java 的缓存 API 定义了五个核心接口:CachingProvider,CacheManager,Cache,Entry 和 ExpiryPolicy。

CachingProvider

定义了建立,配置,获取,管理和控制零个或多个 CacheManager 的机制。应用程序可以在运行期间访问和使用零个或多个 CacheProvider。

CacheManager
定义在上下文中了建立,配置,获取,管理和控制零个或多个唯一命名的缓存的机制。 CacheManager 被单个 CachingProvider 拥有。

Cache
是一个像 Map 一样的数据结构,它允许基于 Key 的临时储存。缓存被单个 CacheManager 拥有。

Entry
是被 Cache 存储的单个 key-value 对,JCache 允许我们定义按值或按引用来存储条目。

ExpiryPolicy

每一个被 Cache 存储的 entry 都定义了存活时间,被称作过期持续时间。**缓存过期时间是可以动态设置的,在执行某些缓存操作后,缓存条目将在设置的时间后过期。**一旦这个过期时间到达,该条目就被认为是过期了。一旦过期,就会从缓存中驱逐出去,不能再访问,更新和删除条目。

  • getExpiryForCreation() - 创建条目时的持续时间
  • getExpiryForAccess() - 条目被访问时的新持续时间
  • getExpiryForUpdate() - 条目被更新时的新持续时间

另外,getExpiryForUpdate() 和 getExpiryForAccess() 也可能返回 null,这表示缓存实现应保留创建时这些操作的条目的有效期限不变。

按值存储和按引用存储

条目(Entry)由每个缓存使用两种机制之一存储。

默认的机制,称为值存储。

指示实现使副本应用程序在将它们储存在 Cache 中之前提供键和值,之后在访问缓存时再返回新的条目副本。复制条目的目的是存储在缓存和从缓存中再次返回时应用程序继续变异键和值的状态,不会对缓存持有的条目造成副作用。

一个简单的方法实现可用于制作键和值的副本是 Java 序列化。

替代和可选机制,称为引用存储。

指示 Cache 实现简单地存储和返回对应用程序提供的键和值的引用,而不是按照按值存储方法的要求制作副本。如果应用程序稍后使用引用存储语义来改变提供给缓存的键或值,则突变的副作用对于从缓存访问条目的人来说是可见的,而应用程序不必更新缓存。

对于在 Java 堆上实现的缓存,引用存储是最快的存储技术。

其它

除了定义核心接口, JCache 还为我们定义了:

  • 缓存无效时的读写集成定义

    JCache 定义基本的配置,具体服务提供商可以提供相应的实现,以便:

    配置 isReadThrough 为 true 时,当请求的值不在高速缓存中时,配置的 cacheLoaderFactory 提供从 CacheLoader 读取值的功能(通过高速缓存读取)
    配置 isWriteThrough 为 true 时,当从缓存中创建/更新/删除值时,配置的 cacheWriterFactory 提供将值写入 CacheWriter (通过缓存写入)

  • 缓存条目监听器接口

    我们可以注册条目不同事件的监听器在运行期或配置阶段,通过 CacheEntryListenerConfiguration 注册 CacheEntryListener。

  • 缓存注解,为用户提供与缓存交互的类的方法拦截器处理,类定义在 javax.cache.annotation 包中

    • @CacheDefaults - 类级别注解,定义类中使用的所有缓存相关注解的默认属性值,如缓存名称、缓存处理器、key 生成器
    • @CacheResult - 方法级别注解,标记方法使用参数生成的键缓存自身返回的结果。
    • @CachePut
    • @CacheRemove
    • @CacheRemoveAll
  • 监控管理

    javax.cache.management 包提供了用于缓存管理和统计的 MXBean 接口

    默认情况下,管理和统计信息均处于禁用状态。 要在配置时将其打开,可以在 MutableConfiguration 上使用以下方法:

    • setManagementEnabled(boolean enabled) 启用管理
    • setStatisticsEnabled(boolean enabled) 启用统计

从 JCache 到 EhCache

使用 JCache 可以完成大部分的缓存操作,但是如果需要使用到 EhCache 的特性,则必须使用提供给程序的特定 API。

EhCache 的主要特性如下:

  • **快速,简单。**EhCache 的线程机制是为大型高并发系统设计的,使用 EhCache 也不需要特别复杂的配置,很容易部署上线和运行。
  • **多种缓存策略。**提供 LRU、LFU 和 FIFO 缓存策略。支持基于 Cache 和 Element 的过期策略。
  • **缓存数据有两级。**内存和磁盘,因此无需担心容量问题。EhCache 为大数据存储做过优化。在大内存情况下,所有进程可以支持数百 GB 的吞吐,在单台虚拟机上可以支持多缓存管理器,还可以通过 Terracotta 服务器矩阵伸缩到数百个节点。
  • **缓存数据会在虚拟机重启的过程中写入磁盘。**EhCache 是第一个引入缓存持久化存储到开源 Java 缓存框架,缓存的数据可以在机器重启后从磁盘上重新获得,可以根据需要使用 cache.flush 方法将缓存刷到磁盘上面。
  • 可以通过 RMI、JMS、JGroups、可插入 API 等方式进行分布式缓存。
  • 具有缓存和缓存管理器的侦听接口。
  • 提供 Hibernate、Mysql 等框架的缓存实现。

下图是 EhCache 的架构图:

Image result for ehcache architecture diagram

使用 EhCache 作为 JCache 供应商

JCache 作为标准有很多的实现,我们这里主要以 EhCache 来展开学习,要想使用 EhCache 作为 JCache 的供应商很简单,我们只要将 EhCache 的 jar 包和 JCache 放在一起便可以使用。

通过 JCache 的接口,我们便可以操控缓存:

CachingProvider provider = Caching.getCachingProvider();  
CacheManager cacheManager = provider.getCacheManager();   
MutableConfiguration<Long, String> configuration =
    new MutableConfiguration<Long, String>()  
        .setTypes(Long.class, String.class)   
        .setStoreByValue(false)   
        .setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.ONE_MINUTE));  
Cache<Long, String> cache = cacheManager.createCache("jCache", configuration); 
cache.put(1L, "one"); 
String value = cache.get(1L); 

JCache 会从应用程序的类路径中检索默认的 CachingProvider 实现。但这必须保证类路径上只有一个 JCache 实现,否则必须指定具体的供应商名称,可以使用设置系统变量 javax.cache.spi.CachingProvider 或通过调用 Caching.getCachingProvider(String) 静态方法指定完整的类名称。

上述代码中,我们获取到缓存的供应器,并从中得到一个默认的 CacheManager 实例,使用不可用的配置类设置了键和值的类型,条目的存储类型,以及过期方案来生成一个名称为 JCache 的缓存对象,并向缓存中添加了一组数据,并使用键获取值。

JCache 提供了一组最小配置,非常适合内存缓存。但是 EhCache 在拓扑和功能方面提供了更强大的实现。

从 JCache 配置访问基础 Ehcache 配置

当以使用 JCache 创建了配置时,你任然可以访问底层的 EhCache CacheRuntimeConfiguration。

MutableConfiguration<Long, String> configuration = new MutableConfiguration<>();
configuration.setTypes(Long.class, String.class);
Cache<Long, String> cache = cacheManager.createCache("someCache", configuration); 

CompleteConfiguration<Long, String> completeConfiguration = cache.getConfiguration(CompleteConfiguration.class); 

Eh107Configuration<Long, String> eh107Configuration = cache.getConfiguration(Eh107Configuration.class); 

CacheRuntimeConfiguration<Long, String> runtimeConfiguration = eh107Configuration.unwrap(CacheRuntimeConfiguration.class);

我们还可以使用 Ehcache CacheConfiguration 创建 JCache 缓存。

EhCache 不总是同意 JCache 的默认行为。两者在默认值上存在一定的差异。

使用编程的方式配置 EhCache

我们可以使用编程配置或 XML 来配置 EhCache,个人比较喜欢以编程的形式配置大多数不是很容易变动的配置,通过使用提供流畅 API 的构建器我们可以很容易完成配置。

CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder() 
    .withCache("preConfigured",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10))) 
    .build(); 
cacheManager.init(); 

Cache<Long, String> preConfigured =
    cacheManager.getCache("preConfigured", Long.class, String.class); 

Cache<Long, String> myCache = cacheManager.createCache("myCache", 
    CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10)));

myCache.put(1L, "da one!"); 
String value = myCache.get(1L); 

cacheManager.removeCache("preConfigured"); 

cacheManager.close(); 

这里的配置很清晰明了,具体配置我们可以查看配置参数查看。

EhCache 存储层

我们可以配置 Ehcache 以使用各种数据存储区域。当缓存配置为使用多个存储区域时,这些区域将按层排列和管理。它们按层次结构组织,最低层(更远)称为权限层,其他层则是缓存层的一部分(更近,也称为近缓存)。缓存层本身可以由多个存储区域组成。最热门的数据保存在缓存层中,这通常不如权限级别那么丰富但速度更快。所有数据都保存在权限层中,这更慢但更丰富。

Ehcache 支持的数据存储包括:

堆内存储

利用 Java 的堆内 RAM 内存来存储缓存条目。此层使用与 Java 应用程序相同的堆内存,所有这些都必须由 JVM 垃圾收集器扫描。JVM 使用的堆空间越多,应用程序性能就越会受到垃圾收集暂停的影响。这个存储非常快,但通常是您最有限的存储资源。

堆外存储

大小受限于可用的 RAM 内存。不受 Java 垃圾收集(GC)的限制。速度非常快,但比堆内存储慢,因为数据必须在存储和重新访问时移入和移出 JVM 堆,移入移出对象可能涉及到对象的序列化和反序列化。

磁盘存储

利用磁盘(文件系统)来存储缓存条目。这种类型的存储资源通常非常丰富,但比基于 RAM 的存储要慢得多。对于使用磁盘存储的所有应用程序,建议使用快速专用磁盘来优化吞吐量。

集群存储

此数据存储是远程服务器上的缓存。远程服务器可以可选地提供具有高可用性的故障转移服务器。由于集群存储会因网络延迟以及建立客户端/服务器一致性等因素而导致性能下降,这个层本质上比本地堆外存储慢。

EhcacheTerminology

用户管理的缓存

用户管理的缓存为我们提供了一种直接配置缓存的简单方法,而无需设置或使用 CacheManager 的复杂性。是否使用 UserManagedCache 而不是 CacheManager 的选择通常取决于我们是否需要 CacheManager 的所有内置功能。如果缓存要求相对简单,并且不需要 CacheManager 的全部功能,则可以考虑使用 UserManagedCache。

使用 UserManagedCache 的典型方案有:方法本地缓存,线程本地缓存或缓存生命周期短于应用程序生命周期的任何其他位置。

使用介绍

Hibernate 缓存

数据库抽象层(如ORM(对象 - 关系映射)框架)的一个优点是它们能够透明地缓存从底层存储中检索的数据。这有助于消除频繁访问数据的数据库访问成本。

在运行时,Hibernate 处理将数据移入和移出二级缓存以响应 Session 执行的操作,该操作充当持久数据的事务级缓存。一旦实体被管理,该对象就会被添加到当前持久化上下文(EntityManager 或 Session)的内部缓存中。持久化上下文也称为第一级缓存,默认情况下已启用。

但由于 Hibernate 会话(session)绑定到当前正在运行的逻辑事务,所以一旦事务结束,该会话也将被销毁,会话中持有的上下文也会消失。

适当的缓存解决方案必须跨越多个 Hibernate 会话,这就是 Hibernate 也支持附加的二级缓存的原因。 **二级缓存是绑定到 SessionFactory 域的生命周期,意味着它由使用相同会话工厂创建的所有会话共享,仅在关闭 SessionFactory 时(通常是在应用程序关闭时)才销毁。**二级缓存主要基于实体,也支持可选的查询缓存解决方案。

Hibernate 缓存不了解其他应用程序对持久性存储所做的更改。比如 EhCache 管理的缓存。要解决此限制,可以在二级缓存区域级别配置 TTL(生存时间)保留策略,以便底层缓存条目定期到期。

配置二级缓存

Hibernate 可以与各种缓存供应商集成,以便在特定会话的上下文 (session) 之外缓存数据。要配置启用二级缓存,则需提供 RegionFactory 的实现类。

RegionFactory

RegionFactory 是 Region 的工厂类,Region 不代表缓存中的特定概念,它只是定义一些具有共同特征的基本合同,而与打算在该区域内存储的数据类型无关。更具体的子类型是 DomainDataRegion(存储实体,集合和自然 ID 数据)和 DirectAccessRegion(存储查询结果和时间戳数据)。

org.hibernate.cache.spi.RegionFactory 定义了 Hibernate 与可插拔缓存供应商之间的集成。Hibernate 二级缓存设计为让我们感觉不到所使用的实际缓存,SPI(服务提供者接口)为缓存实现者定义了接口,Hibernate 只需要提供 org.hibernate.cache.spi.RegionFactory 接口的实现,该接口封装了特定于实际缓存提供者的所有细节。基本上,它充当 Hibernate 和缓存提供者之间的桥梁。Hibernate 内置支持 Java 缓存标准 JCache 以及两个流行的缓存库:Ehcache 和 Infinispan。

缓存配置属性

除了供应商特定的配置之外,集成的 Hibernate 端还有许多配置选项可控制各种缓存行为:

hibernate.cache.use_second_level_cache

整体启用或禁用二级缓存。默认情况下,如果当前配置的 RegionFactory 不是 NoCachingRegionFactory,则将启用第二级缓存。否则,将禁用二级缓存。

hibernate.cache.use_query_cache

启用或禁用查询结果的二级缓存。默认值为 false。

hibernate.cache.query_cache_factory

查询结果缓存由特殊的规则处理,该规则定义了数据的实效策略。默认实现是不失效,适合用于对数据要求松散的应用。或者我们可以指定自己的规则。自定义的规则只需要实现 QueryCacheFactory 接口。hibernate 默认内置的是 StandardQueryCacheFactory。

hibernate.cache.auto_evict_collection_cache

当关联仅从拥有方改变时,启用或禁用双向关联的集合缓存条目的自动驱逐。默认情况下禁用该选项,因为它会跟踪关联状态并对性能产生影响。但是,如果您的应用程序不管理高速缓存集合端的双向关联的双方,则替代方法是在该集合缓存中保留陈旧数据。

使用陈旧数据时很可能会出现数据不一致的情况,比如实体存在于缓存中但不存在于数据库中,在做级联删除时,会报 EntityNotFoundException 异常,可以在级联删除的集合上配置 @NotFound(action = NotFoundAction.IGNORE) 忽略该异常或者启用该选项。

hibernate.cache.use_reference_entries

允许将实体引用直接存储到二级缓存中,以用于只读或不可变实体。使用引用存储缓存的好处是我们不用显式的维护缓存,缓存更新时直接反映给调用者。缺点是该缓存只可用于只读状态,否则对象逸出,会很难控制。

hibernate.cache.default_cache_concurrency_strategy

在 hibernate 二级缓存中,可以对所有区域进行不同的配置,包括在访问特定区域时要使用的并发策略。此设置允许定义要使用的默认策略。有效值包括:

  • read-only
  • read-write
  • nonstrict-read-write
  • transactional

hibernate.cache.keys_factory

当将条目 (entries) 作为键值对存储到二级缓存中时,标识符可以包装为元组 <实体类型,租户,标识符> 以确保唯一性,以防第二级缓存将所有实体存储在单个空间中。这些元组然后用作高速缓存中的键。当前,仅当 Infinispan 配置为第二级缓存实现时才支持此属性。有效值为:

  • default(将标识符包装在元组中)
  • simple(使用标识符作为键,无需任何包装)
  • 实现 org.hibernate.cache.spi.CacheKeysFactory 的完全限定的类名

hibernate.cache.use_structured_entries

如果为 true,则强制 Hibernate 以更人性化的格式将数据存储在二级缓存中。如果希望能够直接在缓存中“浏览”数据,则可能会很有用,但确实会对性能产生影响。

配置二级缓存映射

可以通过 JPA 注解或 XML 描述符或使用特定于 Hibernate 的映射文件来配置缓存映射。默认情况下,实体不是二级缓存的一部分,我们可以通过配置 javax.persistence.sharedCache.mode 来映射缓存。该配置有四个选项:

ENABLE_SELECTIVE

默认也是推荐的选项。除非明确标记为可缓存(使用 @Cacheable 注解),否则不会缓存实体。

DISABLE_SELECTIVE

除非明确标记为不可缓存 @Cachable 注解 value 设置为 false,否则实体将被缓存。

ALL

即使标记为不可缓存,实体也始终被缓存。

NONE

即使标记为可缓存,也不会缓存任何实体。此选项可以完全禁用二级缓存。

默认情况下使用的缓存并发策略可以通过以下方式全局设置 hibernate.cache.default_cache_concurrency_strategy 来配置属性,配置选项如下:

read-only

如果您的应用程序需要读取但不修改持久化类的实例,则只读缓存是最佳选择。应用程序仍然可以删除实体,这些更改应该反映在二级缓存中,以便缓存不提供过时的实体。实现可以基于实体的不变性来使用性能优化。

read-write

如果应用程序需要更新数据,则读写缓存可能是合适的。此策略提供对单个实体的一致访问,但不提供可序列化的事务隔离级别;实现可以基于实体的不变性来使用性能优化。

nonstrict-read-write

与读写策略类似,但在并发访问实体时可能偶尔会出现过时读取。如果应用程序很少同时更新相同的数据并且不需要严格的事务隔离,则可以选择此策略。实现可以使用利用宽松一致性保证的性能优化。

transactional

提供可序列化的事务隔离级别。

建议不要使用全局设置,而是基于每个实体、方法或集合字段上定义缓存并发策略。为此,请使用 @ org.hibernate.annotations.Cache 注解。

该注解有三个属性:

usage

定义 CacheConcurrencyStrategy。

region

定义存储条目的缓存区域。对于每个实体类,Hibernate 将使用单独的缓存区域来存储该类的实例状态。区域名称是完全限定的类名。

include

是否(lazy)延迟加载属性应包含在二级缓存中。默认值是所有延迟属性都可缓存。另一个可能的值是非延迟,因此延迟属性不可缓存。

实体继承和二级缓存映射

传统上,当使用实体继承时,Hibernate 要求实体层次结构完全缓存或根本不缓存。因此,如果要缓存属于给定实体层次结构的子类,则 JPA @Cacheable 和特定于 Hibernate 的 @Cache 注解必须仅在根实体级别声明。

Cacheable 注解的值由子类继承;可以通过在子类上指定 Cacheable 来覆盖它。从 Hibernate ORM 5.3 开始,您现在可以在子类级别覆盖基类 @Cacheable 或 @Cache 定义。但是,Hibernate 缓存并发策略(例如,只读,非严格读写,读写,事务)仍然在根实体级别定义,并且不能被覆盖。

管理缓存数据

传统上,Hibernate 定义了 CacheMode 枚举来描述与缓存数据交互的方式。 JPA 通过存储(CacheStoreMode)和检索(CacheRetrieveMode)分割缓存模式。两者的关系如下表:

HibernateJPA描述
CacheMode.NORMALCacheStoreMode.USE 和 CacheRetrieveMode.USE默认。from/into 缓存读取/写入数据
CacheMode.REFRESHCacheStoreMode.REFRESH 和 CacheRetrieveMode.BYPASS不从缓存中读取,而是在从数据库加载时写入缓存
CacheMode.PUTCacheStoreMode.USE 和 CacheRetrieveMode.BYPASS不从缓存中读取,而是在从数据库读取时写入缓存
CacheMode.GETCacheStoreMode.BYPASS 和 CacheRetrieveMode.USE从缓存中读取,但不写入缓存
CacheMode.IGNORECacheStoreMode.BYPASS 和 CacheRetrieveMode.BYPASS不从缓存读取/写入数据

设置缓存模式可以在直接加载实体或执行查询时完成。

  • 使用 JPA 自定义缓存模式:
Map<String, Object> hints = new HashMap<>();
hints.put( "javax.persistence.cache.retrieveMode" , CacheRetrieveMode.USE );
hints.put( "javax.persistence.cache.storeMode" , CacheStoreMode.REFRESH );
Person person = entityManager.find( Person.class, 1L , hints);
  • 使用 Hibernate 自定义缓存模式:
session.setCacheMode( CacheMode.REFRESH );
Person person = session.get( Person.class, 1L );
  • 使用 Spring Data JPA 自定义缓存模式(1.10 版本之后):
@QueryHints(value = {
    @QueryHint(name = "javax.persistence.cache.retrieveMode", value = "USE"),
    @QueryHint(name = "javax.persistence.cache.storeMode", value = "REFRESH")
})
Optional<Person> findById(Long id);

查询缓存,集合缓存,和缓存统计以后再来补充。

Spring Boot 使用 Cache

个人使用 Gradle 管理项目依赖,以下所述依赖皆是以基于Groovy 的特定于域的语言(DSL)定义。

仅使用简单缓存

Spring Boot 使用简单缓存非常简单,我们只需要导入所需要的包即可开箱即用,如果我们仅仅想使用缓存,则直接引入:org.springframework.boot:spring-boot-starter-cache starter 便可使用。

在配置类或启动类上加入 @EnableCaching 注解,该注解会触发一个后处理器(post processor)去检测每个 Spring Bean 上是否存在公共方法的缓存注解。如果找到这样的注解,则自动创建代理以拦截方法调用并相应地处理缓存行为。

此后处理器管理的注解是 Cacheable,CachePut 和 CacheEvict。Spring Boot 会自动配置合适的 CacheManager 作为相关缓存的供应商。如果只引入了该包,则默认只会使用 Spring 上下文 ConcurrentHashMap 结构来存储缓存,这完全符合 JCache 的标准。

如果当前上下文中存在 JSR-107 API,即 javax.cache:cache-api 该 jar 包,将额外的为 JSR-107 API 注解的 bean 创建代理,这些注解是 @CacheResult,@CachePut,@CacheRemove 和 @CacheRemoveAll。

使用 JCache 来构建 Hibernate 二级缓存

要使用 JCache 的内置集成,您需要将 hibernate-jcache 模块 org.hibernate:hibernate-jcache 依赖集成进去。此外,还需要添加 JCache 实现。

hibernate-jcache 模块定义了以下 Region 工厂类:JCacheRegionFactory。

要使用该 JCacheRegionFactory,只需配置该属性为:hibernate.cache.region.factory_class: org.hibernate.cache.jcache.JCacheRegionFactory。

JCache 要求共享相同 URI 和类加载器的 CacheManagers 在 JVM 中是唯一的。如果未指定其他属性,JCacheRegionFactory 将加载默认的 JCache 供应商并创建默认的 CacheManager。如果未特殊指定,将使用默认的 javax.cache.configuration.MutableConfiguration 创建缓存。

为了控制使用哪个供应商为 CacheManager 和 Caches 指定配置,可以使用以下两个属性:

<property
    name="hibernate.javax.cache.provider"
    value="org.ehcache.jsr107.EhcacheCachingProvider"/>
<property
    name="hibernate.javax.cache.uri"
    value="file:/path/to/ehcache.xml"/>

使用 EhCache 3.0 构建 Hibernate 二级缓存

使用 EhCache 3.0 时,hibernate 并没有默认的模块支持。但是 EhCache 遵循 JCache 标准,我们可以通过 JCache 来指定具体的实现 Ehcache 来配置缓存。

我们同时引入 org.ehcache:ehcache 和 org.hibernate:hibernate-jcache,这里我们并没有指明具体版本,因为我们使用 spring boot starter 管理的版本,具体可以查看自己 spring boot starter 的版本和依赖版本。

Spring Boot 本身支持 JCache 的自动配置,它使用 JCacheCacheConfiguration 自动配置类搜索我们环境中 JCache 供应商并为我们创建合适的 CacheManager,在创建 CacheManager 过程中它提供了一些自定义的函数钩子以便我们进行扩展,下面的配置就是使用 JCacheManagerCustomizer 函数为 Hibernate 的实体和关联创建需要的缓存。

在创建完 JCache 的 CacheManager 后,我们同样通过 Spring Boot 自动配置 Hibernate 的自定义钩子函数 HibernatePropertiesCustomizer 将创建的 CacheManager 配置给 Hibernate,以便 Hiberrnate 使用我们的配置来管理二级缓存。

@Configuration
@EnableCaching
public class CacheConfiguration {

    private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;

    public CacheConfiguration(ApplicationProperties applicationProperties) {
        ApplicationProperties.Cache.Ehcache ehcache = applicationProperties.getCache().getEhcache();

        CacheEventListenerConfigurationBuilder cacheEventListenerConfiguration = CacheEventListenerConfigurationBuilder
            .newEventListenerConfiguration(new CacheListener(applicationProperties), EventType.CREATED, EventType.UPDATED, EventType.EVICTED, EventType.EXPIRED, EventType.REMOVED)
            .unordered().asynchronous();

        jcacheConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(
            CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class,
                ResourcePoolsBuilder.heap(ehcache.getMaxEntries()))
                .add(new Jsr107CacheConfiguration(ConfigurationElementState.ENABLED, ConfigurationElementState.ENABLED))
                .add(cacheEventListenerConfiguration)
                .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(ehcache.getTimeToLiveSeconds())))
                .build());
    }
  
    @Bean
    public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(javax.cache.CacheManager cacheManager) {
        return hibernateProperties -> hibernateProperties.put(ConfigSettings.CACHE_MANAGER, cacheManager);
    }

    @Bean
    public JCacheManagerCustomizer cacheManagerCustomizer() {
        return cm -> {
         cm.createCache(com.zeral.domain.User.class.getName(), jcacheConfiguration);
         cm.createCache(com.auo.spc.domain.User.class.getName() + ".authorities", jcacheConfiguration);
         // application-needle-ehcache-add-entry
        };
    }
}

上述代码使用了堆内存储,并指定了每个域最大缓存条目数,指定了缓存键和值的类型,以及过期策略,并开启了监控,配置了缓存监听器。

使用 Redis 构建 Hibernate 二级缓存

Ehcache 启动速度非常快,对普通的单体应用来说是比较好的缓存解决方案。但随着我们项目规模的扩大,单节点的 Ehcache 逐渐不能满足需求,Ehcache 的分布式需要引入其它组件,增加运维成本。使用天生支持分布式,且流行度比较高的高性能缓存缓存解决方案 Redis 是当前最佳选择。

我们使用 Redisson 作为 Java 客户端,主要是因为:

  • 它被 Redis 官方强烈推荐
  • 它提供了一个 JCache (JSR-107) 实现

同样的我们以 JCache 为标准使用 Redis 作为缓存供应商来配置我们的缓存:

@Configuration
@EnableCaching
public class CacheConfiguration {

    private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;

 		@Bean
    public javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration(JHipsterProperties jHipsterProperties) {
        MutableConfiguration<Object, Object> jcacheConfig = new MutableConfiguration<>();

        URI redisUri = URI.create(jHipsterProperties.getCache().getRedis().getServer()[0]);

        Config config = new Config();
        // 集群配置
        if (jHipsterProperties.getCache().getRedis().isCluster()) {
            ClusterServersConfig clusterServersConfig = config
                .useClusterServers()
                .setMasterConnectionPoolSize(jHipsterProperties.getCache().getRedis().getConnectionPoolSize())
                .setMasterConnectionMinimumIdleSize(jHipsterProperties.getCache().getRedis().getConnectionMinimumIdleSize())
                .setSubscriptionConnectionPoolSize(jHipsterProperties.getCache().getRedis().getSubscriptionConnectionPoolSize())
                .addNodeAddress(jHipsterProperties.getCache().getRedis().getServer());

            if (redisUri.getUserInfo() != null) {
                clusterServersConfig.setPassword(redisUri.getUserInfo().substring(redisUri.getUserInfo().indexOf(':') + 1));
            }
        } else {
            // 单节点配置
            SingleServerConfig singleServerConfig = config
                .useSingleServer()
                .setConnectionPoolSize(jHipsterProperties.getCache().getRedis().getConnectionPoolSize())
                .setConnectionMinimumIdleSize(jHipsterProperties.getCache().getRedis().getConnectionMinimumIdleSize())
                .setSubscriptionConnectionPoolSize(jHipsterProperties.getCache().getRedis().getSubscriptionConnectionPoolSize())
                .setAddress(jHipsterProperties.getCache().getRedis().getServer()[0]);

            if (redisUri.getUserInfo() != null) {
                singleServerConfig.setPassword(redisUri.getUserInfo().substring(redisUri.getUserInfo().indexOf(':') + 1));
            }
        }
        jcacheConfig.setStatisticsEnabled(true);
        jcacheConfig.setExpiryPolicyFactory(
            CreatedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, jHipsterProperties.getCache().getRedis().getExpiration()))
        );
        return RedissonConfiguration.fromInstance(Redisson.create(config), jcacheConfig);
    }
  
    @Bean
    public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(javax.cache.CacheManager cacheManager) {
        return hibernateProperties -> hibernateProperties.put(ConfigSettings.CACHE_MANAGER, cacheManager);
    }

    @Bean
    public JCacheManagerCustomizer cacheManagerCustomizer() {
        return cm -> {
         cm.createCache(com.zeral.domain.User.class.getName(), jcacheConfiguration);
         cm.createCache(com.auo.spc.domain.User.class.getName() + ".authorities", jcacheConfiguration);
         // application-needle-ehcache-add-entry
        };
    }
}

Hibernate 二级缓存如何工作

每当要加载实体时,都会触发 LoadEevent,并且 DefaultLoadEventListener 对其进行如下处理:

Object entity = loadFromSessionCache( event,
    keyToLoad, options );
if ( entity == REMOVED_ENTITY_MARKER ) {
    LOG.debug("Load request found matching entity
        in context, but it is scheduled for removal;
        returning null" );
    return null;
}
if ( entity == INCONSISTENT_RTN_CLASS_MARKER ) {
    LOG.debug("Load request found matching entity
        in context, but the matched entity was of
        an inconsistent return type;
        returning null"
    );
    return null;
}
if ( entity != null ) {
    if ( traceEnabled ) {
        LOG.tracev("Resolved object in "
            + "session cache: {0}",
            MessageHelper.infoString( persister,
                event.getEntityId(),
                event.getSession().getFactory() )
        );
    }
    return entity;
}
 
entity = loadFromSecondLevelCache( event,
    persister, options );
if ( entity != null ) {
    if ( traceEnabled ) {
        LOG.tracev("Resolved object in "
            + "second-level cache: {0}",
            MessageHelper.infoString( persister,
                event.getEntityId(),
                event.getSession().getFactory() )
        );
    }
} else {
    if ( traceEnabled ) {
        LOG.tracev("Object not resolved in "
            + "any cache: {0}",
            MessageHelper.infoString( persister,
                event.getEntityId(),
                event.getSession().getFactory() )
        );
    }
    entity = loadFromDatasource( event, persister,
        keyToLoad, options );
}

该 doLoad 方法协调加载给定实体的工作。首先,尝试从会话级缓存中加载实体。如果在此处找不到,再尝试从二级缓存加载。最后,尝试直接从数据源加载它。

二级缓存内部

实体被作为 CacheEntry 存储,实体 水合 (hydrated)1 状态用于创建缓存条目值。

水合

在 Hibernate 中,水合作用 是将 JDBC ResultSet 转换为由原始值组成数组的过程:

final Object[] values = persister.hydrate(
    rs, id, object,
    rootPersister, cols, eagerPropertyFetch, session
);

水合状态以 EntityEntry 对象的形式保存在当前运行的持久化上下文中,该对象封装了加载时的实体快照。然后通过以下方式使用水合状态:

  • 默认脏检查机制,它将当前实体数据与加载时的快照比较
  • 二级缓存,其缓存项是根据加载时实体快照构建的

反向操作称为脱水,它将实体状态复制到 INSERT 或 UPDATE 语句中。

二级缓存键

键 CacheKey 在之前介绍过,默认会将标识符封装在元组中构建。

二级缓存值元素

二级缓存值元素使用拆解的水合状态构建:

final CacheEntry entry = persister.buildCacheEntry(entity, hydratedState, version, session);

水合状态在存储到 CacheEntry 中之前先进行分解:

this.disassembledState = TypeHelper.disassemble(
    state, 
    persister.getPropertyTypes(),
    persister.isLazyPropertiesCacheable() ? null : persister.getPropertyLaziness(),
    session,
    owner
);

接下来会根据配置来选择是否结构化存储值,其中:

  • 不存储 id(主键)作为缓存键的一部分存储
  • 瞬态(Transient)属性不存储
  • 集合不存储
  • 非关联属性值以其原始格式存储
  • ToOne 关联仅存储 id(外键)

我们必须明确指出集合(OneToMany 或 ManyToMany 关联)是可缓存的,否则就不会缓存。

实际上,Hibernate 将集合存储在单独的缓存区域中,每个集合一个。区域名称是完全限定的类名称加上集合属性的名称。如上诉代码手动添加的用户角色关联缓存。

总结

二级缓存是关系数据缓存,因此它以规范化形式存储数据,并且每个实体更新仅影响一个缓存条目。由于在二级缓存条目中未实现实体关联,因此无法读取整个实体图(entity graph)。

聚合的实体图以使写入操作复杂化为代价,但为读取操作提供了更好的性能。如果未对缓存的数据进行规范化并将其散布在各种聚合模型中,则实体更新将不得不修改多个缓存项,从而影响写入操作的性能。

由于它反映了基础关系数据,因此二级缓存提供了各种并发策略机制,因此我们可以平衡读取性能和保证一致性。


原文链接:Java Cache 入门


  1. 水合,最初是一个术语,表示从 DB 中填充实例化但为空的值-对象/模型。在给某物补水时,例如干无花果,该物质的本质在那里,但缺乏饱满感。这就是“水合”对象时发生的事情。 ↩︎

Caffeine Cache基础入门 1、自动将数据加载到缓存中,同时也可以采用异步的方式加载。2、内存淘汰策略:基于频次、基于最近访问、最大容量。3、根据上一次的缓存访问\上一次的数据写入决定缓存的过期的设置。4、当一条缓存数据过期了,自动清理,清理的时候也是异步线程来做。5、考虑JVM的内存管理机制,加入弱引用、软引用。6、缓存数据被清理后,会收到相关的通知信息7、缓存数据的写入可以传播到外部的存储。8、统计功能:被访问次数,命中,清理的个数,加载个数.build();cache.put("name","张三"); 阅读详情

相关推荐

芋道源码 —— Spring Boot 缓存 Cache 入门

随着系统访问量的增加,数据库往往成为性能瓶颈。为了减少数据库的压力,提高系统的响应速度,我们可以通过缓存来优化系统性能。

shaozheng0503的博客 1847

Java系列 - 缓存方案

缓存缓存的本质就是存储在内存中的KV数据结构,对应的就是jdk中的HashMap,但是要实现缓存,还需要考虑并发安全性、容量限制等策略,下面简单介绍一种利用LinkedHashMap实现缓存的方式。

ifredom 2257

Java 缓存规范:JCache API 与强大的 Spring Cache

Spring Cache 是 Spring 框架提供的缓存模块,它对缓存的使用进行了封装,提供了一套更加简化和便捷的缓存操作方式。JCache API 是 Java 缓存规范的一部分,它定义了一套通用的缓存接口,使开发人员可以在不同的缓存实现之间无缝切换。用作缓存的键,方法返回值会被缓存起来。接下来,使用缓存管理器创建了一个名为 “myCache” 的缓存,并向其中存储了一个键值对。在使用 Spring Cache 时,需要在配置文件中配置缓存管理器和缓存策略,以及指定需要使用缓存的方法等。

DevSavantX的博客 415

java中cache是什么_java中的cache机制

java应用中cache最常用的场合是:将程序或系统经常要调用的对象存在内存中,以便其使用时可以快速调用,不必再去创建新的重复的实例。这样做可以减少系统开销,提高系统效率。一般的用法就是把数据从数据库读到内存,然后之后的数据访问都从内存来读,从而减少对数据库的读取次数来提高效率。使用cache时,不应涉及业务逻辑,而是单纯的为了提高程序效率。应该保证cache拿掉了程序也能运行,且结果正确!读访问...

weixin_39710462的博客 2323

JAVA缓存规范 —— 虽迟但到的JCache API与天生不俗的Spring Cache

业界各大厂商或开源团队都会构建并提供一些缓存框架组件提供给开发者按需选择,这里就会涉及到一个标准规范的遵循问题,本文我们一起聊聊JCache API规范与SpringCache规范。

是Vzn呀 415

docker 安装oracle

本文介绍了使用Docker部署Oracle数据库的实践指南。主要内容包括:常用Oracle镜像的阿里云和华为云拉取地址,11g/12c/19c等不同版本的启动命令示例,以及连接数据库的基本信息。实战部分详细演示了Oracle 11g的安装过程,包括镜像拉取、容器启动、密码修改和数据库连接步骤,并通过Sql Developer和命令行两种方式创建用户。最后提到数据持久化的重要性,建议将数据库文件挂载到宿主机目录以防止容器删除导致数据丢失。文章提供了完整的操作命令和截图说明,适合需要快速部署Oracle测试环境

weixin_39766667的博客 372

【Java】Guava学习:Cache缓存入门

摘要: 学习Google内部使用的工具包Guava,在Java项目中轻松地增加缓存,提高程序获取数据的效率。 一、什么是缓存? 根据科普中国的定义,缓存就是数据交换的缓冲区(称作Cache),当某一硬件要读取数据时,会首先从缓存中查找需要的数据,如果找到了则直接执行,找不到的话则从内存中找。由于缓存的运行速度比内存快得多,故缓存的作用就是帮助硬件更快地运行。 在这里,我们借用了硬件缓存的概念,...

然后微微笑的博客 495

基于Quartus II软件的FPGA综合实验——多功能数字钟

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、设计要求二、设计原理三、设计过程1.数码管扫描模块2.计时模块3.闹钟模块4.闹钟响铃模块5.数码管显示模块6.整点报时功能7.模式选择模块(计时器主控电路)四、实验中遇到的困难及解决方案五、总结 前言 FPGA综合实验——多功能数字钟 基于Quartus II软件 一、设计要求 设计一个能进行时、分、秒计时的十二小时制或二十四小时制的数字钟,并具有定时与闹钟功能,能在设定的时间发出闹铃音,能非常方便地对小时、分钟和秒进.

qq_53022342的博客 2万+

Java的应用缓存cache如何入门

Java的应用缓存cache入门:java常用的缓存有:ehcache, oscache,jcache,这些cache都是单机的,即存在本机的内存中,另外分布式的cache我用过memcache,它被独立部署在一台服务器上,可以实现多个客户端共用缓存。 一般用到缓存的场景:在处理并发请求,需要及时响应的。加快系统响应速度。举个例子:比如购物网站有  售卖物品的排行榜,这种数据都是由数据库中N多表

makerule的博客 814

避坑指南:CentOS7.9安装Python3.8.16后yum报错的快速修复方法

本文详细解析了在CentOS7.9系统安装Python3.8.16后导致yum报错的原因,并提供了快速修复方法。通过修改yum配置文件指向Python2解释器,以及使用alternatives工具管理多版本Python,有效解决兼容性问题。同时推荐使用虚拟环境隔离开发环境,避免影响系统稳定性。

weixin_26908317的博客 493

QMI8658姿态传感器实战:从校准到自检的完整避坑指南

本文详细解析了QMI8658姿态传感器的校准与自检流程,提供了从硬件环境搭建到高级功能开发的完整避坑指南。通过真实案例揭示电源设计、接口优化、温度补偿等关键细节,帮助开发者提升传感器精度和可靠性,适用于工业级应用场景。

weixin_29184075的博客 455

cache2k快速入门:5分钟构建你的第一个高性能Java缓存

cache2k是一款轻量级、高性能的Java缓存库,专为需要快速响应和高效数据处理的应用设计。本文将带你在5分钟内完成从环境配置到实现基础缓存功能的全过程,让你轻松掌握这款强大工具的核心使用方法。 ## 📦 第一步:添加依赖 要在项目中使用cache2k,首先需要添加Maven依赖。打开你的`pom.xml`文件,加入以下配置: ```xml <dependency> <groupId

gitblog_00612的博客 900

【科研建模】Pycaret自动机器学习框架使用流程及多分类项目实战案例详解

对pycaret的使用流程进行梳理,并详细介绍了基于多分类预测的案例

lys_828的博客 780

SpringBoot——Cache缓存初探,java基础入门第二版知识点总结

public List testCache(String key){ List list = new ArrayList<>(); list.add(“a”); list.add(“b”); list.add(“c”); list.add(key); return list; } /** 修改缓存,缓存的名字是testList,用key来标识 @param key @return */ @CachePut(cacheNames = “testList” , key = “#key

m0_65320833的博客 214

ABB机器人刷功能选项全一篇就够——不怕丢失程序和IO配置数据

ABB机器人刷功能选项

m0_66701835的博客 1258

机器人轨迹规划——梯形速度轨迹(超详细)

机器人轨迹 这一系列轨迹教程将主要包括以下内容: 点到点轨迹(P2P) 五次多项式插值轨迹 三次多项式插值轨迹 梯形速度曲线轨迹 双S形速度曲线轨迹 多个自由度轨迹的时间同步 在线轨迹规划 多项式在线轨迹规划 梯形在线轨迹规划 双S形在线轨迹规划 非线性实时轨迹滤波 多点轨迹(Multi-point) 三次样条曲线(cubic spline) 贝赛尔曲线(Bezier Curve) B样条曲线(BSpline) 时间最优轨迹 三次样条时间最优轨迹 任意路径下的时间最优轨迹 时间

出门搔白首,若负平生志 1万+

Caffeine Cache基础入门:Java本地缓存之王完全指南

本文介绍了高性能Java本地缓存库Caffeine的核心用法,它是Spring Boot 2.x默认的缓存组件。主要内容包括:Caffeine的八大特性(自动加载、多种淘汰策略等);基础使用方法与Maven依赖;同步/异步两种缓存加载机制;三种缓存淘汰策略(基于容量、时间、引用);以及状态统计与监控功能。文章通过完整代码示例,帮助开发者从入门到精通掌握这一"本地缓存之王",替代传统的Guava Cache实现更高效的缓存方案。

java_cj的专栏 579

ethernet eth0: Could not attach to PHY

整个流程看下来就是MAC connect PHY的过程失败了,失败的具体原因根据config_init,上图(bcm54xx_config_init) ,从此方向入手去定位原因;我这里的问题是has_gmac4没有置1,根据 has_gmac4: uses GMAC4 core.按照上面的调用逻辑,添加了部分打印,定位到__phy_read接口返回失败。stmmac_mdio_read 大部分都是linux的标准接口。最终取决于平台用的是哪家的phy,调用config_init。

无线互联&网络介绍 2700

《partner4java 讲述Spring入门》之:spring cache支持(spring3.1如何使用cache 缓存)

(以下内容参照自官方文档;p4jorm下载地址http://blog.youkuaiyun.com/partner4java/article/details/8629578;cache demo下载地址http://download.youkuaiyun.com/detail/partner4java/5102477) 若您只想尽快简单的使用,可以直接跳转到“第三部分:Hello World” 1、引言:

java相关技术(partner4java专栏)希望对您的学习有所帮助 1万+

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

【本地缓存】Java 中的 4 种本地缓存

是 Google 开发的 Guava 工具包中一套完善的JVM 本地缓存框架,底层实现的数据结构类似于,但是进行了更多的能力拓展,包括:缓存过期时间设置、缓存容量设置、多种淘汰策略、缓存监控等。是一个支持高并发的线程安全的本地缓存。多线程情况下也可以安全的访问或者更新 Cache,这些都是借鉴了的结果支持最大容量限制支持两种过期删除策略(插入时间和读取时间)支持简单的统计功能基于 LRU 算法实现Caffeine 官网Caffeine 是基于Java 1.8 的高性能本地缓存库。

sco5282的博客 1万+

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

java项目当中使用redis

分类数据一般情况下不会做过多的修改,因此可以将分类数据进行缓存,以提高页面的加载速度。

qq_62079814的博客 430

Java 本地缓存入门指南

易用性:Guava Cache、Caffeine 和 EhCache 都有成熟的接入方案。功能性:Guava Cache 和 Caffeine 功能类似,EhCache 功能更丰富。性能比较:Caffeine 性能最佳,其次是 Guava Cache,EhCache 性能较差。推荐在本地缓存中使用 Caffeine,结合 Redis 或 Memcache 构建多级缓存体系,以保证性能和可靠性。

qq_45438032的博客 1011

JetCache快速入门指南:高效Java缓存解决方案

JetCache是一个基于Java的缓存框架,由阿里巴巴开源,提供了统一的缓存访问API和注解驱动的缓存操作方式。它支持多种缓存后端(如Redis、内存缓存等),并能轻松实现多级缓存架构,是提升Java应用性能的利器。 ## 核心概念 在开始使用JetCache前,需要了解几个核心概念: 1. **Cache实例**:表示一个具体的缓存容器,类似于Map结构 2. **方法缓存**:通过注解...

gitblog_00999的博客 708

java后端项目技术记录(二)

是Spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。定时任务框架定时自动执行某段Java代码只要是需要定时处理的场景都可以使用Spring TaskWebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接, 并进行双向数据传输。HTTP是短连接WebSocket是长连接HTTP通信是单向的,基于请求响应模式WebSocket支持双向通信。

weixin_53475254的博客 692

Ensemble入门教程之CacheObject教程说明

一.教程说明  本教程分为三部分,第一部分为CacheObject语言学习部分,第二部分为Cache SQL 部分,第三部分为Ensemble入门学习。教程更新速度为一天一节内容,读者可以根据自己的需要选择性阅读。 教程适用对象为:有面向对象基础的人员,或者拥有JAVA,C#开发经验者。

501室 2005
下一篇: 解决 Hibernate N+1 问题
ZeralZhang
博客等级 码龄9年 1粉丝 6原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值