-
Notifications
You must be signed in to change notification settings - Fork 1
Defining Entities
The EntityDescriptor is one piece of metadata telling a backend how to model your entity: its collection name, how to extract the key, how to (de)serialize it, and optionally its indexes and version field. Build it once and every backend understands your entity.
📌 Note — an
EntityDescriptoris immutable and backend-agnostic. You build one per entity type and hand the same descriptor to any storage. Repositories are cached per collection name, sostorage.repository(sameDescriptor)returns the same object.
import br.com.finalcraft.everydatabase.EntityDescriptor;
import br.com.finalcraft.everydatabase.codec.JacksonJsonCodec;
import java.util.UUID;
EntityDescriptor<UUID, PlayerEntity> PLAYER_DESCRIPTOR = EntityDescriptor.builder(UUID.class, PlayerEntity.class)
.collection("players")
.keyExtractor(PlayerEntity::getUuid)
.codec(new JacksonJsonCodec<>(PlayerEntity.class))
.build();Four required pieces: the types (in builder), the collection, the keyExtractor, and the
codec. build() validates them and fails fast if any is missing or invalid.
| Method | Required? | What it sets |
|---|---|---|
builder(keyType, type) |
✅ | the Class<K> key type and Class<V> entity type (key first) |
.collection(String) |
✅ | the logical collection/table/directory name (validated — see below) |
.keyExtractor(Function<V, K>) |
✅ | how to read an entity's key |
.codec(Codec<V>) |
✅ | the serialization strategy |
.index(IndexHint) |
optional | a manual secondary index (see Indexing) |
.version(getter, setter) / .versioned()
|
optional | optimistic locking (see Optimistic Locking) |
.build() |
✅ | validates, scans annotations, returns the immutable descriptor |
build() also scans the entity class for @Indexed and @OptimisticLock annotations and merges
them in — covered on Indexing & Queries and Optimistic Locking. This page focuses on the four
essentials.
The logical name of the table (SQL), collection (Mongo), or directory (local files), validated at
build() against:
^[a-zA-Z][a-zA-Z0-9_]*$
Start with a letter; the rest may be letters, digits, or underscores. No spaces, hyphens, dots, backticks, or quotes — the safe intersection across every backend, so a valid name never needs quoting or escaping.
.collection("players") // ✅
.collection("guild_members") // ✅
.collection("audit_log_2024") // ✅
.collection("guild-members") // ❌ hyphen -> IllegalStateException at build()
.collection("2fa") // ❌ leading digit
.collection("user.profile") // ❌ dotA bad name throws IllegalStateException from build() with a message telling you the rule.
A key is persisted two ways, and its type must satisfy both:
- by its
toString()— the SQL primary key, the Mongo unique index, the LocalFile filename; - by
equals/hashCode— the in-memory backend and the manager cache.
So a key type needs a stable, unique toString() of at most 255 characters and value-based
equals/hashCode. These qualify out of the box:
-
UUID,String,Long,Integer,Boolean - any
record(value-based by definition)
⚠️ Gotcha — an oversized key is rejected up front, never silently truncated. If a key'stoString()exceeds 255 characters,save/saveAllreturn a future that completes exceptionally withIllegalArgumentException— the key never reaches storage, so it can't be truncated into a collision.
💡 Tip — the
keyExtractorjust reads the key off an instance; it does not have to be a field. A compositerecordkey works fine:public record SettingKey(String scope, String name) {} // ... .keyExtractor(s -> new SettingKey(s.getScope(), s.getName()))The whole key contract (the 255-char limit, the dual persistence) is also detailed on Entities, Keys & Collections.
The codec turns your entity into bytes and back. With the default JacksonJsonCodec, your entity
must be Jackson-serializable (Jackson 2.x, com.fasterxml.jackson.*):
- a no-arg constructor (or an appropriate
@JsonCreator), and - accessors Jackson can use (getters/setters), or Jackson annotations describing the mapping.
public class PlayerEntity {
private UUID uuid;
private String name;
private int score;
public PlayerEntity() {} // <- Jackson needs this
public PlayerEntity(UUID uuid, String name, int score) {
this.uuid = uuid; this.name = name; this.score = score;
}
public UUID getUuid() { return uuid; }
public String getName() { return name; }
public int getScore() { return score; }
// setters...
}Lombok works well — @Data @NoArgsConstructor @AllArgsConstructor gives exactly the shape above.
(The test entities and manager examples are written this way.)
| Codec | Output | Use it for |
|---|---|---|
new JacksonJsonCodec<>(Type.class) |
compact JSON | the default — smallest payload, what a database wants |
JacksonJsonCodec.pretty(Type.class) |
indented JSON | human-readable file-backend output |
new JacksonYamlCodec<>(Type.class) |
YAML |
file backends only (LocalFile / GroupedFile) — eyeball-friendly .yml files |
⚠️ Gotcha — SQL, Mongo, and InMemory require a JSON codec (they parse/store the payload as native JSON). Only the file backends (LocalFile and GroupedFile) accept a non-JSON codec likeJacksonYamlCodec. Attaching a YAML codec to a SQL descriptor is rejected. The deciding signal isCodec.isJsonCodec(); the full story — content types, custom codecs — is on Codecs.
💡 Tip —
JacksonJsonCodec.pretty(...)on a file backend gives you indented JSON files you can read and diff by hand, without leaving JSON for YAML.
Because a descriptor is immutable and engine-neutral, the same PLAYER_DESCRIPTOR constant drives
every backend — that's what makes the one-line backend swap in Quick Start possible, and lets
Moving Data Between Backends copy a collection between engines by handing the transfer the same
descriptor.
Repository<UUID, PlayerData> onSql = sqlStorage.repository(PLAYER_DESCRIPTOR);
Repository<UUID, PlayerData> onMongo = mongoStorage.repository(PLAYER_DESCRIPTOR); // same descriptor💡 Tip — declare descriptors as
static finalconstants (often in oneDescriptorsholder class) and pass them around. Don't rebuild a descriptor per call; build once at startup.
- Quick Start — the descriptor in a full save/find/close flow.
- Entities, Keys & Collections — the deep dive on the key contract and collection regex.
-
Codecs —
Codec<V>SPI, JSON vs YAML,isJsonCodec(), custom codecs. -
Indexing & Queries —
@Indexedand manualIndexHinton the builder. -
Optimistic Locking —
@OptimisticLock,.versioned(),.version(getter, setter). - Choosing a Backend — which engines accept which codecs and capabilities.
EveryDatabase · Home · made by Petrus Pradella
Getting Started
Core Concepts
Working with Data
Backends
- Choosing a Backend
- MySQL & MariaDB
- PostgreSQL
- H2
- MongoDB
- Local Files
- Grouped Files
- In-Memory
- Benchmarks
Manager Module
- Caching & References
- Typed References (Ref)
- Caching Managers
- Cache Policies & Freshness
- Cross-Process Cache Sync
- Write-Back & Conflict Resolution
- Payload Schema Evolution
- One Entity, Many Databases
Operations
Advanced
Reference
Contributing