Nexora is a lightweight, async-first SQL ORM for Bukkit/Spigot plugins with explicit relation includes, safe schema synchronization, and write-through managed entities.
- Annotation-based entity mapping
- Explicit relation includes, no lazy proxies
- Async DB access with Bukkit main-thread callback helpers
- L1 Caffeine cache with optional Redis L2 and pub/sub invalidation
- Safe schema synchronization on startup
- Managed entities with debounced write-through persistence
- HikariCP connection pooling
- Java 17
- Spigot/Paper API for Bukkit integration (compileOnly)
- MySQL/MariaDB or SQLite
GPL-3.0
./gradlew buildNexoraConfig config = NexoraConfig.builder()
.jdbcUrl("jdbc:mysql://localhost:3306/nexora")
.username("root")
.password("password")
.defaultCacheTtl(Duration.ofMinutes(5))
.cacheMaxSize(10_000)
.cacheMode(CacheMode.MEMORY_REDIS)
.redisHost("127.0.0.1")
.redisPort(6379)
.autoFlushDebounceMs(50)
.migrationMode(MigrationMode.APPLY_SAFE)
.databaseType(DatabaseType.AUTO)
.build();
NexoraContext context = NexoraContext.builder(this, config)
.register(Profile.class)
.register(Clan.class)
.build();
context.getRepository(Profile.class)
.get(playerUuid)
.with(ProfileRelations.CLAN)
.cache(Duration.ofMinutes(10))
.async()
.thenAcceptSync(this, profileOptional -> profileOptional.ifPresent(profile -> {
Bukkit.broadcastMessage("Loaded " + profile.getProfileId());
profile.setCoins(profile.getCoins() + 5); // debounced write-through
}));Entities extend ManagedEntity and call markDirty("column") in setters to enable debounced write-through persistence.
@Entity(table = "profiles")
public class Profile extends ManagedEntity<UUID> {
@PrimaryKey
@Column(name = "profile_id")
private UUID profileId;
@Column(name = "coins", nullable = false, defaultValue = "0")
private int coins;
public void setCoins(int coins) {
if (this.coins != coins) {
this.coins = coins;
markDirty("coins");
}
}
}Profile profile = context.getRepository(Profile.class).getNow(playerUuid).orElseThrow();
profile.setCoins(profile.getCoins() + 100);
profile.saveNowAsync();
profile.invalidateCache();
profile.detach();On startup, Nexora inspects entity metadata, compares it to the live schema, and applies safe changes only. Unsafe changes (drops, type changes, NOT NULL without default) are blocked.
Rename hints:
@RenamedFrom("old_column")
@Column(name = "new_column")
private String value;
@TableRenamedFrom("old_table")
@Entity(table = "new_table")
public class Profile { }docs/README.mddocs/CONFIG.mddocs/ENTITIES.mddocs/REPOSITORIES.mddocs/RELATIONS.mddocs/CACHING.mddocs/MIGRATIONS.mddocs/THREADING.md
See:
src/main/java/dev/eths/nexora/example/ExamplePlugin.javasrc/main/java/dev/eths/nexora/example/entities/Profile.javasrc/main/java/dev/eths/nexora/example/entities/Clan.java
@Override
public void onDisable() {
if (context != null) {
context.close();
}
}