How Dictionary Data Caching and Synchronization Work in ContiNew‑Admin
ContiNew‑Admin stores dictionary data in three synchronized layers—database, Redis via JetCache, and an in‑memory ConcurrentHashMap—ensuring consistency by invalidating Redis keys on every write operation while keeping enum‑based dictionaries in a read‑only memory cache populated at startup.
ContiNew‑Admin implements a multi‑tier caching strategy for system dictionaries to minimize database load while guaranteeing data consistency. This article examines how the repository coordinates persistent storage, distributed Redis caching, and local in‑memory enum mappings in continew‑org/continew‑admin. The implementation relies on JetCache annotations for automatic caching and explicit cache eviction during write operations.
Three-Layer Caching Architecture
The system maintains dictionary data across three distinct layers, each serving a specific purpose in the caching hierarchy.
Database Layer
The database serves as the source of truth, storing persistent records in the dict and dict_item tables. All write operations—create, update, and delete—execute against these tables first. No application cache is updated directly without a corresponding database transaction.
Redis Cache via JetCache
The Redis cache layer stores serialized lists of dictionary items keyed by dictionary code. In continew‑system/src/main/java/top/continew/admin/system/mapper/DictItemMapper.java, the query method uses JetCache’s @Cached annotation:
@Cached(key = "#dictCode", name = CacheConstants.DICT_KEY_PREFIX)
List<LabelValueResp> listByDictCode(@Param("dictCode") String dictCode);
The cache key follows the pattern defined in continew-common/src/main/java/top/continew/admin/common/constant/CacheConstants.java, where DICT_KEY_PREFIX resolves to "DICT" plus a delimiter. The first query for a specific code stores the result in Redis under DICT:<dictCode>; subsequent requests hit Redis directly, bypassing the database.
In-Memory Enum Dictionary Cache
The in‑memory cache holds enum‑based dictionaries in a static ConcurrentHashMap<String, List<LabelValueResp>> named ENUM_DICT_CACHE. Unlike the Redis layer, this cache is read‑only after application startup and stores dictionaries derived from Java enums implementing BaseEnum. It resides in continew‑system/src/main/java/top/continew/admin/system/service/impl/DictItemServiceImpl.java.
Cache Population and Eviction Logic
Synchronization depends on distinct mechanisms for reading and writing data.
Automatic Redis Caching on Reads
When DictItemServiceImpl.listByDictCode() receives a request, it first checks the in‑memory enum cache. If the code is not present, it delegates to DictItemMapper.listByDictCode(), which triggers JetCache:
return Optional.ofNullable(ENUM_DICT_CACHE.get(dictCode.toLowerCase()))
.orElseGet(() -> baseMapper.listByDictCode(dictCode));
If the mapper executes, JetCache intercepts the result and stores it in Redis using the configured key prefix.
Explicit Cache Invalidation on Writes
All write operations in DictItemServiceImpl immediately clear the Redis cache to prevent stale data. The service uses RedisUtils.deleteByPattern() from continew‑starter‑cache‑redisson/src/main/java/top/continew/starter/cache/redisson/util/RedisUtils.java to remove keys matching DICT:*:
- Create – The
beforeCreate()method deletes the pattern after inserting the new record (lines 60‑63). - Update – The
beforeUpdate()method performs the same eviction after saving changes (lines 66‑69). - Batch Delete – The
deleteByDictIds()method clears the pattern after removing rows (lines 78‑84).
Because the wildcard pattern matches every dictionary key, any modification forces the next read operation to repopulate Redis with fresh data from the database.
Startup Enum Scanning
The in‑memory enum cache populates once during application initialization via an @PostConstruct method in DictItemServiceImpl:
@PostConstruct
public void init() {
Set<Class<?>> classSet = ClassUtil.scanPackageBySuper(applicationProperties.getBasePackage(), BaseEnum.class);
for (Class<?> cls : classSet) {
List<LabelValueResp> value = this.toEnumDict(cls);
if (CollUtil.isEmpty(value)) { continue; }
String key = StrUtil.toUnderlineCase(cls.getSimpleName()).toLowerCase();
ENUM_DICT_CACHE.put(key, value);
}
log.debug("枚举字典已缓存到内存:{}", ENUM_DICT_CACHE.keySet());
}
This method scans the base package for all BaseEnum implementations, converts constants to LabelValueResp objects, and stores them under snake_case keys. Because enums rarely change at runtime, the system never evicts these entries.
Implementation in Source Code
The synchronization flow relies on specific classes in the continew‑admin codebase.
Cache Constants – CacheConstants.java defines the key structure:
String DICT_KEY_PREFIX = "DICT" + DELIMITER; // Results in "DICT:"
Mapper Layer – DictItemMapper.java applies the JetCache annotation to the query method, enabling automatic Redis storage without manual template code.
Service Layer – DictItemServiceImpl.java orchestrates the three layers:
- Lines 27‑36 contain the
init()method for enum scanning. - Lines 72‑75 implement the fallback logic from enum cache to database.
- Lines 60‑84 handle cache eviction during mutations.
Usage Examples
Fetching a dictionary automatically leverages the appropriate cache layer:
@Autowired
private DictItemService dictItemService;
// Returns cached enum list if present, otherwise DB + Redis cached list
List<LabelValueResp> colors = dictItemService.listByDictCode("color");
Creating a dictionary item triggers immediate cache invalidation:
@Autowired
private DictItemService dictItemService;
DictItemReq req = new DictItemReq();
req.setDictId(1L);
req.setValue("NEW");
req.setLabel("New Item");
dictItemService.create(req); // Cache eviction happens inside beforeCreate()
Adding a new enum dictionary requires only implementing BaseEnum and restarting:
public enum OrderStatus implements BaseEnum {
SUCCESS("成功", "1"),
FAIL("失败", "0");
private final String label;
private final String value;
// constructor and getters...
}
After restart, order_status becomes available via listByDictCode("order_status") without database queries.
Summary
- Three storage layers – Database persists data, Redis caches database queries via JetCache, and a
ConcurrentHashMapholds enum dictionaries in memory. - Read path – Enum cache is checked first, then Redis via
@Cached, then database as fallback. - Write path – Every create, update, or delete operation executes
RedisUtils.deleteByPattern(CacheConstants.DICT_KEY_PREFIX + "*")to clear the Redis layer immediately. - Enum caching – Classpath scanning during
@PostConstructpopulates the read‑only memory cache once at startup. - Consistency guarantee – No manual cache clearing is required; the service layer handles synchronization automatically.
Frequently Asked Questions
How does ContiNew‑Admin ensure cache consistency when dictionary items are modified?
The service layer invalidates Redis entries immediately after any database write. Methods like beforeCreate(), beforeUpdate(), and deleteByDictIds() in DictItemServiceImpl execute RedisUtils.deleteByPattern() using the DICT:* wildcard, forcing subsequent reads to fetch fresh data from the database and repopulate the cache.
What distinguishes database dictionaries from enum dictionaries?
Database dictionaries are dynamic entries stored in dict_item tables and cached in Redis. Enum dictionaries are static Java constants implementing BaseEnum, loaded into an in‑memory ConcurrentHashMap at application startup. Enum dictionaries bypass both the database and Redis entirely, offering the lowest latency for unchanging reference data.
Why does the system delete cache entries by pattern rather than specific keys?
The deleteByPattern approach using DICT:* ensures that any structural change to a dictionary—such as adding an item to a different dictionary type—clears all potentially stale dictionary caches without requiring the service to track individual key dependencies. This trade‑off prioritizes consistency and implementation simplicity over granular cache retention.
How can I add a new dictionary that loads from an enum?
Create a Java enum implementing BaseEnum in any package under the configured basePackage. The @PostConstruct init() method in DictItemServiceImpl automatically scans the classpath, converts the enum to a list of LabelValueResp objects, and stores it in ENUM_DICT_CACHE under a snake_case key matching the enum name. The dictionary becomes available via listByDictCode() immediately after application restart.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →