Caching in MyBatis With Valkey or Redis
MyBatis is a lightweight Java persistence framework that maps methods to SQL statements and stored procedures. Its second-level cache can store query results across sessions, cutting database round-trips and improving response times. To share that cache across every node in a distributed application, you need to back it with an external data store — and that is where Valkey or Redis comes in.
This article shows how to add a distributed MyBatis cache with the redisson-mybatis module, how it differs from the caching extension bundled with MyBatis, and how Redisson's local caching and data partitioning features take it further.
How MyBatis Caching Works
MyBatis has two cache levels:
-
First-level (local) cache is enabled by default and scoped to a single
SqlSession. Once the session closes, the cache is gone. It is not shared between sessions or application instances. -
Second-level cache is scoped to a mapper namespace and shared across sessions. You turn it on by adding a
<cache>element to a mapper, and you choose the implementation with thetypeattribute.
The default second-level cache is an in-process, per-JVM store. That is fine for a single instance, but in a clustered deployment each node keeps its own copy — so cache hit rates drop and cache invalidation becomes inconsistent across the fleet. Backing the second-level cache with Valkey or Redis turns it into a distributed cache that every node shares.
Two Ways to Back MyBatis With Valkey or Redis
There are two common options:
-
The bundled
mybatis/redis-cacheextension. It is maintained by the MyBatis project and uses Jedis under the hood. It covers the basics but offers no per-entry expiration, no near cache, and no data partitioning. -
The
redisson-mybatismodule. Built on Redisson'sRMapCache, it adds per-entry time-to-live and idle eviction, bounded cache sizes, an optional client-side (local) cache, data partitioning in cluster mode, and native support for both Valkey and Redis.
The rest of this guide uses the Redisson module.
Setting Up MyBatis Caching With Redisson
1. Add the Dependency
Maven:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-mybatis</artifactId>
<version>4.6.1</version>
</dependency>
Gradle:
implementation 'org.redisson:redisson-mybatis:4.6.1'
The org.redisson group ID above is the open-source Community Edition, which covers the standard distributed cache. The local caching and data partitioning features described later are Redisson PRO capabilities.
<dependency>
<groupId>pro.redisson</groupId>
<artifactId>redisson-mybatis</artifactId>
<version>4.6.1</version>
</dependency>
2. Configure the Valkey or Redis Connection
Create a redisson.yaml file on the classpath (for example, src/main/resources/redisson.yaml). A single-node setup looks like this:
singleServerConfig:
address: "redis://127.0.0.1:6379"
The same configuration works against Valkey. For a clustered deployment, use clusterServersConfig instead:
clusterServersConfig:
nodeAddresses:
- "redis://127.0.0.1:7000"
- "redis://127.0.0.1:7001"
- "redis://127.0.0.1:7002"
Redisson also supports replicated, sentinel, and managed deployments (AWS ElastiCache and MemoryDB, Azure Cache, Google Cloud Memorystore, and others) through the same config file.
3. Enable the Cache in Your Mapper
Add a <cache> element to the mapper XML and point its type at the Redisson implementation:
<?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.mapper.StudentMapper">
<cache type="org.redisson.mybatis.RedissonCache">
<property name="timeToLive" value="200000"/>
<property name="maxIdleTime" value="100000"/>
<property name="maxSize" value="100000"/>
<property name="redissonConfig" value="redisson.yaml"/>
</cache>
<select id="selectById" resultType="com.example.model.Student">
select * from student where id = #{id}
</select>
</mapper>
Note the class name: it is org.redisson.mybatis.RedissonCache, not a Hibernate class. With this in place, results from selectById are stored in Valkey or Redis and shared across every application node pointed at the same server.
Configuration Parameters
The standard cache supports the following properties:
| Property | Description |
|---|---|
timeToLive | Maximum time, in milliseconds, an entry is kept before it expires. |
maxIdleTime | Maximum time, in milliseconds, an entry may stay unused before it is evicted. |
maxSize | Maximum number of entries. Superfluous entries are evicted by the configured policy. 0 means unbounded. |
redissonConfig | Path to the Redisson YAML configuration file. |
A key detail: these limits apply per entry. The bundled redis-cache extension expires the cache as a whole rather than tracking time-to-live and idle time for each entry, so Redisson gives you far finer control over what stays warm and what gets evicted.
Faster Reads With Local Caching
For read-heavy workloads, a near cache — Redisson calls it a local cache — keeps a subset of entries in the application's own memory so hot reads never leave the JVM. Because reading from local memory avoids a network round-trip, it executes read operations up to 45 times faster than a standard distributed cache. Local cache instances with the same name share a pub/sub channel and exchange update and invalidate events, so nodes stay consistent.
Use the RedissonLocalCachedCache implementation:
<cache type="org.redisson.mybatis.RedissonLocalCachedCache">
<property name="timeToLive" value="200000"/>
<property name="maxIdleTime" value="100000"/>
<property name="maxSize" value="100000"/>
<property name="localCacheEvictionPolicy" value="LRU"/>
<property name="localCacheSize" value="1000"/>
<property name="localCacheTimeToLive" value="2000000"/>
<property name="localCacheMaxIdleTime" value="1000000"/>
<property name="localCacheSyncStrategy" value="INVALIDATE"/>
<property name="redissonConfig" value="redisson.yaml"/>
</cache>
The additional properties control the in-memory tier:
| Property | Description |
|---|---|
localCacheEvictionPolicy | Local eviction policy: LFU, LRU, SOFT, WEAK, or NONE. |
localCacheSize | Maximum entries held locally. 0 means unbounded. |
localCacheTimeToLive | Time-to-live, in milliseconds, for each local entry. |
localCacheMaxIdleTime | Max idle time, in milliseconds, for each local entry. |
localCacheSyncStrategy | How updates propagate between instances: INVALIDATE, UPDATE, or NONE. |
If you want to understand the pattern behind this, see our guide to client-side caching on Java for Valkey and Redis and the LRU cache glossary entry.
Scaling With Data Partitioning
By default a Valkey or Redis map lives on a single master node, even in a cluster — so one node's memory and throughput cap the cache. Redisson's data partitioning spreads a single logical cache across multiple master nodes, scaling available memory, read/write throughput, and the eviction process along with the cluster. For MyBatis, use the partitioned implementation:
<cache type="org.redisson.mybatis.RedissonCacheV2">
<property name="timeToLive" value="200000"/>
<property name="redissonConfig" value="redisson.yaml"/>
</cache>
This makes Redisson the practical choice for large distributed applications whose MyBatis cache would otherwise be bottlenecked on a single node.
Choosing the Right Implementation
| Implementation | What it adds | Edition |
|---|---|---|
RedissonCache | Standard distributed cache with per-entry TTL, idle eviction, and bounded size. | Community |
RedissonCacheNative | Uses native server-side entry expiration; requires Valkey 9.0+ or Redis 7.4+. | Community |
RedissonCacheV2 | Adds data partitioning across cluster master nodes. | Redisson PRO |
RedissonLocalCachedCache | Adds a near cache for up to 45× faster reads. | Redisson PRO |
Editions can change between releases — confirm the current breakdown on the feature comparison page. Full details for every option are in the Cache API implementations documentation.
Redisson vs. the Bundled redis-cache Extension
Both back MyBatis with Valkey or Redis, but Redisson goes further:
-
Per-entry expiration.
timeToLiveandmaxIdleTimeapply to individual entries, not the whole cache. -
Bounded caches with eviction policies. Cap memory with
maxSizeand choose how entries are evicted. - Local (near) caching. Serve hot reads from application memory for up to 45× faster reads.
- Data partitioning. Scale a single cache across multiple cluster nodes.
- Valkey and Redis, everywhere. One client for self-managed servers and managed services alike, across single-node, replicated, sentinel, and cluster topologies.
If you use MyBatis alongside other frameworks, the same engine backs Redisson's Hibernate second-level cache, Spring Cache, and JCache integrations, so caching behaves consistently across your stack. For a broader look at caching on the JVM, see How to Use Redis Cache in Java.
MyBatis Caching: Frequently Asked Questions
Does MyBatis Cache Query Results?
Yes. The first-level cache is on by default within a SqlSession. The second-level cache is shared across sessions and is enabled per mapper with a <cache> element.
Can MyBatis Use Valkey or Redis as Its Cache?
Yes. Add the redisson-mybatis module and set the mapper's cache type to a Redisson implementation. Results are then stored in Valkey or Redis and shared across all application nodes.
What Is the Difference Between MyBatis' First- and Second-Level Cache?
The first-level cache is scoped to a single session and cleared when it closes. The second-level cache is scoped to a mapper namespace and shared across sessions — and, when backed by Valkey or Redis, across application instances.
How Is a MyBatis Second-Level Cache Different From a Local Cache?
The second-level cache is the shared, distributed layer in Valkey or Redis. A local cache is an in-memory tier on the application side that fronts it, serving hot reads without a network round-trip and staying in sync through invalidate or update events.