Database (ORM)
AstralCore ships a lightweight annotation-based ORM on top of HikariCP. It auto-generates SQL for standard CRUD operations; no XML or query strings required.
Defining an Entity
Annotate a class with @Entity and mark fields with the ORM annotations:
import com.astralrealms.core.storage.annotation.*;
import com.astralrealms.core.model.Unique;
@Entity("players") // table name
public class MinecraftPlayer implements Unique {
@Id
private UUID uniqueId; // primary key
@Column("player_name")
private String name;
@Column("server_id")
@NonUpdatable // never included in UPDATE statements
private String serverId;
@CreatedAt
private long createdAt; // auto-set on INSERT
@UpdatedAt
private long updatedAt; // auto-set on INSERT and UPDATE
@Transient // not persisted
private transient boolean online;
// getters, constructors …
}
Annotation Reference
Annotation | Target | Description |
|---|
@Entity(value)
| Class | Marks a class as a persistent entity. value is the table name. |
@Id
| Field | Primary key column. Should be UUID. |
@Column(value)
| Field | Maps the field to a column name. Omit to use the field name as-is. |
@CreatedAt
| Field (long) | Automatically set to System.currentTimeMillis() on INSERT. |
@UpdatedAt
| Field (long) | Automatically set on INSERT and every UPDATE. |
@NonUpdatable
| Field | Excluded from generated UPDATE statements. |
@Transient
| Field | Excluded from all SQL (not stored). |
CrudRepository
CrudRepository<T extends Unique> provides auto-generated async SQL for the annotated entity:
CrudRepository<MinecraftPlayer> repo =
new CrudRepository<>(databaseService, MinecraftPlayer.class);
Basic CRUD
// Find by primary key
repo.findById(uuid).thenAccept(opt -> opt.ifPresent(this::cache));
// Find all rows
repo.findAll().thenAccept(players -> { });
// Insert
repo.insert(player);
// Update
repo.update(player);
// Upsert (insert or update)
repo.upsert(player);
// Delete
repo.delete(uuid);
// Existence check
repo.exists(uuid).thenAccept(exists -> { });
// Count
repo.count().thenAccept(n -> { });
Query by Column
// Find one by a specific column value
repo.findByColumn("player_name", "Steve")
.thenAccept(opt -> { });
// Find all matching
repo.findAllByColumn("server_id", "lobby-1")
.thenAccept(list -> { });
// Multiple column conditions (AND)
repo.findByColumns(Map.of("server_id", "lobby-1", "player_name", "Steve"))
.thenAccept(opt -> { });
// Delete by column
repo.deleteByColumn("server_id", "lobby-1");
Custom SQL
repo.findOne("SELECT * FROM players WHERE lower(player_name) = ?", List.of("steve"))
.thenAccept(opt -> { });
repo.findMany("SELECT * FROM players WHERE server_id = ? ORDER BY created_at", List.of("lobby-1"))
.thenAccept(list -> { });
Batch Operations
// Batch insert
repo.insertBatch(List.of(player1, player2, player3));
// Batch update
repo.updateBatch(players);
// Batch upsert
repo.upsertBatch(players);
Pageable pageable = Pageable.of(0, 20); // page 0, 20 rows per page
repo.findAll(pageable).thenAccept(page -> {
List<MinecraftPlayer> content = page.content();
int total = page.totalElements();
int pages = page.totalPages();
});
DatabaseService
DatabaseService provides the underlying connection pool and transaction support:
DatabaseService db = AstralCore.get().database();
// Async query returning a result
db.supply(connection -> {
PreparedStatement stmt = connection.prepareStatement("SELECT COUNT(*) FROM players");
ResultSet rs = stmt.executeQuery();
rs.next();
return rs.getInt(1);
}).thenAccept(count -> { });
// Async void operation
db.run(connection -> {
connection.createStatement().execute("TRUNCATE temp_data");
});
// Transaction — auto-rollback on exception
db.transaction(connection -> {
// multiple operations in one transaction
insertSomething(connection);
updateSomething(connection);
});
// Synchronous (blocking)
int count = db.runSync(connection -> {
// …
});
CachedRepository
CachedRepository<T> combines CrudRepository and CacheRepository into a two-tier read-through pattern:
public class PlayerRepository extends CachedRepository<MinecraftPlayer> {
public PlayerRepository(DatabaseService db, CacheService cache) {
super(db, cache, MinecraftPlayer.class, "player", Duration.ofMinutes(10));
}
}
On findById: checks Redis first; on cache miss, queries the database and caches the result automatically. On save: writes to the database and updates the cache. On delete: removes from both layers.
Supported Databases
Upsert behaviour is automatically adapted to the detected database engine:
Database | Upsert SQL |
|---|
MariaDB / MySQL | INSERT … ON DUPLICATE KEY UPDATE
|
PostgreSQL / H2 | INSERT … ON CONFLICT DO UPDATE
|
23 April 2026