Vorwort
Dieser Artikel widmet sich der Anbindung einer MySQL Datenbank per Hibernate Framework in eine Java (Maven) Anwendung.
Maven sollte aus dem letzten Blog bereits bekannt sein. Ich ergänze eine weitere Abhängigkeit um die Hibernate und MySql Connector Bibliotheken.

Über die Hibernate Annotationen werden Entitäten markiert, deren Konfiguration auf die Datenbanktabellen durch das Hibernate-Framework übertragen und bei Start der Anwendung synchronisiert werden.
Ebenso ist Hibernate in der Lage umgekehrt aus der fertigen Datenbank entsprechende Datenbank-Entitäten zu erstellen. Da ich zu jedem Zeitpunkt jedoch die Kontrolle über das Aussehen meiner Entitäten behalten möchte, wählte ich den modelbasierten Ansatz.
Hibernate bedarf einer Konfigurations XML-Datei. Diese habe ich hibernate.cfg.xml genannt. Zudem sind die künftigen Modelle bereits durch den vorangehenden Blog in Verwendung. Diese muss ich nur noch um die entsprechenden Annotationen ergänzen.
Ablauf
- MySQL Datenbank erstellen
- Tabellen erstellen gemäß existierender Modelle
- Hibernate mit MySQL anbinden in Maven und Java
- Entitäten definieren, gemäß der Datenbanktabellen
Als Besonderheit soll nun auch der mehrsprachige Text berücksichtigt werden, jedoch beschränkt auf Deutsch und Englisch. Aus diesem Grund ergeben sich drei Datenbanktabellen, wie hier zu sehen:

Die Action Tabelle soll nur IDs beinhalten, die der ursprünglichen Indexzahl entsprechen (plus 1). Daher ist die ID in der späteren Entität nicht automatisch generiert. Das gleiche soll für die Sprachschlüssel in der Tabelle “Languages” gelten.
Typ beschreibt einen textuellen enumerations Wert der Werte:
- CHILD_1
- CHILD_2
- MOM
- FRIEND
- PET
- TOY
Languages geht mit seiner primär ID als Schlüssel eine Bindung ein, wodurch das Kürzel der Sprache, bestehend aus nur zwei Zeichen ein Unikat ergeben soll, und zwar für die Kürzel:
- ID=1 ~ DE (Deutsch)
- ID=2 ~ EN (Englisch)
Lediglich die IDs der sprachspezifischen Texte von LocalizedTexts ist generiert auf Entitätenebene in Java. Für MySQL selbst sind die IDs alle auto_increment (automatisch hochzählend).
Die Sprachschlüsselkürzel sollen dann einem entsprechenden Enumeration-Typ entsprechen und problemlos umgewandelt werden können.

Die Gesamtstruktur der Datenbank sieht entsprechend so aus:

Hier nun die Projektdateien, die Hibernate benötigt:

META-INF/persistence.xml enthält

resources/hibernate.cfg.xml enthält die Konfigurationen:

Hier werden auch alle mappings deklariert, die für Hibernate in Betracht kommen:
- ActionEntity
- ActionLocaleTextEntity
- LocaleNameEntity
Die property “hibernate.hbm2ddl.auto” spezifiziert mit “update”, dass es Hibernate erlaubt ist, die Konfiguration aus den Entitäten in Java auf die Datenbanktabellenkonfiguration zu spiegeln.
Entität “ActionEntity“

