JExcellence
JExcellence
Full-Stack · Oldenburg
BACKEND · 11 MIN LESEZEIT

JEHibernate 4: Hibernate 7 ohne Boilerplate, von Paper bis Spring Boot

Version 4 macht aus meiner Plugin-Bibliothek ein modulares Persistenz-Toolkit: HikariCP, Flyway, Multi-Tenancy, Envers-Audit, Tests gegen echte Datenbanken und ein Spring-Boot-Starter. Was neu ist und was beim Upgrade bricht.
Justin Eiletz26. April 2026 · Aktualisiert 8. Oktober 2026

JEHibernate begann als kleiner Wrapper, damit ich in Minecraft-Plugins nicht jedes Mal dieselben 200 Zeilen Hibernate-Setup schreiben musste. Mit Version 4 ist daraus eine Bibliothek geworden, die ich genauso in Spring-Boot-Diensten einsetze: vier Module, Hibernate 7.1, Connection-Pool, Migrationen, Multi-Tenancy und Audit, veröffentlicht auf Maven Central.

Dieser Artikel ersetzt meinen Text vom April. Seitdem hat sich so viel geändert, dass ein Nachtrag nicht mehr gereicht hätte. Der Code liegt auf github.com/JExcellence/JEHibernate, aktuell ist Version 4.0.2.

Bis 3.x war JEHibernate ein einzelnes Artefakt, das Paper-Abhängigkeiten, Spring-Code und Testhilfen mitbrachte, ob man sie brauchte oder nicht. Version 4 trennt das sauber. Nur der Kern ist Pflicht, alles andere holt man sich dazu.

Modul
Wofür
Kern-API
jehibernate-core
Repositories, Abfragen, Pool, Migrationen, Transaktionen
JEHibernate
jehibernate-plugin
Konfiguration aus dem Datenordner eines Paper-Plugins
PluginPropertyLoader
jehibernate-spring-boot
Auto-Configuration, nutzt die DataSource von Spring
jehibernate.*
jehibernate-testing
JUnit 5, Testcontainers, Fixtures
@JEHibernateTest

Der Kern läuft ab Java 17. Läuft er auf Java 21 oder neuer, nutzt die asynchrone API automatisch virtuelle Threads, auf älteren JVMs fällt sie auf einen normalen Thread-Pool zurück.

Die gleichen Repositories laufen im Plugin, in Spring Boot und in einem normalen Java-Programm. Nur der Einstieg unterscheidet sich:

In Spring Boot reicht der Starter auf dem Classpath: Sobald eine DataSource existiert, registriert die Auto-Configuration einen JEHibernate-Bean. Wer selbst einen Bean definiert, überschreibt sie, und jehibernate.enabled=false schaltet sie ganz ab.

Entities erben von einer der drei Basisklassen LongIdEntity, UuidEntity oder StringIdEntity. Alle bringen createdAt, updatedAt, eine @Version-Spalte für Optimistic Locking und ein equals/hashCode mit, das auch vor dem ersten Speichern in einem HashSet funktioniert.

Für die meisten Abfragen braucht es weder SQL noch JPQL. Der Query-Builder deckt Filter, Sortierung, Seitenaufteilung und Fetch-Joins ab. Die Seite liefert Inhalt und Gesamtzahl aus derselben Session, damit beides zusammenpasst.

Der Pool ist jetzt HikariCP, standardmäßig mit maximal 10 und mindestens 2 offenen Verbindungen. Konfiguriert wird er über PoolConfig oder die Properties jehibernate.pool.*. Mit getPoolHealth() lässt sich jederzeit abfragen, wie viele Verbindungen aktiv sind und wie viele Threads gerade warten. Das ist der erste Wert, den ich ansehe, wenn ein Server unter Last langsam wird.

Flyway läuft jetzt, bevor Hibernate startet, standardmäßig aus classpath:db/migration. Liquibase ist als Alternative wählbar. Ohne Flyway auf dem Classpath passiert einfach nichts.

Drei Strategien sind eingebaut: eine Mandantenspalte pro Zeile (DISCRIMINATOR), ein Schema pro Mandant (SCHEMA) oder eine eigene Datenbank pro Mandant (DATABASE). Bei der Spalten-Variante wirft die Bibliothek einen Fehler, wenn eine Abfrage ohne gesetzten Mandanten läuft. So landen Daten nicht versehentlich beim falschen Kunden.

Mit enableAudit() und @Audited an einer Entity schreibt Hibernate Envers jede Änderung mit. Zu jeder Revision speichert JEHibernate, wer sie ausgelöst hat, und bei Multi-Tenancy den Mandanten. Wer der Nutzer ist, legen Sie einmal über AuditContext.setResolver(...) fest, etwa die Spieler-UUID oder den eingeloggten Nutzer. Voraussetzung ist hibernate-envers auf dem Classpath.

AbstractCachedRepository legt einen Caffeine-Cache vor das Repository, nach ID und nach einem frei wählbaren Schlüssel wie dem Spielernamen. Gleichzeitige Fehltreffer für denselben Schlüssel werden zu einer einzigen Datenbankabfrage zusammengefasst, und die Ablaufzeiten streuen leicht, damit nach einem Massen-Preload nicht alles in derselben Sekunde abläuft. Für gleichzeitige Änderungen wiederholt OptimisticLockRetry eine Operation mit wachsender Wartezeit:

H2 im Speicher ist schnell, verhält sich aber nicht wie PostgreSQL. Das Testing-Modul startet deshalb auf Wunsch echte Datenbanken per Testcontainers: PostgreSQL, MySQL, MariaDB und SQL Server. Die eigene CRUD-Suite der Bibliothek läuft gegen alle vier und gegen H2. Ist kein Docker vorhanden, werden die Container-Tests übersprungen statt rot.

Zwischen den Tests setzt DatabaseReset die Datenbank zurück, wahlweise per TRUNCATE, Neuanlage des Schemas oder Rollback der Transaktion.

Datenbankarbeit gehört nie auf den Main-Thread eines Minecraft-Servers. Die *Async-Methoden laufen deshalb auf einem eigenen Executor. Zurück auf den Main-Thread wechseln müssen Sie aber selbst, sobald Sie mit der Welt oder einem Spieler interagieren. JEHibernate kennt den Bukkit-Scheduler bewusst nicht, damit der Kern ohne Paper-Abhängigkeit auskommt.

  • Die Koordinate heißt jetzt de.jexcellence.hibernate:jehibernate-core statt JEHibernate.
  • ddl-auto ist standardmäßig validate. Ohne Migrationen vorübergehend update setzen.
  • connectionPool(min, max) konfiguriert jetzt HikariCP statt Agroal.
  • Specifications.equal heißt jetzt equalTo.
  • Paper-spezifisches Laden der Konfiguration liegt im eigenen Modul jehibernate-plugin.

Kein Soft Delete, keine Micrometer-Metriken und kein Open-Session-in-View. Audit gibt es nur über Envers, einen eigenen Event-Listener und automatisches Löschen alter Revisionen habe ich in den Architekturentscheidungen bewusst verschoben. Wer das braucht, baut es heute auf den vorhandenen Bausteinen.

Fragen oder ein Projekt, in dem Persistenz gerade bremst? Schreiben Sie mir, das ist genau die Art Arbeit, die ich gern übernehme.

INHALT
Hibernate
JPA
Java
Spring Boot
Paper
Open Source
Hat dieser Artikel etwas in deinem Projekt aufgeworfen? Lass uns 30 Minuten darüber sprechen — kostenfrei und unverbindlich.