Wenn es um die Verwendung von Hibernate geht, nutzen die meisten Projekte nur die grundlegenden Funktionen. Dies ist meist auf Naivität oder Unkenntnis des Produkts zurückzuführen. Hibernate ist ein sehr ausgereiftes und funktionsreiches Produkt, mit dem sich viele grundlegende oder fortgeschrittene Probleme lösen lassen. Ich denke, es geht hier darum, die Komplexität auf das richtige Maß zu bringen: Sie können immer einen sehr einfachen Ansatz für die Verwendung von Hibernate wählen und die Diskrepanz zwischen Ihrem Mapping und Ihrem Modell in Ihrem Modell lösen, aber das würde zu einer Komplexität in Ihrem Modell führen, die im Grunde genommen Kesselsteincode ist. Sie ist nur vorhanden, weil die Persistenzschicht nicht korrekt verwendet wird. Wenn Sie die Persistenzschicht (in diesem Fall das Hibernate-Mapping) komplexer machen, wird sie schwieriger zu pflegen sein, aber Ihr Modellcode wird prägnanter und leichter zu verstehen sein.
Projekt Kochbuch
Angenommen, Sie möchten eines Tages alle Ihre Rezepte in einem Online-Kochbuch verwalten. Bevor Sie sich an die Arbeit machen und Codezeilen schreiben, sollten Sie einen Schritt zurücktreten und sich fragen: "Was steht in diesem Kochbuch?". Ein Kochbuch ist im Grunde eine Sammlung von Rezepten, und einer der interessantesten Aspekte eines Rezepts ist, dass es Zutaten enthält. Nun, eigentlich würde ich sagen, dass Sie in einem Rezept eine bestimmte Menge einer Zutat verwenden. Bei der Beziehung zwischen Rezept und Zutat handelt es sich also nicht um eine normale Eltern-Kind- oder sogar eine Viele-zu-Viele-Beziehung. Es handelt sich um eine Beziehung mit einem Attribut: Das Attribut ist die Menge der Zutat, die Sie in einem Rezept verwenden.
Modell
Gut, dann können wir jetzt mit dem Programmieren beginnen! Da dies eine Webanwendung sein wird, wenden wir uns natürlich an Grails, um sicher zu sein, dass wir unser Kochbuch heute Abend verwenden können. Wir verwenden also Grails-1.0-RC-1. und suchen uns einen guten Platz in der Verzeichnisstruktur und geben ein
C:projectsgrails> grails create-app cookbook
Damit wird im Wesentlichen die Infrastrukturstruktur für die Grails-Anwendung erstellt. Unterhalb des Verzeichnisses "cookbook" werden eine Reihe von Verzeichnissen und Dateien erstellt. Jetzt wollen wir ein paar Domain-Klassen hinzufügen. Wir haben die folgenden Entitäten erkannt: Recipe, Ingredient und Unit. Die Entität Einheit drückt die Einheit aus, in der die Menge der in einem Rezept zu verwendenden Zutat ausgedrückt wird. Ein Beispiel wäre, dass Sie 500 Gramm Zucker in einem Apfelkuchenrezept verwenden. Die Einheit ist hier 'Gramm'. Fügen wir also diese Entitäten hinzu.
C:projectsgrailscookbook> grails create-domain-class Recipe
C:projectsgrailscookbook> grails create-domain-class Ingredient
C:projectsgrailscookbook> grails create-domain-class Unit
Dies sind sehr einfache Befehle, die im Grunde zwei Dateien erstellen: eine im Quellverzeichnis und eine im Testverzeichnis. Lassen Sie uns die Dateien Ingredient.groovy und Unit.groovy bearbeiten, um die einfachen Eigenschaften hinzuzufügen, die diese Entitäten haben.
Ingredient.groovy:
Klasse Ingredient {
String-Name
}
Einheit.groovy
Klasse Unit {
String-Name
}
Das ist alles, was wir für den Moment brauchen. Die Entität Rezept wird ein wenig spannender sein. Wir erinnern uns an die kurze Diskussion darüber, was ein Rezept ist. Es enthält Zutaten, aber es möchte auch die Menge jeder Zutat wissen. Es wird also eine Karte der Zutaten erstellt, deren Schlüssel eine Zutat ist und deren Wert die Menge der Zutat ist, die Sie im Rezept verwenden. Die Menge ist ein Wertobjekt (Komponente, einbettbar), da es sich nicht um eine Entität, sondern um eine Eigenschaft der Beziehung zwischen Rezept und Zutat handelt. Die Komponente amount hat einen Verweis auf die Entität Unit. Um Grails mitzuteilen, dass die Klasse Amount keine Entität ist, legen wir die Datei Amount.groovy in das Verzeichnis src/groovy. Recipe.groovy
Klasse Recipe {
String-Name
Karte Zutaten
}
Betrag.groovy (in src/groovy)
Klasse Betrag {
int-Wert
Einheit Einheit
}
Erstellung von Controllern und Ansichten
Jetzt kommt also die ganze Kraft von Grails zum Tragen. Es ist an der Zeit, die Controller und Views für diese Entitäten zu erstellen.
C:projectsgrailscookbook> grails generate-all Recipe
C:projectsgrailscookbook> grails generate-all Ingredient
C:projectsgrailscookbook> grails generate-all Unit
Danach starten wir den Grails-Container und sehen uns die Anwendung an. Die Hauptseite wird angezeigt und obwohl Sie diese später ändern möchten, reicht sie für dieses Beispiel aus. Wenn Sie sich den Ingredients- und den Unit-Controller ansehen, dann funktionieren sie einfach von Anfang an. Das ist auch besser so, denn sie sind im Grunde das Einfachste, was möglich ist: ein Ding mit einem Namen. Mit diesen Controllern können Sie die Zutaten und Einheiten im System hinzufügen, auflisten, aktualisieren und entfernen.
Nun wenden wir uns dem Recipe-Controller zu. Der Recipe-Controller ist für die Aufgaben, die wir mit ihm erledigen möchten, nicht geeignet. Wenn Sie die Ansicht "Erstellen" oder "Bearbeiten" öffnen, können Sie zwar die Eigenschaft "Name" bearbeiten, aber die Eigenschaft "Zutaten" ist nicht editierbar. Die Ansicht show zeigt die Map an, allerdings als einfache Eigenschaft und nicht als Sammlung.
Ändern der Zuordnung
Das ist nicht das, was wir wollen. Wir möchten Zutaten für ein Rezept hinzufügen, auflisten, aktualisieren und entfernen. Jetzt müssen wir die Ansicht und den Controller für Recipe ändern, aber ich bin an dieser Stelle etwas misstrauisch, was das Datenmodell angeht. Sehen wir uns also das Schema an, das Hibernate generiert hat.
Alle Tabellen sind nicht miteinander verbunden! Es gibt keine Fremdschlüssel im System. Der Grund dafür ist, dass Hibernate standardmäßig die Schlüssel und Werte in der Map als Einzelwerte speichert. Sowohl die IDX- als auch die ELT-Spalten in RECIPE_INGREDIENTS haben den Typ VARCHAR. Bevor wir die Benutzeroberfläche reparieren, sollten wir versuchen, dieses Problem zu lösen. Ein Ansatz, der vielleicht einfach aussieht, ist, die ganze Map zu vergessen und ein Set einer Entitäts- oder Komponentenklasse zu verwenden, das einen Verweis auf die Zutat und eine Menge enthält. Aber was würde diese Klasse darstellen? Es wäre eine Art IngredientUse oder etwas Ähnliches, das die Verwendung einer Zutat in einem Rezept darstellt. Für mich ist das nicht ganz richtig. In der Domäne gibt es normalerweise so etwas wie IngredientUse nicht. Wir verwenden lediglich eine bestimmte Menge einer Zutat in einem Rezept. Um dieses Modell beizubehalten, müssen wir also die Persistenzschicht ein wenig intelligenter gestalten. Durch das Hinzufügen von 2 Standard-Hibernate-Konfigurationsdateien in das Verzeichnis grails-app/conf/hibernate wird genau das erreicht. hibernate.cfg.xml
Rezept.hbm.xml
Außerdem müssen wir der Klasse Recipe eine id-Eigenschaft (und optional eine Versions-Eigenschaft für optimistisches Locking) hinzufügen. Wenn wir nun den Server neu starten und uns das neu erstellte Datenbankschema ansehen, sieht es wie die folgende Abbildung aus, die nun wie erwartet die Fremdschlüssel und einige besser lesbare Spaltennamen enthält.
Ändern des Controllers
Um die Karte nun über die Webanwendung bearbeiten zu können, müssen wir dem RecipeController einige Methoden hinzufügen. Die Methoden ändern den Inhalt der Zutatenkarte in einem bestimmten Rezept.
def addIngredient = {
doWith( params.id, params.ingredientId ) { Recipe recipe, Ingredient ingredient ->
def unit = Unit.get( params.unitId )
def Betrag = new Betrag(Wert : Integer.parseInt(params.Betrag), Einheit : Einheit)
recipe.ingredients.put(Zutat, Menge)
}
}
def updateIngredient = {
doWith( params.id, params.ingredientId ) { Recipe recipe, Ingredient ingredient ->
recipe.ingredients.get(ingredient).value = Integer.parseInt(params.amount)
}
}
def deleteIngredient = {
doWith( params.id, params.ingredientId ) { Recipe recipe, Ingredient ingredient ->
recipe.ingredients.remove(Zutat)
}
}
private doWith (recipeId, ingredientId, cl) {
def recipe = Recipe.get( recipeId )
wenn (Rezept) {
def ingredient = Ingredient.get( ingredientId )
wenn (Zutat) {
cl.call(rezept, zutat)
redirect(action:show,id:recipe.id)
}
sonst {
flash.message = "Zutat mit Id $ nicht gefunden{ingredientId}"
redirect(action:edit,id:recipeId)
}
}
sonst {
flash.message = "Rezept nicht gefunden mit id ${recipeId}"
redirect(action:edit,id:recipeId)
}
}
Die Controller-Methoden sind ziemlich einfach: Sie holen die erforderlichen Entitäten, führen eine Validierung durch und bearbeiten schließlich die Zutatenkarte. Die Änderungen werden mithilfe von Hibernate zum Zeitpunkt der Übergabe transparent gespeichert.
Ändern Sie die Ansicht
Was das Modell und die Hibernate-Zuordnung betrifft, könnten wir hier aufhören, aber wir werden den Ansichtsteil der Anwendung nur zum Spaß fertigstellen!!! In den Dateien edit.gsp, create.gsp, list.gsp und show.gsp für das Rezept entfernen wir die Elemente, die die Eigenschaften der Zutaten anzeigen, da die Darstellung nicht sehr nützlich ist. Es gibt mehrere Stellen, die wir nutzen könnten, um eine Benutzeroberfläche zur Manipulation der Karte bereitzustellen. Ich habe mich dafür entschieden, die Komponenten in die Anzeigeansicht des Rezepts einzufügen, was im Wesentlichen zu einer Anzeigeansicht des Rezepts und einer Bearbeitungsansicht für die Zutatenliste führt. Man könnte die Komponenten auch in die Bearbeitungsansicht verschieben, aber das würde zu einer Ansicht mit vielen Schaltflächen führen, mit denen die verschiedenen Elemente bearbeitet werden können. Wie auch immer, mit diesem Modell- und Controller-Entwurf wäre jede Art von Benutzeroberfläche möglich. Fragment von show.gsp
Zutaten:
${Eintrag.Wert.Einheit.Name} von ${Eintrag.Schlüssel.Name} von
Die Tags <g:actionSubmit> werden zu Schaltflächen, die die Methoden updateIngredient, deleteIngredient und addIngredient auf dem RecipeController auslösen. Auf diese Weise kann der Benutzer die Zutatenkarte für das Rezept manipulieren.
Mit dieser Anwendung können Sie jetzt ganz einfach eine Sammlung von Rezepten mit Zutaten verwalten. Sie finden alle Quellen dieser Anwendung in der angehängten Zip-Datei.
Fazit
Wenn Sie eine Diskrepanz zwischen einem einfachen Hibernate-Mapping und einem ausdrucksstarken Modell feststellen, versuchen Sie, dem Modell treu zu bleiben. Wenn Sie ein etwas komplexeres Mapping verwenden, bleibt das Modell einfach, ausdrucksstark und leicht zu verwenden. In manchen Fällen kann eine Map verwendet werden, um eine komplexe Assoziation zwischen Entitäten auszudrücken, die über Eigenschaften verfügt. Dabei haben wir gesehen, dass es einfach ist und Spaß macht, Webanwendungen in Grails zu entwickeln. Hibernate-Mappings in XML-Dateien können zu Grails hinzugefügt werden, um die Mapping-Funktionen, die Grails von Haus aus bietet, zu erweitern.
Verfasst von
Maarten Winkels
Contact