# How Dictionary Data Caching and Synchronization Work in ContiNew‑Admin

> Explore ContiNew-Admin's dictionary data caching and synchronization across database, Redis, and memory. Learn how this ensures data consistency through effective key invalidation and read-only caching strategies.

- Repository: [OpenContiNew/continew-admin](https://github.com/continew-org/continew-admin)
- Tags: internals
- Published: 2026-02-28

---

**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:

```java
@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`](https://github.com/continew-org/continew-admin/blob/main/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:

```java
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`:

```java
@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`](https://github.com/continew-org/continew-admin/blob/main/CacheConstants.java) defines the key structure:

```java
String DICT_KEY_PREFIX = "DICT" + DELIMITER; // Results in "DICT:"

```

**Mapper Layer** – [`DictItemMapper.java`](https://github.com/continew-org/continew-admin/blob/main/DictItemMapper.java) applies the JetCache annotation to the query method, enabling automatic Redis storage without manual template code.

**Service Layer** – [`DictItemServiceImpl.java`](https://github.com/continew-org/continew-admin/blob/main/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:

```java
@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:

```java
@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:

```java
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 `ConcurrentHashMap` holds 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 `@PostConstruct` populates 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.