Die Hibernate Entitäten erkennt man an der entsprechenden Annotation @Entity.
Die übrigen Annotationen, die die Felder / Spalten der Datenbanktabelle entsprechen können an den Klassen-Feldern oder an dessen Gettern angebracht werden.
Jede @Entity benötigt eine @Id Annotation an einem Feld, welches den Primärschlüsselwert der Tabelle zugeordnet ist. Die Annotation @Table(name = „Tabellenname“) sollte der Tabelle entsprechen, in der die jeweiligen Entitäten Informationen zugrunde liegen oder liegen sollen.
Die @Column(name = „Spaltenname“)-Annotation beinhaltet den Feldnamen in der entsprechenden Tabelle. IntellJ bietet die Möglichkeit entsprechende Namensvorschläge zu machen, was den Fortschritt der Implementierung erheblich beschleunigt. Soweit der Feldname bereits der Spalte in der Tabelle entspricht, ist diese @Column-Annotation nicht erforderlich. Dennoch empfehle ich, diese feste Benennung in der Annotation zu hinterlegen. Denn wenn es zum Refractoring kommt, welches den Feldnamen verändert, würde es zu unerwarteten Fehlern kommen oder ggf. sogar neuen Feldern in der Datenbanktabelle, was sicherlich unerwünscht ist.
Die @Enumerated-Annotation erlaubt das verwenden von enum-Typen an einem eigentlichen Textfeld der Tabelle.
Die ActionEntity soll mehrere Texte in verschiedenen Sprachen aus der Datenbank lesen. Demnach ist eine @OneToMany-Annotation erforderlich. Die Angabe „Eager“ erlaubt die Daten in den Listenelementen bereits auszulesen, wenn ActionEntity zurückgegeben wird. Die Standardeinstellung ist „Lazy“. Während der Testphase war es nicht möglich die Listeneinträge zum passenden Moment abzurufen, da diese noch nicht geladen waren im „Lazy“-Modus. Über die @JoinColumn-Annotation ist nur noch erforderlich die Fremdschlüsselspalte zu benennen, die benötigt wird, um jeden einzelnen Listeeintrag zu identifizieren.
Zuletzt darf man bei @Entity-Modellen nicht den Parameterlosen Konstruktor zu hinterlegen. Dies ist erforderlich, da Hibernate im Stande sein muss das Objekt instanziieren zu können. Der Konstruktor muss public sein!
ActionLocaleTextEntity ist das Tabellenmodel, welches einen sprachspezifischen Text repräsentiert. Es sieht so aus:

Über die Id aus der Tabelle Languages wird per language_id_fk die in der Tabelle localized_action_texts hinterlegt ist. Weil immer nur 1 Sprache auf 1 sprachspezifischen Text passt, handelt es sich um eine 1 zu 1 Beziehung. Daher muss hier die @OneToOne-Annotation verwendet werden. Wie der Kommentar an localeName schon erwähnt, soll durch das Löschen des Textes keine Kaskadierung für den Löschbefehl erfolgen. Aus diesem Grund werden nur Persist-Befehl (anlegen) und Merge-Befehl (update) weitergegeben an die Tabelle für die Sprachen in der Entität „LocaleNameEntity“.
LocaleNameEntity enthält lediglich ein 2-Stelligen-Sprachkürzel und ist ebenfalls ein Tabellenmodel. Es sieht so aus:

Die Besonderheit dieser Tabelle ist, dass sowohl das Feld / die Spalte „name“ als auch „locale_id“ eindeutig sein müssen, sodass die Tabelle nicht mit redundanten identischen Sprachkürzeln zugemüllt wird. Hierfür wird daher der uniqueConstraints Parameter verwendet, als auch @Column( … , unique = true) für das Feld “localeName“.
Die besondere Herausforderung hierbei war es zu erreichen, dass die Tabelle letztlich nur 2 Sprachkürzel aufweist mit den festen Ids 1 und 2. Daher wurde hier auf die @GeneratedValue-Annotation verzichtet. Gleiches findet man auch für das ActionEntity-Model.
Sobald nun die Spring Application gestartet wird, werden aufgrund der Referenzen und Entitäteneinstellungen entsprechende Datenbankänderungen durchgeführt, soweit notwendig. Es werden Foreign-Keys erstellt, wie auch alles andere, was nötig ist, um die Entitäten abzubilden.
Doch die Datenbank ist noch nicht zugänglich. Es fehlt das Database-Access-Object (DAO), “ActionDao”.
Die Datenbankschicht wird durch das DAO spezifiziert. Es ist die Maske, die bestimmt, welche Informationen aus und in die Datenbank transferiert werden dürfen.
Ich benötige zwei Informationen aus der Datenbank für den ActionServiceHandler:
- Gibt es eine Action für eine angefragte ID?
- Und liefere mir die Action für eine Action ID
Zudem entspricht das Modell für den @RestController nicht dem genauen Model der ActionEntity, wie sie in der Datenbank existiert. Es müssen zusätzliche Daten für den @RestController hinzugefügt werden. Wie die ID (UUID), die vom Service mitgegeben wurde und ob es eine weitere Action hinter der aktuell-angefragten Action ID gibt. Das ist zu ergänzen ist nicht Teil der Aufgaben des Dao. Das ist Business-Logik.

