Skip to content

Quick Start

Petrus Pradella edited this page Jun 18, 2026 · 4 revisions

Quick Start

What this page covers: the minimal end-to-end path — define an entity, describe it once, open a backend, save / find / count, and close — followed by a one-line swap to a completely different database.

📌 Note — already added the dependency? Good. If not, do Installation first.


The whole thing, top to bottom

import br.com.finalcraft.everydatabase.*;
import br.com.finalcraft.everydatabase.codec.JacksonJsonCodec;
import br.com.finalcraft.everydatabase.modules.sql.SqlConfig;

import java.util.Optional;
import java.util.UUID;

// 1. A plain entity — no-arg constructor + accessors so Jackson can (de)serialise it.
public class PlayerData {
    private UUID uuid;
    private String name;
    private int score;

    public PlayerData() {}                                  // Jackson needs this
    public PlayerData(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 omitted for brevity
}

// 2. Describe it once. Note the order: KEY type FIRST, ENTITY type second.
EntityDescriptor<UUID, PlayerData> PLAYERS = EntityDescriptor.builder(UUID.class, PlayerData.class)
        .collection("players")
        .keyExtractor(PlayerData::getUuid)
        .codec(new JacksonJsonCodec<>(PlayerData.class))
        .build();

// 3. Pick a backend and open it.
Storage storage = Storages.createSQL(
        new SqlConfig("jdbc:mariadb://localhost:3306/mydb", "root", "root"));
storage.init().join();

// 4. Get a typed repository and use it.
Repository<UUID, PlayerData> repo = storage.repository(PLAYERS);

UUID aliceId = UUID.randomUUID();
repo.save(new PlayerData(aliceId, "Alice", 100)).join();   // upsert

Optional<PlayerData> alice = repo.find(aliceId).join();    // -> Optional[Alice]
long total = repo.count().join();                          // -> 1

// 5. Release the pool when you're done.
storage.close().join();

That's the entire lifecycle: describe → open → use → close. Everything between init() and close() is backend-agnostic.

⚠️ GotchaEntityDescriptor.builder(KeyType.class, EntityType.class) takes the key type first. Both arguments are Class<?>, so the compiler won't catch a flip — builder(PlayerData.class, UUID.class) compiles and then fails confusingly at runtime. Key first, entity second.


Why .join() everywhere?

Every I/O call returns a CompletableFuture — there are no blocking variants. .join() simply waits for the result, which keeps these examples short. In real code you usually compose instead of blocking:

repo.find(aliceId)
    .thenApply(opt -> opt.map(PlayerData::getScore).orElse(0))
    .thenAccept(score -> System.out.println("score = " + score));

Errors surface as the exceptional completion of the future (as the cause of a CompletionException when you .join()). The full async model — composition, exception handling, the executor — is on The Async API.


Swapping the backend is one line

Everything from storage.repository(PLAYERS) downward is identical regardless of engine. To move the same code onto MongoDB, change only how you open the storage:

import br.com.finalcraft.everydatabase.modules.mongo.MongoConfig;

Storage storage = Storages.createMongo(new MongoConfig("mongodb://localhost:27017", "mydb"));
storage.init().join();
// ...repository(PLAYERS), save, find, count — all unchanged

Or an embedded H2 file, or pure in-memory for a test:

Storage h2  = Storages.createH2(new SqlConfig("jdbc:h2:file:./data/storage", "", ""));
Storage mem = Storages.createInMemory();

⚠️ Gotcha — the typed factories (createSQL, createPostgreSQL, createH2, createMongo, createLocalFile, createInMemory) return the concrete storage type, so capability interfaces are reachable without a cast. The generic Storages.create(StorageConfig) dispatches by config type but always picks the MySQL/MariaDB dialect for any SqlConfig — use createPostgreSQL / createH2 explicitly when you need those dialects.

See Choosing a Backend for the capability matrix and per-backend setup, and the per-engine config (SqlConfig, MongoConfig, LocalFileConfig) on each backend's page.


What you just used

Type Role
EntityDescriptor<K, V> immutable metadata: collection, key extractor, codec, indexes, versioning
Storages static factory — typed create* builders + generic create(StorageConfig)
Storage lifecycle (init / close / health) + a factory for repositories
Repository<K, V> typed CRUD for one collection; every method returns a CompletableFuture
JacksonJsonCodec<V> the serialization strategy attached to the descriptor

Next steps: dig into the descriptor on Defining Entities, the full CRUD surface on CRUD Operations, and declarative indexes on Indexing & Queries.


See also

  • Defining EntitiesEntityDescriptor.builder, collection rules, valid keys, codecs.
  • The Async API — the CompletableFuture model, composition, exceptional completion.
  • CRUD Operationsfind / findMany / save / saveAll / delete / exists / count / all.
  • Choosing a Backend — the capability matrix and per-backend instantiation.
  • Installation — coordinates and flavor choice.

Clone this wiki locally