Centralized data persistence layer for Minecraft server backends, providing unified database access, caching, and cross-plugin data consistency.
In a multi-plugin Minecraft server, each plugin typically manages its own data, leading to:
- duplicated database connections
- inconsistent data models
- race conditions across plugins
- difficulty maintaining consistent updates across systems
DataCenterPlugin centralizes persistence into a single layer, enforcing consistent data access patterns and reducing duplication across plugins.
DataCenterPlugin acts as the core data service for a Minecraft server backend.
It provides:
- unified database access
- schema management
- caching for performance
- consistent read/write patterns
Other plugins depend on it for reliable player data storage and retrieval.
- Manage database connections using HikariCP
- Register schemas and create tables automatically
- Provide unified data access via
DataKeyabstractions - Maintain in-memory caching with TTL
- Ensure consistency across plugins accessing shared data
- Perform atomic upsert operations
-
DataCenter
Central manager handling connections and schema registration -
DataKey
Defines a data domain (table + schema metadata) -
Column
Maps Java types to SQL types -
Data / SingleData / MultipleData
Represent stored entities and persistence logic
Player Event
↓
Plugin Logic
↓
DataCenter.get(DataKey, UUID)
↓
Cache lookup (if enabled)
↓
Database query (on miss)
↓
Data object returned
↓
Modification → Upsert
↓
Cache update
Plugins access the system through a shared DataCenter instance.
Each plugin:
- Registers its
DataKey - Uses typed access to read/write data
- Relies on DataCenter for consistency
This avoids tight coupling while maintaining shared state.
- Primary storage: MariaDB
- Connection pooling: HikariCP
- Cache: In-memory, per-module TTL-based
- Configuration: YAML-based
-
SingleData
One value per player (e.g., credits) -
MultipleData
Structured multi-column data (e.g., stats, rewards)
- Writes use an upsert strategy (UPDATE → fallback INSERT)
- Cache is invalidated immediately on write
- Read-after-write consistency is guaranteed within a single server instance
- No distributed locking; consistency relies on controlled access through DataCenter
- Modular data registration via
DataKey - Automatic table creation
- TTL-based caching for frequently accessed data
- Type-safe schema definitions
- Built-in support for leaderboard queries
Instead of separating INSERT and UPDATE:
- Attempt UPDATE
- If no rows affected → INSERT
This ensures:
- simpler calling code
- correct handling of new players
- idempotent operations
Some modules store multiple values in a single column using CSV.
Tradeoff:
- simpler queries
- less normalized schema
Chosen due to limited relational complexity needs.
Credits stored as:
NUMERIC(15,3)
Avoids floating-point rounding issues common in game economies.
- Simplicity over full normalization (e.g., CSV storage)
- Local caching instead of distributed caching (single-server assumption)
- Explicit SQL instead of ORM for control and predictability
- Auto-commit transactions instead of manual transaction control
Problem:
Multiple plugins required shared access to player data without tight coupling.
Solution:
Introduced DataKey registry pattern:
- plugins declare schemas
- receive typed access objects
- remain independent but consistent
Problem:
Frequent reads (e.g., credits during PvP) caused excessive DB load.
Solution:
Added module-specific TTL caching:
- reduced redundant queries
- maintained acceptable staleness
Problem:
Schema changes risk breaking existing deployments.
Solution:
- used
CREATE TABLE IF NOT EXISTS - avoided destructive migrations
- allowed incremental schema updates
-
Plugin requests credits:
Credits credits = dataCenter.get(CreditsKey.INSTANCE, playerUUID); -
Cache is checked
-
On miss → database query executed
-
Plugin updates value:
credits.add(100.0); -
Upsert operation persists data
-
Cache updated
- Designed for single-server architecture (no multi-server sync)
- No distributed cache coherence
- No explicit transaction boundaries beyond single queries
- Schema migrations are manual
- No concurrency control beyond server-thread model
This is a library plugin.
Example:
DataCenter dataCenter = SolarDataCenter.ins.getDataCenter();
Credits credits = dataCenter.get(CreditsKey.INSTANCE, playerUuid);
- Java 8
- Spigot API (1.8.8)
- MariaDB
- HikariCP
- YAML configuration
- Maven
Used in a live multiplayer server environment with frequent player-driven data updates.
The system prioritizes:
- consistency
- simplicity
- performance
over feature completeness.
- Persist schema metadata and detect missing columns on startup
- Safely add new columns (
ALTER TABLE ADD COLUMN) without breaking existing data - Replace all raw SQL with prepared statements
- Centralize query construction to avoid unsafe dynamic SQL
- Improve cache handling (configurable TTL, better invalidation)
- Add basic transaction support for multi-step operations
- Improve error handling and logging