Der ActionServiceHandler arbeitet nun mit der Implementierung des Dao. Wie dessen API aussehen soll, bestimmt das Dao-Interface „IActionDao“. Die konkrete Implementierung hinter diesem Interface ist allerdings verborgen und wird per Spring und Dependency Injection verwendet.

Das IActionDao-Interface und die Implementierung:

Für die Datenbank und den späteren Verlauf ist notwendig, dass all die Aktionen bereits hinterlegt sind, wenn die erste Aktion versucht wird abgerufen zu werden.
Meine Bedingung für die initiale Datenerstellung
Konstruktoraufrufe sollen kurz und ohne komplexe Logik sein.
Darum möchte ich den Initial Load (Initiale Ladevorgang) der Daten Datenbank nicht während des Konstruktoraufrufs erfolgen.
Das Spring-Framework bietet mit der @PostConstruct-Annotation für Methoden direkt eine Lösung. Eine solche annotierte Methode wird aufgerufen wird, sobald die Instanz der Bean (@Component-Klasse) erstellt wurde.
Diese nutze ich, um die initialen Daten zu erzeugen. Doch ich muss prüfen, ob die initialen Daten bereits in der Datenbank existieren. Um es einfach zu halten gehe ich davon aus, dass der ersten Eintrag existieren muss. Auch möchte ich nicht, dass die Erstellung der Daten im Dao abgelegt ist. An dieser Stelle entschied ich mich für das Command-Design-Pattern und lagerte die Logik aus.

Das Command-Design-Pattern lagert eine Teilaufgabe aus. Ein Command beinhaltet eine Prüfung (required), ob die Aufgabe erfüllt werden muss und die tatsächliche Ausführung (execute).
Hierbei unterstützt die Auslagerung die Lesbarkeit und das Verständnis des Codes. Wir sehen, was getan wird, ohne durch die Komplexität der eigentlichen Aufgabe abgelenkt zu werden, die im Command steht.
Die Datenbank befüllen im Command

Die required-Implementierung hier verwendet hier Objects.isNull(..), da es eine besser Lesbarkeit erreicht als session.get(…) == null.
Es gibt hier eine Hilfsmethode, die die Lesbarkeit der Erstellung der Entitäten kürzer darstellen sollen:

Execute demonstriert, wie mit der Erstellung der Datenbankeinträge vorgegangen werden soll. Zunächst alle Entitäten bereinigen, dann die Sprachentitäten erstellen und dann die Aktionen und Texte persistieren.

Bei der Schreibweise mit doppelten Doppelpunkten „::“ handelt es sich in Java um die Methodgroup-Schreibweise einer Lambda-Funktion. Eine Lambda-Funktion ist eine Kurzschreibweise einer ausführlichen regulären Funktion oder Methode. Lambda-Funktionen gibt es in C# ebenfalls ab .Net 4 und in Java ab Version 7.

AbstractHibernateDao:

Hier ist zu erkennen, dass die Session-Aufgaben, wie „get(ActionEntity.class, indexId)“ in einem try-finally-Block aufgerufen wird. Damit stelle ich sicher, dass die Sitzung immer geschlossen wird, egal ob ein Fehler auftritt oder nicht.
Randinfo
Die Id einer ActionEntity, die in Blog 1 noch als Index-Nummer Nullbasierend war, ist nun Einsbasierend (Index + 1).

Die Grund-Funktionalitäten zur Session wurden in eine Elternklasse ausgelagert:


So werden nun die Daten initial erstellt, sobald das erste Mal die erste ActionEntity nicht abgerufen werden kann und stehen zur Verfügung.
Zusammenfassung des Ablaufs
Der Client sendet eine WebAnfrage an den @RestController mit einem Index und einer beliebigen Indikator-ID für den Client, sowie das Sprachkürzel, in welchem die Texte sein sollen.


Der Client erhält am Ende die angefragte Aktion über den Rückgabewert oder er erhält einen Fehler. Der Service-Handler verwendet das ActionDao, um die Daten zu erhalten und erweitert diese um Logische Informationen, die der Client benötigt.
