Groovy als Web-Backend: Unterschied zwischen den Versionen

Aus Jonas Notizen Webseite
Zur Navigation springen Zur Suche springen
(Grails-Skripte und Grails-Befehle Kapitel geschrieben. "Gradle und Grails"-Seitentitel (inkl Link) mit TODO message eingefügt)
(Kleine Texte verbessert)
 
(32 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
 +
[[Datei:Grails logo.png|mini|Grails Logo]]'''Grails''' ist ein freies '''Webframework''' '''für''' die Programmiersprache '''Groovy'''. Grails bietet Konzepte wie Scaffolding, automatische Validatoren und Internationalisierung. '''Grails ist an Ruby on Rails angelehnt''' und baut auf mehreren etablierten Frameworks wie '''Spring''', '''Hibernate''' und SiteMesh auf und verbindet diese mit der Skriptsprache ''Groovy''.
 +
<br />
 
=Einführung=
 
=Einführung=
 
==Quellen==
 
==Quellen==
Zeile 10: Zeile 12:
 
<br />
 
<br />
 
==Über Grails==
 
==Über Grails==
[[Datei:Grails logo.png|mini|Grails Logo]]
+
[[wikipedia:Comparison_of_web_frameworks#Java_2|Für die Java-Technologie gibt es eine große Anzahl Web-Frameworks]]. Diese sind aber meist komplizierter als sie sein müssten, und folgen meist nicht dem '''[https://de.wikipedia.org/wiki/Don’t_repeat_yourself DRY-Prinzip]''' ("'''D'''on't '''R'''epeat '''y'''ourself").  
Für die Java-Technologie gibt es eine große Anzahl Web-Frameworks. Diese sind aber meist komplizierter als sie sein müssten, und folgen meist nicht dem '''[https://de.wikipedia.org/wiki/Don’t_repeat_yourself DRY-Prinzip]''' ("'''D'''on't '''R'''epeat '''y'''ourself").  
 
  
<br>Dynamische Frameworks wie [https://www.djangoproject.com/ Django (Python)] und [https://rubyonrails.org/ Rails (Ruby)] haben neue moderne Einblicke gesetzt, wie man über Web-Applikationen denken sollte.  
+
<br>Dynamische Frameworks wie [https://www.djangoproject.com/ Django (Python)] und [https://rubyonrails.org/ Rails (Ruby)] haben neue Moderne Einblicke gesetzt, wie man über Web-Applikationen denken sollte.  
  
'''Grails baut auf diesen Konzepten auf und reduziert die Komplexität beim Erstellen von Webanwendungen auf der Java-Plattform erheblich.''' ''Das Besondere daran ist jedoch, dass es auf bereits etablierten Java-Technologien wie Spring und Hibernate aufbaut.''
+
'''Grails baut auf dessen Konzepte auf und reduziert die Komplexität beim Erstellen von Webanwendungen auf der Java-Plattform erheblich.''' ''Das Besondere daran ist, dass es auf bereits etablierten Java-Technologien wie Spring und Hibernate aufbaut.''
  
 
Zu den hervor-zu-hebenden Features zählen:
 
Zu den hervor-zu-hebenden Features zählen:
  
*[http://gorm.grails.org/ GORM] - Eine einfach zu verwendende [https://de.wikipedia.org/wiki/Objektrelationale_Abbildung Objekt-Mapping]-Bibliothek für [http://gorm.grails.org/latest/hibernate SQL], [http://gorm.grails.org/latest/mongodb MongoDB], [http://gorm.grails.org/latest/neo4j/ Neo4j] und [http://gorm.grails.org/ andere].
+
*[http://gorm.grails.org/ GORM] - Eine einfach zu verwendende [https://de.wikipedia.org/wiki/Objektrelationale_Abbildung '''O'''bjekt-'''R'''elationale-'''M'''apping]-Bibliothek für [http://gorm.grails.org/latest/hibernate SQL], [http://gorm.grails.org/latest/mongodb MongoDB], [http://gorm.grails.org/latest/neo4j/ Neo4j] und [http://gorm.grails.org/ andere].
 
*View-Templating zum [https://gsp.grails.org/ rendern von HTML] sowie auch [http://views.grails.org/ JSON]
 
*View-Templating zum [https://gsp.grails.org/ rendern von HTML] sowie auch [http://views.grails.org/ JSON]
 
*Eine auf [http://www.spring.io/ Spring Boot] aufbauende Controller-Logik
 
*Eine auf [http://www.spring.io/ Spring Boot] aufbauende Controller-Logik
 
*Ein Plugin-System mit [http://plugins.grails.org/ hunderten von Plugins]
 
*Ein Plugin-System mit [http://plugins.grails.org/ hunderten von Plugins]
*Flexible "App-Profile" zur [http://start.grails.org/#/index einfachen Erstellung von Apps mit AngularJS/React/...-Integration] ''(soz. Starthilfen)''
+
*Flexible "App-Profile" zur [http://start.grails.org/#/index einfachen Erstellung von Apps mit AngularJS/React/...-Integration] ''(Starthilfen)''
 
*Eine interaktive Kommandozeilen-Anwendung (CLI) sowie einem Build-System auf [http://gradle.org/ Gradle]-Basis
 
*Eine interaktive Kommandozeilen-Anwendung (CLI) sowie einem Build-System auf [http://gradle.org/ Gradle]-Basis
*Einen eingebauten [http://tomcat.apache.org/ Apache Tomcat]-Container, der sogar während des laufenden Betriebes neugeladen werden kann
+
*Einen eingebauten [http://tomcat.apache.org/ Apache Tomcat]-Container, der sogar während des laufenden Betriebes neu-geladen werden kann
  
 
All dies wird durch die Leistungsfähigkeit der [http://groovy-lang.org/ Groovy]-Sprache und den umfassenden Einsatz [https://de.wikipedia.org/wiki/Domänenspezifische_Sprache domänenspezifischer  Sprachen] (DSLs) wesentlich vereinfacht.
 
All dies wird durch die Leistungsfähigkeit der [http://groovy-lang.org/ Groovy]-Sprache und den umfassenden Einsatz [https://de.wikipedia.org/wiki/Domänenspezifische_Sprache domänenspezifischer  Sprachen] (DSLs) wesentlich vereinfacht.
Zeile 34: Zeile 35:
  
 
===Installations-Anforderungen===
 
===Installations-Anforderungen===
''Für Grails 3 wird eine minimale JDK-Version von 1.8 gefordert [https://docs.grails.org/3.3.10/guide/single.html#requirements].''
+
<blockquote>[https://docs.grails.org/3.3.10/guide/single.html#requirements ''Für Grails 3 wird eine minimale JDK-Version von 1.8 gefordert'':] Wenn man JDK 1.7 verwenden möchte, müsste man Gradle mit Java 1.7.0_131-b31 oder höher ausführen, um die [https://blog.gradle.org/unable-to-download-maven-central-bintray Auflösung von Gradle-Abhängigkeiten zu beheben, wenn die Unterstützung von TLS v1.1 und v1.0 eingestellt wird].</blockquote>
 
 
*Wenn man JDK 1.7 verwenden möchte, müsste man Gradle mit Java 1.7.0_131-b31 oder höher ausführen, um die [https://blog.gradle.org/unable-to-download-maven-central-bintray Auflösung von Gradle-Abhängigkeiten zu beheben, wenn die Unterstützung von TLS v1.1 und v1.0 eingestellt wird].
 
  
<br />
 
 
===Automatische Grails-Installation mithilfe des SDKMAN's-Projekts===
 
===Automatische Grails-Installation mithilfe des SDKMAN's-Projekts===
<blockquote>SDKMAN! ist ein Werkzeug zur Verwaltung paralleler Versionen mehrerer Software Development Kits auf den meisten Unix-basierten Systemen.  
+
<blockquote>SDKMAN! ist ein Werkzeug zur Verwaltung paralleler Versionen mehrerer '''S'''oftware '''D'''evelopment '''K'''its (SDKs) auf den meisten Unix-basierten Systemen. Es bietet eine bequeme Befehlszeilenschnittstelle (CLI) und API zum Installieren, Wechseln, Entfernen und Auflisten von Kandidaten (SDKs) an.</blockquote>
 
 
Es bietet eine bequeme Befehlszeilenschnittstelle (CLI) und API zum Installieren, Wechseln, Entfernen und Auflisten von Kandidaten.</blockquote>
 
  
 
Nachdem man [https://sdkman.io/install SDKMAN! mithilfe der offiziellen Anleitung erfolgreich installiert hat] kann man mit dem folgenden Befehl Grails auf sein System installieren:<syntaxhighlight lang="bash">
 
Nachdem man [https://sdkman.io/install SDKMAN! mithilfe der offiziellen Anleitung erfolgreich installiert hat] kann man mit dem folgenden Befehl Grails auf sein System installieren:<syntaxhighlight lang="bash">
 
$ sdk install grails 3.3.9
 
$ sdk install grails 3.3.9
</syntaxhighlight>Falls dies die erste Installation von Grails mithilfe des SDKMAN! ist, wird diese Version direkt als Standard in allen Umgebungen gesetzt und auch direkt angewendet.<br />
 
==[http://docs.grails.org/3.3.9/guide/single.html#introduction#creatingAnApplication App erstellen]==
 
<code>$ grails create-app APP-NAME</code> erstellt im derzeitigen Verzeichnis die Grundlage für die neue App.
 
  
===Befehle ausführen===
+
Optional noch Java und Groovy SDK installieren, falls nicht schon getan:
Wenn man jetzt in den erstellten Projekt-Ordner wechselt ''(<code>$ cd APP-NAME</code>)'' gibt es 2 Möglichkeiten, Grails-Befehle auszuführen:
+
$ sdk install groovy 2.4.15
 +
$ sdk install java 8.0.252.j9-adpt
 +
</syntaxhighlight>Falls dies die erste Installation von Grails mithilfe des SDKMAN! ist, wird diese Version direkt als Standard in allen Umgebungen gesetzt und somit direkt angewendet.<br />
 +
==[http://docs.grails.org/3.3.9/guide/single.html#creatingAnApplication App erstellen]==
 +
<code>$ grails [http://docs.grails.org/3.3.9/ref/Command%20Line/create-app.html create-app] APP-NAME</code> erstellt im derzeitigen Verzeichnis ''innerhalb eines neuen Ordner mit dem angegebenen Namen'' das Grundgerüst für eine neue Grails-Anwendung.
  
*<code>$ grails BEFEHL</code>
+
''Das Standardmäßig genutzte [https://github.com/grails-profiles Profil] zur Erstellung des Grundgerüsts ist <code>web</code>.''
**Nachteil: Für jeden Befehl muss eine neue JVM-Instanz erstellt werden (= Bei jedem Befehl muss man eine gewisse Zeit extra warten)
 
**Nachteil: Keine Auto-Vervollständigung mithilfe der Tabulator-Taste
 
*Mithilfe von <code>$ grails</code> in die [https://docs.grails.org/3.3.9/guide/gettingStarted.html#usingInteractiveMode Interaktive-Konsole] wechseln, und dort seine Befehle ''(Ohne <code>grails</code> vorne dran)'' ausführen
 
**Vorteil: Es wird nur einmal die JVM gestartet / benötigte Projekt und Grails-Daten geladen (= Weniger Ladezeit)
 
**Vorteil: Auto-Vervollständigung mithilfe der Tabulator-Taste
 
  
 +
===Grails-Befehle ausführen===
 +
Wenn man jetzt in den erstellten Projekt-Ordner wechselt ''(<code>$ cd APP-NAME</code>)'' gibt es 2 Möglichkeiten, Grails-Befehle aufzurufen:
  
D.h.: Wenn man in einer Dokumentation <code>grails <<command>></code> kann man:
+
#<code>$ grails BEFEHL</code>
 +
#*Nachteil: Für jeden Befehl muss eine neue JVM-Instanz erstellt werden (= Bei jedem Befehl muss man eine gewisse Zeit extra warten)
 +
#*Nachteil: Keine Auto-Vervollständigung mithilfe der Tabulator-Taste
 +
#Mithilfe von <code>$ grails</code> in die [https://docs.grails.org/3.3.9/guide/gettingStarted.html#usingInteractiveMode Interaktive-Konsole] wechseln, und dort seine Befehle ''(Ohne <code>grails</code> vorne dran)'' ausführen
 +
#*Vorteil: Es wird nur einmal die JVM gestartet und die benötigten Projekt- und Grails-Daten geladen (= Weniger Ladezeit)
 +
#*Vorteil: Auto-Vervollständigung mithilfe der Tabulator-Taste
  
*Direkt <code>grails <<command>></code> in seiner Shell eingeben
+
 
*Zuerst mithilfe von <code>grails</code> in die Interaktive Grails-Shell eindringen und danach den <code><<command>></code> eingeben.
+
Das heißt: Wenn man in einer Dokumentation <code>grails <<command>></code> kann man:
 +
 
 +
#Direkt <code>grails <<command>></code> in seiner Shell eingeben
 +
#Zuerst mithilfe von <code>grails</code> in die Interaktive Grails-Shell eindringen und danach den <code><<command>></code> eingeben.
  
 
<br />
 
<br />
Zeile 71: Zeile 73:
  
 
===IntelliJ-IDEA===
 
===IntelliJ-IDEA===
IntelliJ IDEA ist eine '''ausgezeichnete''' IDE für die Entwicklung von Grails 3.0. Sie ist in 2 Editionen erhältlich:
+
IntelliJ IDEA in der "Ultimate"-Edition ist eine '''ausgezeichnete''' IDE für die Entwicklung von Grails (Sowie auch für viele andere (Web-)Frameworks, siehe [https://www.jetbrains.com/idea/features/editions_comparison_matrix.html offizieller Vergleichs-Tabelle "Ultimate vs Community"]).  
 
 
*der kostenlosen Community-Edition und
 
*der kostenpflichtigen "Ultimate" Edition. (Tipp: Es gibt auch kostenlose Studenten-Pakete)
 
  
Die Community Edition kann für die meisten Dinge verwendet werden, obwohl GSP-Syntax-Highlighting nur ein Teil der "Ultimate" Edition ist.
+
Ich kann es jedem nur empfehlen - ich könnte nicht-mehr ohne die Funktionen von IntelliJ IDEA Ultimate leben.
<br />
 
  
 
===TextMate, Sublime, VIM, ...===
 
===TextMate, Sublime, VIM, ...===
Zeile 83: Zeile 81:
 
<br />
 
<br />
 
==[http://docs.grails.org/3.3.9/guide/single.html#conventionOverConfiguration Konventionen über Konfiguration]==
 
==[http://docs.grails.org/3.3.9/guide/single.html#conventionOverConfiguration Konventionen über Konfiguration]==
"Konventionen über Konfiguration" bedeutet, '''dass der Ort/Name einer Datei zur Identifikation seiner Rolle/Eigenschaften genutzt wird'''.<br>'''Je nach Speicherort einer Klasse erhält diese spezifische Variablen/Methoden "injeziert"'''. (Beispiel: [https://docs.grails.org/latest/ref/Constraints/Usage.html constraints], [http://docs.grails.org/3.1.1/ref/Domain%20Classes/hasMany.html hasMany]).  
+
"Konventionen über Konfiguration" bedeutet, '''dass der Ort/Name einer Datei zur Identifikation seiner Rolle/Eigenschaften genutzt wird'''.<br>'''Je nach Speicherort einer Klasse erhält diese spezifische Variablen/Methoden/[http://openbook.rheinwerk-verlag.de/javainsel/13_001.html Beans]''' (durch die Verwendung von Dependency-Injections und Groovy Domänspezifischen-Sprachen (DSLs)) '''injeziert'''. (Beispiel: [https://docs.grails.org/latest/ref/Constraints/Usage.html constraints], [http://docs.grails.org/3.1.1/ref/Domain%20Classes/hasMany.html hasMany]).  
  
 
Da der Ort einer Klasse entscheidet, wofür/wie sie von Grails genutzt wird, sollte man sich mit dem Aufbau einer Grails-Applikation bekannt werden lassen:
 
Da der Ort einer Klasse entscheidet, wofür/wie sie von Grails genutzt wird, sollte man sich mit dem Aufbau einer Grails-Applikation bekannt werden lassen:
Zeile 131: Zeile 129:
  
 
*Standard-Port dieses Web-Servers ist <code>8080</code>, kann aber durch den Parameter <code>-port=[PORT]</code> abgeändert werden.
 
*Standard-Port dieses Web-Servers ist <code>8080</code>, kann aber durch den Parameter <code>-port=[PORT]</code> abgeändert werden.
*Unter welcher URL die App erreichbar ist wird in der Konsole angezeigt. ''(Standardmäßig http://localhost:8080/APP-NAME/<nowiki/> - kann aber auch durch die Laufzeit-Konfigurationsvariable <code>server.contextPath</code> geändert werden.)''
+
*Unter welcher URL die App erreichbar ist wird in der Konsole angezeigt. ''(Standardmäßig http://localhost:8080/APP-NAME/<nowiki/> - kann aber auch durch die Laufzeit-Konfigurationsvariable <code>server.contextPath</code> geändert werden.)<nowiki/>''
  
 
==App testen==
 
==App testen==
Zeile 139: Zeile 137:
  
 
==[https://docs.grails.org/3.3.9/guide/gettingStarted.html#deployingAnApplication App bereitstellen]==
 
==[https://docs.grails.org/3.3.9/guide/gettingStarted.html#deployingAnApplication App bereitstellen]==
Grail-Apps können auf mehrere Weisen bereitgestellt werden. <br>Wenn man einen Traditionellen Container ''(Wie Apache's Tomcat, Jetty, etc.)'' benutzt, kann man ganz einfach ein sog. "Web Application Archive" (<code>.war</code>-Datei) erstellen, welches alle App-Daten in eine Archiv-ähnliche Datei zusammenfasst. ''(Vergleichbar mit einem <code>.jar</code>-Archiv)''<br>
+
Grail-Apps können auf mehrere Weisen bereitgestellt werden. <br>Wenn man einen Traditionellen Container ''(Wie Apache's Tomcat, Jetty, etc.)'' benutzt, kann man ganz einfach ein sog. "Web Application Archive" (<code>.war</code>-Datei) erstellen, welches alle Anwendungsdaten in eine Archiv-ähnliche Datei zusammenfasst. ''(Vergleichbar mit einem <code>.jar</code>-Archiv)''<br>
  
 
===Grails und Tomcat===
 
===Grails und Tomcat===
Grails bindet standardmäßig einen Tomcat-8 Container mit jeder WAR-Datei ein.<br>
+
Grails bindet standardmäßig einen Tomcat-8 Container mit jeder WAR-Datei ein.<br>'''Da dies aber zu Problemen führen kann''', wenn der Server bereits einen aktiven Tomcat-Container mit einer anderen Version besitzt, kann man diese Abhängigkeit (Dependency) innerhalb der <code>build.gradle</code>-Datei von <code>compile</code> auf <code>provided</code> umstellen:  
'''Da dies aber zu Problemen führen kann''', wenn der Server bereits einen aktiven Tomcat-Container mit einer anderen Version besitzt, kann man diese Abhängigkeit (Dependency) innerhalb der <code>build.gradle</code>-Datei von <code>compile</code> auf <code>provided</code> umstellen:  
 
 
<syntaxhighlight lang="gradle">
 
<syntaxhighlight lang="gradle">
 
provided "org.springframework.boot:spring-boot-starter-tomcat"
 
provided "org.springframework.boot:spring-boot-starter-tomcat"
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Die Tomcat-Version kann man ebenfalls unter <code>build.gradle</code> innerhalb der <code>dependencies {}</code>-Sektion wie folgt auf zB. Tomcat-7 ändern:
+
Die Tomcat-Version kann man ebenfalls unter <code>build.gradle</code> innerhalb des <code>dependencies {}</code>-Abschnitt wie folgt auf zB. Tomcat-7 ändern:
 
<syntaxhighlight lang="gradle">
 
<syntaxhighlight lang="gradle">
 
ext['tomcat.version'] = '7.0.59'
 
ext['tomcat.version'] = '7.0.59'
Zeile 154: Zeile 151:
  
 
===WAR-Datei erstellen und starten===
 
===WAR-Datei erstellen und starten===
Mit dem Grails-Befehl <code>$ grails war</code> baut Grails die "<code>.war</code>-Version" seiner App im Verzeichnis <code>build/libs</code> des Projekts auf.
+
Mit dem Grails-Befehl <code>$ grails war</code> baut Grails die "<code>.war</code>-Version" der App und speichert das Resultat unter <code>build/libs</code>.
  
Mit dem Java-Befehl <code>java -Dgrails.env=prod -jar ['''DEINE-WAR-DATEI'''].war</code> kann man nun ganz einfach seine erstellte .war-Datei ausführen.<br>
+
Mit dem Java-Befehl <code>java -Dgrails.env=prod -jar ['''DEINE-WAR-DATEI'''].war</code> kann man nun ganz einfach seine erstellte .war-Datei ausführen. ''Beispiel: <code>java -Dgrails.env=prod -jar build/libs/mywar-0.1.war</code>''
Beispiel: <code>java -Dgrails.env=prod -jar build/libs/mywar-0.1.war</code>
 
  
 
===Unterstützte Java EE-Container===
 
===Unterstützte Java EE-Container===
Grails 3-3 läuft auf jeden Container der  die [https://de.wikipedia.org/wiki/Servlet Java Servlet API] in der Version 3.0 (oder darüber) unterstützt. Darunter gelten:
+
Grails 3.3.x läuft auf jeden Container der  die [https://de.wikipedia.org/wiki/Servlet Java Servlet API] in der Version 3.0 (oder darüber) unterstützt. Darunter gelten:
  
 
*Tomcat 7
 
*Tomcat 7
Zeile 168: Zeile 164:
 
*Jetty 8
 
*Jetty 8
 
*Oracle Weblogic 12c
 
*Oracle Weblogic 12c
*IBM WebSphere 8.0
+
*IBM WebSphere 8.0 ([http://docs.grails.org/3.3.9/guide/single.html#supportedJavaEEContainers *])
  
 
<br />
 
<br />
==Automatisches generieren von Artifakten für eine Domänen-Klasse (Scaffolding)==
+
==Automatisches generieren von Artefakten für eine Domänen-Klasse (Scaffolding)==
Mit [https://docs.grails.org/3.3.10/guide/scaffolding.html Scaffolding] kann man einige grundlegende [https://glossar.hs-augsburg.de/CRUD CRUD] (Create, Read, Update, Delete)-Schnittstellen für eine Domänen-Klasse generieren, darunter:
+
Um schnell mit Grails zu beginnen, ist es oft nützlich, ein Feature namens Scaffolding zu verwenden, um das Skelett einer Anwendung zu erzeugen. Hierfür gibt es diverse <code>generate-*</code>-Befehle wie z.B. <code>[http://docs.grails.org/latest/ref/Command%20Line/generate-all.html generate-all]</code>, das einen [http://docs.grails.org/latest/guide/theWebLayer.html#controllers Controller] (sowie dessen [http://docs.grails.org/3.3.9/guide/single.html#services Service]klasse und Unit-Tests) und die zugehörigen [https://docs.grails.org/3.3.10/guide/theWebLayer.html#gsp Views] erzeugt:
 
 
*Die benötigten [https://docs.grails.org/3.3.10/guide/theWebLayer.html#gsp Views] (.gsp)
 
*Die dazugehörigen [http://docs.grails.org/latest/guide/theWebLayer.html#controllers Controller]-Klassen für CRUD-Operationen
 
*Die dazugehörigen Unit-Tests
 
 
 
Alle oben genannten Skelette können mithilfe von <code>$ grails [http://docs.grails.org/latest/ref/Command%20Line/generate-all.html generate-all] (PACKET.)KLASSEN-NAME</code> ganz einfach erstellt werden.
 
 
 
 
 
  
  
Zeile 189: Zeile 177:
  
  
''Möchte man dann aber zB. seine eigene MySQL-Verbindung einrichten, kann man dies mithilfe simpler Konfigurationsdateien einstellen.''
+
''Möchte man dann aber z.B. seine eigene MySQL-Verbindung einrichten, kann man dies mithilfe simpler Konfigurationsdateien bewerkstelligen.''
  
 
==[https://docs.grails.org/3.3.9/guide/single.html#config Grundlegende Konfiguration]==
 
==[https://docs.grails.org/3.3.9/guide/single.html#config Grundlegende Konfiguration]==
Grails Konfigurations-Dateien sind in 2 Sektionen aufgeteilt:
+
Grails Konfigurationsdateien sind in 2 Sektionen aufgeteilt:
  
 
*Build (<code>build.gradle</code>)
 
*Build (<code>build.gradle</code>)
*Runtime (<code>grails-app/conf/application.yml</code>)
+
*Runtime (<code>grails-app/conf/application.yml</code> oder <code>application.groovy</code> und <code>runtime.groovy</code> wenn man den alten Groovy-Syntax/Groovy-Angehensweiße benutzen will)
  
 
===Standard-Variablen===
 
===Standard-Variablen===
Für die Konfigurations-Dateien stehen folgende Variablen zur Verfügung:
+
Für die Groovy-Konfigurationsdateien (<code>build.gradle</code>) stehen folgende Variablen zur Verfügung: (Eingebunden/Benutz werden sie wie in jedem GString: <code>${grailsHome}</code>)
 
{| class="wikitable"
 
{| class="wikitable"
 
|-
 
|-
Zeile 210: Zeile 198:
 
|-
 
|-
 
|<code>appVersion</code>||Die Applikations-Version, gelesen von <code>build.gradle</code>
 
|<code>appVersion</code>||Die Applikations-Version, gelesen von <code>build.gradle</code>
|}
+
|}<br />
<br />
 
 
===Grails 2.0 Konfigurations-Syntax verwenden===
 
===Grails 2.0 Konfigurations-Syntax verwenden===
 
Wenn man es vorzieht, eine Groovy-Konfiguration im Stil von Grails 2.0 zu verwenden, dann ist es möglich, die Konfiguration mit dem Groovy-[http://docs.groovy-lang.org/latest/html/documentation/#_configslurper ConfigSlurper]-Syntax zu spezifizieren.  
 
Wenn man es vorzieht, eine Groovy-Konfiguration im Stil von Grails 2.0 zu verwenden, dann ist es möglich, die Konfiguration mit dem Groovy-[http://docs.groovy-lang.org/latest/html/documentation/#_configslurper ConfigSlurper]-Syntax zu spezifizieren.  
  
 
*ConfigSlurper ist eine Utility-Klasse zum Lesen von Konfigurationsdateien, die in Form von Groovy-Skripten definiert sind.  
 
*ConfigSlurper ist eine Utility-Klasse zum Lesen von Konfigurationsdateien, die in Form von Groovy-Skripten definiert sind.  
**Wie es bei Java *.properties-Dateien der Fall ist, erlaubt ConfigSlurper eine Punktnotation. Zusätzlich erlaubt es aber auch Closure-Scoped Konfigurationswerte und beliebige Objekttypen.
+
**Wie es bei Java <code>*.properties</code>-Dateien der Fall ist, erlaubt ConfigSlurper eine Punktnotation. Zusätzlich erlaubt es aber auch Closure-Scoped Konfigurationswerte und beliebige Objekttypen.
  
 
Zwei Groovy-Konfigurationsdateien sind hierbei verfügbar:  
 
Zwei Groovy-Konfigurationsdateien sind hierbei verfügbar:  
Zeile 225: Zeile 212:
 
***runtime.groovy
 
***runtime.groovy
 
}}
 
}}
 
+
<br />
 
===[http://docs.grails.org/3.3.9/guide/single.html#_accessing_configuration_with_grailsapplication Konfigurationswerte mithilfe des <code>GrailsApplication</code>-Objekts abrufen]===
 
===[http://docs.grails.org/3.3.9/guide/single.html#_accessing_configuration_with_grailsapplication Konfigurationswerte mithilfe des <code>GrailsApplication</code>-Objekts abrufen]===
Um innerhalb von Controllern/Tag-Libraries auf die Laufzeit-Konfiguration zuzugreifen, gibt es eine spezielle öffentliche Variable namens <code>grailsApplication</code> vom Typ [http://docs.grails.org/4.0.0.M1/api/grails/core/GrailsApplication.html GrailsApplication].
+
Um innerhalb von Controllern/Tag-Libraries auf die Laufzeit-Konfiguration zuzugreifen, gibt es eine spezielle injezierte Variable namens <code>grailsApplication</code> vom Typ [http://docs.grails.org/4.0.0.M1/api/grails/core/GrailsApplication.html GrailsApplication].
  
 
Dessen <code>config</code>-Eigenschaft vom Typen [http://docs.grails.org/3.3.9/api/grails/config/Config.html grails.config.Config] bietet nützliche Funktionen um Werte aus der Konfigurationsdatei zu erhalten:
 
Dessen <code>config</code>-Eigenschaft vom Typen [http://docs.grails.org/3.3.9/api/grails/config/Config.html grails.config.Config] bietet nützliche Funktionen um Werte aus der Konfigurationsdatei zu erhalten:
Zeile 248: Zeile 235:
 
</syntaxhighlight>Die <code>config</code>-Eigenschaft des <code>grailsApplication</code>-Objekts ist eine Instanz der [http://docs.grails.org/3.3.9/api/grails/config/Config.html Config]-Schnittstelle und bietet eine Reihe nützlicher Methoden zum Auslesen der Konfiguration der Anwendung.
 
</syntaxhighlight>Die <code>config</code>-Eigenschaft des <code>grailsApplication</code>-Objekts ist eine Instanz der [http://docs.grails.org/3.3.9/api/grails/config/Config.html Config]-Schnittstelle und bietet eine Reihe nützlicher Methoden zum Auslesen der Konfiguration der Anwendung.
  
Insbesondere die <code>getProperty</code>-Methode (siehe oben) ist nützlich, um Konfigurationseigenschaften effizient abzurufen, während der Eigenschaftstyp angegeben wird (der Standardtyp ist String) und/oder ein Standard-Fallback-Wert bereitgestellt wird.
+
Insbesondere die <code>getProperty</code>-Methode (siehe oben) ist nützlich, um Konfigurationseigenschaften effizient abzurufen, während der Eigenschaftstyp angegeben wird (der Standardtyp ist String) und/oder ein Fallback-Wert bereitgestellt wird.
  
  
Zeile 259: Zeile 246:
 
liest und sie zu einem einzigen Objekt zusammenführt.
 
liest und sie zu einem einzigen Objekt zusammenführt.
  
 
+
Das <code>GrailsApplication</code>-Objekt kann auch ganz einfach in <code>service's</code> und andere Grails-Artifakte wie folgt eingebunden werden:<syntaxhighlight lang="groovy">
 
 
Das <code>GrailsApplication</code>-Objekt kann auch ganz einfach in <code>service's</code> und andere Grails-Artifakte wie folgt injected werden:<syntaxhighlight lang="groovy">
 
 
import grails.core.*
 
import grails.core.*
  
Zeile 287: Zeile 272:
 
     }
 
     }
 
}
 
}
</syntaxhighlight>Im Groovy-Code müssen Sie für den Wert der Value-Annotation einfache Anführungszeichen um die Zeichenfolge verwenden, andernfalls wird sie als GString und nicht als Spring-Ausdruck interpretiert.
+
</syntaxhighlight>Beachte: Im Groovy-Code müssen für den Wert der Value-Annotation einfache Anführungszeichen (<code>'</code>) um die Zeichenfolge verwenden, andernfalls wird sie als GString und nicht als Spring-Ausdruck interpretiert.
 
<br />
 
<br />
 
===Logging-Konfiguration===
 
===Logging-Konfiguration===
 
Standardmäßig wird die Protokollierung in Grails 3.0 mithilfe des [http://logback.qos.ch/ Logback]-Frameworks ([http://logback.qos.ch/ Offizielle Dokumentation]) verarbeitet und kann mit der Datei unter <code>grails-app/conf/logback.groovy</code> konfiguriert werden.
 
Standardmäßig wird die Protokollierung in Grails 3.0 mithilfe des [http://logback.qos.ch/ Logback]-Frameworks ([http://logback.qos.ch/ Offizielle Dokumentation]) verarbeitet und kann mit der Datei unter <code>grails-app/conf/logback.groovy</code> konfiguriert werden.
  
====Logger-Namen====
+
Hier ist ein Beispiel meiner zusammengebastelten LogBack-Groovy-Konfiguration:<syntaxhighlight lang="groovy">
Grails-Artifakte (Controller, Services, ...) wird automatisch eine <code>log</code> Methode injeziert.
+
import ch.qos.logback.core.util.FileSize
 +
import grails.util.BuildSettings
 +
import grails.util.Environment
 +
import org.springframework.boot.ApplicationPid
 +
import org.springframework.boot.logging.logback.ColorConverter
 +
import org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter
 +
 
 +
// Set "PID"-Environment-Property to the one assigned to the Grails Application if it has not already been set - We'll use it in the logging output.
 +
if (!System.getProperty("PID")) {
 +
    System.setProperty("PID", (new ApplicationPid()).toString())
 +
}
  
*Vor Grails 3.3.0 folgte der Name des Loggers für Grails Artefakt der Konvention <code>grails.app.<type>.<className></code>, wobei <code>type</code> für den Artefakt-Typ steht, z.B. Controller oder Dienste, und <code>className</code> für den voll qualifizierten Namen des Artefakts.
+
// Mimic Spring Boot logging configuration:
 +
conversionRule 'clr', ColorConverter
 +
conversionRule 'wex', WhitespaceThrowableProxyConverter
 +
def COLORFUL_AND_VERBOSE_PATTERN = '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} ' + // Date
 +
        '%clr(%5p) ' + // Log level
 +
        '%clr(%property{PID}){magenta} ' + // PID
 +
        '%clr(---){faint} %clr([%15.15t]){faint} ' + // Thread
 +
        '%clr(%-40.40logger{39}){cyan} %clr(:){faint} ' + // Logger
 +
        '%m%n%wex' // Message
 +
def UNCOLORFUL_HUMANOID_PATTERN = '%d{yyyy-MM-dd} | %d{HH:mm:ss.SSS} | %-20.20thread | %5p | %-25.25logger{25} | %12(ID: %8mdc{id}) | %m%n'
  
*Ab Grails 3.3.x wurden die Logger-Namen vereinfacht.
+
// See http://logback.qos.ch/manual/groovy.html for details on configuration..
 +
def HOME_DIR = "."
  
Das nächste Beispiel veranschaulicht die Änderung:
+
// DEFINE Appender
{| class="wikitable"
+
appender('STDOUT', ConsoleAppender) {
 +
    encoder(PatternLayoutEncoder) {
 +
        pattern = COLORFUL_AND_VERBOSE_PATTERN
 +
    }
 +
}
 +
 
 +
// DEFINE Appender
 +
appender("ROLLING", RollingFileAppender) {
 +
    encoder(PatternLayoutEncoder) {
 +
        pattern = "%level %logger - %msg%n"
 +
    }
 +
    // TimeBasedRollingPolicy implement both the TriggeringPolicy (which specifies when the rollover should occur) and the RollingPolicy (How to perform the rollover)
 +
    // See http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy for Documentation
 +
    rollingPolicy(TimeBasedRollingPolicy) {
 +
        /*
 +
        * TimeBasedRollingPolicy gets both it's rolling behavior (creating a new log file with the current date/time in the file name) and it's triggering behavior (rollover will occur based on the specified timestamp pattern) from the fileNamePattern property.
 +
        * The trigger policy is the most interesting part here - it takes an approach that bases the rollover occurrence on how specific you define the timestamp in the fileNamePattern. So if you specify down to the month, rollover will occur each month. Specify a pattern down to the day, and it will occur daily).
 +
        * It's easier to understand when you see it in action, so here's some example patterns taken from Logback's documentation:
 +
            fileNamePattern = "/myApp-log.%d{yyyy-MM}.log"         //Rollover at the beginning of each month, file format: myApp-log.2016-11.log
 +
            fileNamePattern = "/myApp-log.%d{yyyy-ww}.log"         //Rollover at the first day of each week. Note that the first day of the week depends on the locale.
 +
            fileNamePattern = "/myApp-log.%d{yyyy-MM-dd_HH}.log" //Rollover at the top of each hour.
 +
        *
 +
        * Note that in the above examples we are configuring the timestamp in the filename of the log files. We can also use the timestamp to create a file directory structure, like this example:
 +
            fileNamePattern = "/logs/%d{yyyy/MM}/myApp.log" // Rollover at the beginning of each month.
 +
            // Each log file will be stored in a year/month directory, e.g: /logs/2016/11/myApp.log, /logs/2016/12/myApp.log, /logs/2017/01/myApp.log
 +
        *
 +
        * Finally, adding a zip or gz file extension to our fileNamePattern will apply the selected compression to the rolled-over log files:
 +
            fileNamePattern = "/myApp-log.%d{yyyy/MM}.gz"       //Rollover at the beginning of each month, compress the rolled-over file with GZIP
 +
        */
 +
        fileNamePattern = "${HOME_DIR}/logs/myApp-%d{yyyy-MM-dd_HH}.log"
 +
        // maxHistory sets the upper limit on how many log files to preserve (when the max is reached the oldest file is deleted)
 +
        maxHistory = 30
 +
        // totalSizeCap sets a cap on how much disk space our log files are allowed to use (again, when the cap is reached the oldest files are deleted).
 +
        totalSizeCap = FileSize.valueOf("2GB")
 +
    }
 +
}
 +
 
 +
// Finally, let's specify that we want our new RollingFileAppender to be used in production mode only, while keeping the default ConsoleAppender for development mode:
 +
def targetDir = BuildSettings.TARGET_DIR
 +
if (Environment.isDevelopmentMode() && targetDir != null) {
 +
    println("Using development logging configuration. targetDir = " + targetDir.getAbsolutePath())
 +
    appender("FULL_STACKTRACE", FileAppender) {
 +
        file = "${targetDir}/stacktrace.log"
 +
        append = true
 +
        encoder(PatternLayoutEncoder) {
 +
            pattern = UNCOLORFUL_HUMANOID_PATTERN
 +
        }
 +
    }
 +
 
 +
    logger("StackTrace", ERROR, ['FULL_STACKTRACE'], false)
 +
    root(ERROR, ['STDOUT', 'FULL_STACKTRACE'])
 +
} else {
 +
    println("Using rolling production logging configuration. HOME_DIR = " + new File(HOME_DIR).getAbsolutePath())
 +
    root(ERROR, ['ROLLING'])
 +
}
 +
 
 +
</syntaxhighlight>
 +
 
 +
====Logger-Namen====
 +
Grails-Artifakte (Controller, Services, ...) wird automatisch eine <code>log</code> Methode injeziert.
 +
 
 +
*Vor Grails 3.3.0 folgte der Name des Loggers für Grails Artefakt der Konvention <code>grails.app.<type>.<className></code>, wobei <code>type</code> für den Artefakt-Typ steht, z.B. Controller oder Dienste, und <code>className</code> für den voll qualifizierten Namen des Artefakts.
 +
 
 +
*Ab Grails 3.3.x wurden die Logger-Namen vereinfacht.
 +
 
 +
Das nächste Beispiel veranschaulicht die Änderung:
 +
{| class="wikitable"
 
|+
 
|+
 
|
 
|
Zeile 316: Zeile 387:
 
|com.company.BookController
 
|com.company.BookController
 
|}
 
|}
<br />
+
[https://docs.grails.org/3.3.10/guide/single.html#externalLoggingConfiguration Logging-Konfigurationsdatei bestimmen]
  
====[https://docs.grails.org/3.3.10/guide/single.html#externalLoggingConfiguration Logging-Konfigurationsdatei bestimmen]====
 
 
Um zu bestimmen, von welcher Datei Logback seine Konfigurationswerte lesen soll, gibt es 3 Möglichkeiten:
 
Um zu bestimmen, von welcher Datei Logback seine Konfigurationswerte lesen soll, gibt es 3 Möglichkeiten:
  
Zeile 338: Zeile 408:
 
</syntaxhighlight><br />
 
</syntaxhighlight><br />
 
====[https://docs.grails.org/3.3.10/guide/single.html#maskingRequestParametersFromStacktraceLogs Anfrage-Parameter im Log-Stacktrace verstecken]====
 
====[https://docs.grails.org/3.3.10/guide/single.html#maskingRequestParametersFromStacktraceLogs Anfrage-Parameter im Log-Stacktrace verstecken]====
Wenn Grails einen Stacktrace protokolliert, kann die Protokollnachricht die Namen und Werte aller Anforderungsparameter für die aktuelle Anforderung enthalten. Parameter, die in dem Protokoll nicht aufgeführt werden sollen, können innerhalb der Konfigurationsvariable <code>grails.exceptionresolver.params.exclude</code> in Form einer hinterlegt werden:
+
Wenn Grails eine Stapel(speicher)zurückverfolgung protokolliert, kann die Protokollnachricht die Namen und Werte aller Anforderungsparameter für die aktuelle Anforderung enthalten. Parameter, die in dem Protokoll nicht aufgeführt werden sollen, können innerhalb der Konfigurationsvariable <code>grails.exceptionresolver.params.exclude</code> in Form einer Liste hinterlegt werden:
 
<syntaxhighlight lang="yaml">
 
<syntaxhighlight lang="yaml">
 
grails:
 
grails:
Zeile 348: Zeile 418:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Die Protokollierung von Anforderungsparametern kann ganz abgeschaltet werden, indem der Konfigurationswert <code>grails.exceptionresolver.logRequestParameters</code> auf <code>false</code> gesetzt wird.
+
Die Protokollierung von Anforderungsparametern kann '''ganz abgeschaltet''' werden, indem der Konfigurationswert <code>grails.exceptionresolver.logRequestParameters</code> auf <code>false</code> gesetzt wird.
  
Der Standardwert ist  
+
'''Der Standardwert ist'''
  
*<code>true</code>, wenn die Anwendung im Modus DEVELOPMENT läuft, und
+
*'''<code>true</code>, wenn die Anwendung im Modus DEVELOPMENT läuft, und'''
*<code>false</code> für alle anderen Umgebungen.<br />
+
*'''<code>false</code> für alle anderen Umgebungen.<br />'''
  
  
 
===[https://docs.grails.org/3.3.10/guide/single.html#environments Umgebungs-Abhängige Konfigurationswerte]===
 
===[https://docs.grails.org/3.3.10/guide/single.html#environments Umgebungs-Abhängige Konfigurationswerte]===
Wenn man die Konfigurationen direkt in die Datei einfügt (Am Anfang der Zeile, aka. als Parent-Node) gilt diese für alle gestarteten Umgebungen.
+
Wenn man einen Konfigurationspfad direkt in die Datei einfügt (Am Anfang der Zeile, aka. als Parent-Node) gilt diese für alle gestarteten Umgebungen.
  
  
Die Dateien <code>application.yml</code> und <code>application.groovy</code> im Verzeichnis <code>grails-app/conf</code> können das Konzept der "Konfiguration pro Umgebung" verwenden, wobei entweder YAML oder die von ConfigSlurper bereitgestellte Syntax verwendet wird.
+
Die Dateien <code>application.yml</code> und <code>application.groovy</code> im Verzeichnis <code>grails-app/conf</code> verstehen das Konzept der "Konfiguration pro Umgebung", wobei entweder YAML oder die von ConfigSlurper bereitgestellte Syntax verwendet werden kann.
  
 
Als Beispiel betrachte man die folgende von Grails bereitgestellte Standard application.yml-Definition:
 
Als Beispiel betrachte man die folgende von Grails bereitgestellte Standard application.yml-Definition:
Zeile 412: Zeile 482:
  
 
====Befehl in gewünschter Umgebung ausführen====
 
====Befehl in gewünschter Umgebung ausführen====
Tipp: Grails hat die Kababilität, Befehle mit einer gesetzten Umgebung auszuführen: <code>grails <<environment>> <<command name>></code>. Beispiel: <code>grails test war</code>
+
Tipp: Grails hat die Kapabilität, Befehle mit einer gesetzten Umgebung auszuführen: <code>grails <<Environment>> <<Command Name>></code>. Beispiel: <code>grails test war</code>
  
 
====Umgebung programmatisch auslesen====
 
====Umgebung programmatisch auslesen====
Zeile 428: Zeile 498:
 
         break
 
         break
 
}
 
}
</syntaxhighlight>
+
</syntaxhighlight><br />
 
 
 
==[http://docs.grails.org/latest/guide/single.html#dataSource DataSource]==
 
==[http://docs.grails.org/latest/guide/single.html#dataSource DataSource]==
Weil Grails auf Java aufbaut sollte man ein bisschen Verständnis von JDBC ([https://de.wikipedia.org/wiki/Java_Database_Connectivity Java Database Connectivity], ''die'' Datenbankschnittstelle der Java-Plattform) besitzen.<br>
+
Weil Grails auf Java aufbaut sollte man ein bisschen Verständnis von JDBC ([https://de.wikipedia.org/wiki/Java_Database_Connectivity Java Database Connectivity], ''die'' Datenbankschnittstelle der Java-Plattform) besitzen.
  
 
===Treiber einfügen===
 
===Treiber einfügen===
Zeile 449: Zeile 518:
 
Für die Konfigurations-Eigenschaft <code>dataSource</code> stehen folgende Eigenschaften zur Einstellung bereit:
 
Für die Konfigurations-Eigenschaft <code>dataSource</code> stehen folgende Eigenschaften zur Einstellung bereit:
  
 +
*<code>'''url'''</code> - Die JDBC-URL der Datenbank
 
*<code>'''driverClassName'''</code> - Klassenname des JDBC-Treibers (zB. <code>"com.mysql.jdbc.Driver"</code>)
 
*<code>'''driverClassName'''</code> - Klassenname des JDBC-Treibers (zB. <code>"com.mysql.jdbc.Driver"</code>)
 
*<code>'''username'''</code>, <code>'''password'''</code> - Login-Daten, um die JDBC-Verbindung aufzubauen
 
*<code>'''username'''</code>, <code>'''password'''</code> - Login-Daten, um die JDBC-Verbindung aufzubauen
*<code>'''url'''</code> - Die JDBC-URL der Datenbank
+
*<code>jndiName</code> - Der Name der JNDI-Ressource für die DataSource
*<code>'''dbCreate'''</code> - Gibt an, ob die Datenbank automatisch aus dem Domänen-Modell generiert werden soll: "create-drop", "create", "update" oder "validate".
+
*<u><code>'''dbCreate'''</code> - Gibt an, ob die Datenbank automatisch aus dem Domänen-Modell generiert werden soll: "create-drop", "create", "update" oder "validate".</u>
 
**<code>create</code> - Falls sich das Datenbank-Schema verändert hat, wird die Tabelle entleert und mithilfe des neuen Schemas erstellt
 
**<code>create</code> - Falls sich das Datenbank-Schema verändert hat, wird die Tabelle entleert und mithilfe des neuen Schemas erstellt
 
**<code>'''create-drop'''</code> - Gleich wie <code>create</code>, '''leert die Datenbank (!)''' hingegen bei <u>jedem</u> neustart (Egal ob sich das Schema geändert hat oder nicht, nützlich zum Testen)
 
**<code>'''create-drop'''</code> - Gleich wie <code>create</code>, '''leert die Datenbank (!)''' hingegen bei <u>jedem</u> neustart (Egal ob sich das Schema geändert hat oder nicht, nützlich zum Testen)
 
**<code>'''update'''</code> - Aktualisiert das Model der Tabelle, falls es sich verändert hat ('''Die Daten bleiben erhalten''', Die Domainklassen-Tabelle muss existieren) Notiz: Solch eine Weise veträgt natürlich nicht viele Änderung des Schemas. (zB. Beim ändern des Namens einer Spalte bleibt die alte Spalte mit den existierenden Daten und die neue wird einfach hinzugefügt)
 
**<code>'''update'''</code> - Aktualisiert das Model der Tabelle, falls es sich verändert hat ('''Die Daten bleiben erhalten''', Die Domainklassen-Tabelle muss existieren) Notiz: Solch eine Weise veträgt natürlich nicht viele Änderung des Schemas. (zB. Beim ändern des Namens einer Spalte bleibt die alte Spalte mit den existierenden Daten und die neue wird einfach hinzugefügt)
 
**''<code>validate</code> - Warnt vor Änderungen, aber verändert das Datenbank-Schema bei Veränderung '''nicht'''''
 
**''<code>validate</code> - Warnt vor Änderungen, aber verändert das Datenbank-Schema bei Veränderung '''nicht'''''
 +
**<code>none</code> - ''Siehe unten angeführte Notiz''
 
*<code>pooled</code> - Ob ein Pool von Verbindungen aufgebaut werden sollte (Standard: true)
 
*<code>pooled</code> - Ob ein Pool von Verbindungen aufgebaut werden sollte (Standard: true)
 +
*<code>lazy</code> - Ob ein <code>LazyConnectionDataSourceProxy</code> verwendet werden soll
 +
*<code>transactionAware</code> - ob ein <code>TransactionAwareDataSourceProxy</code> verwendet werden soll
 +
*<code>readOnly</code> - Wenn <code>true</code>, ist die DataSource schreibgeschützt, was dazu führt, dass der Verbindungspool für jede Verbindung <code>setReadOnly(true)</code> aufruft
 
*<code>type</code> - Die Verbindungspoolklasse, wenn Sie Grails zwingen möchten, sie zu verwenden, wenn mehr als eine verfügbar ist.
 
*<code>type</code> - Die Verbindungspoolklasse, wenn Sie Grails zwingen möchten, sie zu verwenden, wenn mehr als eine verfügbar ist.
 
*<code>logSql</code> - Leitet SQL-Logs zum stdout um
 
*<code>logSql</code> - Leitet SQL-Logs zum stdout um
 
*<code>formatSql</code> - Ob SQL-Logs formatiert werden sollen
 
*<code>formatSql</code> - Ob SQL-Logs formatiert werden sollen
 
*<code>''dialect''</code> - Eine Zeichenfolge oder Klasse, die den Ruhezustand-Dialekt darstellt, der für die Kommunikation mit der Datenbank verwendet wird. Informationen zu verfügbaren Dialekten findet man im Paket [http://docs.jboss.org/hibernate/core/3.6/javadocs/org/hibernate/dialect/package-summary.html org.hibernate.dialect].
 
*<code>''dialect''</code> - Eine Zeichenfolge oder Klasse, die den Ruhezustand-Dialekt darstellt, der für die Kommunikation mit der Datenbank verwendet wird. Informationen zu verfügbaren Dialekten findet man im Paket [http://docs.jboss.org/hibernate/core/3.6/javadocs/org/hibernate/dialect/package-summary.html org.hibernate.dialect].
*<code>readOnly</code> - Wenn <code>true</code>, ist die DataSource schreibgeschützt, was dazu führt, dass der Verbindungspool für jede Verbindung <code>setReadOnly(true)</code> aufruft
 
 
*''<code>transactional</code> - Wenn <code>false</code>, wird die DataSource-Transaktionsmanager-Bean außerhalb der verketteten BE1PC-Transaktionsmanager-Implementierung belassen.'' Dies gilt nur für zusätzliche Datenquellen.
 
*''<code>transactional</code> - Wenn <code>false</code>, wird die DataSource-Transaktionsmanager-Bean außerhalb der verketteten BE1PC-Transaktionsmanager-Implementierung belassen.'' Dies gilt nur für zusätzliche Datenquellen.
 
*<code>''persistenceInterceptor''</code> - Die Standarddatenquelle wird automatisch mit dem Persistenz-Interceptor verbunden, andere Datenquellen werden nicht automatisch verbunden, es sei denn, dies ist auf <code>true</code> gesetzt
 
*<code>''persistenceInterceptor''</code> - Die Standarddatenquelle wird automatisch mit dem Persistenz-Interceptor verbunden, andere Datenquellen werden nicht automatisch verbunden, es sei denn, dies ist auf <code>true</code> gesetzt
Zeile 475: Zeile 548:
 
Im Entwicklungsmodus (<code>development</code>) ist <code>dbCreate</code> standardmäßig auf "<code>create-drop</code>" eingestellt, aber irgendwann in der Entwicklung (und auf jeden Fall, wenn Sie zur Produktion übergehen) müssen Sie aufhören, die Datenbank jedes Mal, wenn Sie Ihren Server starten, fallen zu lassen und neu zu erstellen.
 
Im Entwicklungsmodus (<code>development</code>) ist <code>dbCreate</code> standardmäßig auf "<code>create-drop</code>" eingestellt, aber irgendwann in der Entwicklung (und auf jeden Fall, wenn Sie zur Produktion übergehen) müssen Sie aufhören, die Datenbank jedes Mal, wenn Sie Ihren Server starten, fallen zu lassen und neu zu erstellen.
  
Es ist verlockend, zu <code>"update"</code>zu wechseln, so dass Sie vorhandene Daten beibehalten und das Schema nur aktualisieren, wenn sich Ihr Code ändert, aber die Aktualisierungsunterstützung von Hibernate ist sehr konservativ. Es werden keine Änderungen vorgenommen, die zu Datenverlusten führen könnten, und umbenannte Spalten oder Tabellen werden nicht erkannt, so dass Sie das alte Schema behalten und auch das neue haben.
+
Es ist verlockend, zu <code>"update"</code> zu wechseln, so dass Sie vorhandene Daten beibehalten und das Schema nur aktualisieren, wenn sich Ihr Code ändert, aber die Aktualisierungsunterstützung von Hibernate ist sehr konservativ. Es werden keine Änderungen vorgenommen, die zu Datenverlusten führen könnten, und umbenannte Spalten oder Tabellen werden nicht erkannt, so dass Sie das alte Schema behalten und auch das neue haben.
  
  
Grails unterstützt Datenbankmigrationen mit Liquibase oder Flyway über Plugins.:
+
Grails unterstützt Datenbankmigrationen mit Liquibase oder Flyway (Siehe [https://www.liquibase.org/liquibase-vs-flyway offizieller Vergleich]) über Plugins:
  
*[http://plugins.grails.org/plugin/grails/database-migration database-migration Plugin (Liquibase)]
+
*[http://plugins.grails.org/plugin/grails/database-migration '''database-migration Plugin (Liquibase)''']
 
*[http://plugins.grails.org/plugin/saw303/org.grails.plugins%3Agrails-flyway flyway Plugin (Flyway)]
 
*[http://plugins.grails.org/plugin/saw303/org.grails.plugins%3Agrails-flyway flyway Plugin (Flyway)]
 
</blockquote><br />
 
</blockquote><br />
Zeile 595: Zeile 668:
  
 
==[http://docs.grails.org/3.3.9/guide/single.html#applicationClass Die <code>Application</code>-Klasse]==
 
==[http://docs.grails.org/3.3.9/guide/single.html#applicationClass Die <code>Application</code>-Klasse]==
Jede neue Grails-Anwendung verfügt über eine Application innerhalb des Verzeichnisses grails-app/init.
+
Jede neue Grails-Anwendung verfügt über eine Klasse namens <code>Application.groovy</code> innerhalb des Verzeichniss <code>grails-app/init</code>.
  
 
Die <code>Application</code>-Klasse ist eine Unterklasse der <code>[http://docs.grails.org/3.3.9/api/grails/boot/config/GrailsAutoConfiguration.html GrailsAutoConfiguration]</code>-Klasse und verfügt über die <code>static void main</code>, d.h. sie kann als reguläre Anwendung ausgeführt werden.<br />
 
Die <code>Application</code>-Klasse ist eine Unterklasse der <code>[http://docs.grails.org/3.3.9/api/grails/boot/config/GrailsAutoConfiguration.html GrailsAutoConfiguration]</code>-Klasse und verfügt über die <code>static void main</code>, d.h. sie kann als reguläre Anwendung ausgeführt werden.<br />
Zeile 602: Zeile 675:
 
Wenn man eine IDE verwendet, kann man ganz einfach mit der rechten Maustaste auf die Klasse klicken und sie direkt von der IDE aus starten, wodurch Ihre Grails-Anwendung gestartet wird.
 
Wenn man eine IDE verwendet, kann man ganz einfach mit der rechten Maustaste auf die Klasse klicken und sie direkt von der IDE aus starten, wodurch Ihre Grails-Anwendung gestartet wird.
  
Dies ist auch für das Debugging nützlich, da Sie direkt von der IDE aus debuggen können, ohne einen Remote-Debugger anschließen zu müssen, wenn Sie den Befehl run-app --debug-jvm von der Befehlszeile aus ausführen.
+
Dies ist auch für das Debugging nützlich, da man hiermit <u>direkt von der IDE aus debuggen</u> kann, ohne einen Remote-Debugger mit dem Befehl <code>run-app --debug-jvm</code> von der Befehlszeile aus extern anschließen zu müssen.
  
Man kann die Anwendung auch z.B. in eine lauffähige WAR-Datei packen (nützlich, wenn man plant die Anwendung mit einem containerlosen Ansatz zu implementieren.):<syntaxhighlight lang="bash">
+
Man kann die Anwendung auch z.B. in eine lauffähige WAR-Datei packen (nützlich, wenn man plant die Anwendung mit einem Kontainerlosen Ansatz zu implementieren.):<syntaxhighlight lang="bash">
 
$ grails package
 
$ grails package
 
$ java -jar build/libs/myapp-0.1.war
 
$ java -jar build/libs/myapp-0.1.war
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
 
<br />
 
 
=[https://docs.grails.org/3.3.9/guide/commandLine.html Grails Scripte]=
 
=[https://docs.grails.org/3.3.9/guide/commandLine.html Grails Scripte]=
 
''Das Befehlszeilensystem von Grails 3.0 unterscheidet sich '''stark''' von früheren Versionen von Grails und verfügt über APIs zum Aufrufen von Gradle für build-bezogene Aufgaben sowie zur Codegenerierung.''
 
''Das Befehlszeilensystem von Grails 3.0 unterscheidet sich '''stark''' von früheren Versionen von Grails und verfügt über APIs zum Aufrufen von Gradle für build-bezogene Aufgaben sowie zur Codegenerierung.''
Zeile 617: Zeile 687:
 
$ grails <<command name>>
 
$ grails <<command name>>
 
</syntaxhighlight>
 
</syntaxhighlight>
 
  
 
Es wird zuerst die Anwendung und dann das Profil nach Befehlen durchsucht. Beispiel am Befehl <code>run-app</code>:
 
Es wird zuerst die Anwendung und dann das Profil nach Befehlen durchsucht. Beispiel am Befehl <code>run-app</code>:
Zeile 626: Zeile 695:
  
 
==[https://docs.grails.org/3.3.9/guide/commandLine.html Grails-Scripte erstellen]==
 
==[https://docs.grails.org/3.3.9/guide/commandLine.html Grails-Scripte erstellen]==
Mit dem Befehl <code>$ grails [https://docs.grails.org/3.3.9/ref/Command&#x20;Line/create-script.html create-script] NAME</code> kann man ein Grund-Gerüst für sein neues Skript unter <code>src/main/scripts/</code> erstellen lassen.<blockquote>Im Allgemeinen sollten Grails-Skripte für die Skripterstellung des Gradle-basierten Build-Systems und für die Code-Generierung verwendet werden. Skripte können keine Anwendungsklassen laden und sollten dies auch nicht tun, da Gradle zur Konstruktion des Anwendungs-Klassenpfades erforderlich ist.</blockquote><br />
+
Mit dem Befehl <code>$ grails [https://docs.grails.org/3.3.9/ref/Command&#x20;Line/create-script.html create-script] NAME</code> kann man ein Grund-Gerüst für sein neues Skript unter <code>src/main/scripts/</code> erstellen lassen.<blockquote>Im Allgemeinen sollten Grails-Skripte für die Skripterstellung des Gradle-basierten Build-Systems und für die Code-Generierung verwendet werden. Skripte können keine Anwendungsklassen laden und sollten dies auch nicht tun, da Gradle zur Konstruktion des Anwendungs-Klassenpfades erforderlich ist.</blockquote>
 
==description()==
 
==description()==
 
Jedes Skript erbt (unter anderem)
 
Jedes Skript erbt (unter anderem)
  
* von [https://docs.grails.org/3.3.9/ref/Command&#x20;Line/create-script.html GroovyScriptCommand], welches eine API für viele nützliche Aufgaben bereitstellt, sowie  
+
*von [https://docs.grails.org/3.3.9/ref/Command&#x20;Line/create-script.html GroovyScriptCommand], welches eine API für viele nützliche Aufgaben bereitstellt, sowie
* von [https://docs.grails.org/3.3.9/api/org/grails/cli/profile/commands/templates/TemplateRenderer.html TemplateRenderer], welches die zur Code-Generierung genutzten <code>render</code>/<code>template</code>-Methoden zur Verfügung stellt. (Siehe Beispiel unten)
+
*von [https://docs.grails.org/3.3.9/api/org/grails/cli/profile/commands/templates/TemplateRenderer.html TemplateRenderer], welches die zur Code-Generierung genutzten <code>render</code>/<code>template</code>-Methoden zur Verfügung stellt. (Siehe Beispiel unten)
 
 
  
 
Die description()-Methode wird für die Ausgabe vom <code>grails help</code> Befehl verwendet, um den Nutzern zu helfen.<br>Beispiel am <code>generate-all</code> Befehl:<syntaxhighlight lang="groovy">
 
Die description()-Methode wird für die Ausgabe vom <code>grails help</code> Befehl verwendet, um den Nutzern zu helfen.<br>Beispiel am <code>generate-all</code> Befehl:<syntaxhighlight lang="groovy">
Zeile 640: Zeile 708:
 
   argument name:'Domain Class', description:'The name of the domain class'
 
   argument name:'Domain Class', description:'The name of the domain class'
 
}
 
}
</syntaxhighlight><br />
+
</syntaxhighlight>
 
==[https://docs.grails.org/3.3.9/guide/commandLine.html#_the_model model()]==
 
==[https://docs.grails.org/3.3.9/guide/commandLine.html#_the_model model()]==
 
Wenn man <code>model()</code> mit einer Klassen/String/Datei/Resource-Parameter aufruft, erhält man eine neue Instanz der Klasse <code>[http://docs.grails.org/4.0.0.M1/api/grails/codegen/model/Model.html Model]</code> welche hiflreiche Methoden zur Quellcode-Generierung besitzt.
 
Wenn man <code>model()</code> mit einer Klassen/String/Datei/Resource-Parameter aufruft, erhält man eine neue Instanz der Klasse <code>[http://docs.grails.org/4.0.0.M1/api/grails/codegen/model/Model.html Model]</code> welche hiflreiche Methoden zur Quellcode-Generierung besitzt.
Zeile 662: Zeile 730:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== [https://docs.grails.org/3.3.9/guide/commandLine.html#_invoking_gradle Gradle-Tasks aufrufen] ===
+
===[https://docs.grails.org/3.3.9/guide/commandLine.html#_invoking_gradle Gradle-Tasks aufrufen]===
 
Mithilfe der injezierten <code>gradle</code>-Variable können auch Gradle-Tasks getriggert werden:<syntaxhighlight lang="groovy">
 
Mithilfe der injezierten <code>gradle</code>-Variable können auch Gradle-Tasks getriggert werden:<syntaxhighlight lang="groovy">
 
gradle.compileGroovy()
 
gradle.compileGroovy()
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== [https://docs.grails.org/3.3.9/guide/commandLine.html#_invoking_ant Ant-Tasks aufrufen] ===
+
===[https://docs.grails.org/3.3.9/guide/commandLine.html#_invoking_ant Ant-Tasks aufrufen]===
 
Man kann auch Ant-Tasks aus Skripten heraus aufrufen, was beim schreiben von Codegenerierung und Automatisierungsaufgaben sehr nützlich sein kann:
 
Man kann auch Ant-Tasks aus Skripten heraus aufrufen, was beim schreiben von Codegenerierung und Automatisierungsaufgaben sehr nützlich sein kann:
  
Zeile 711: Zeile 779:
 
}
 
}
 
</syntaxhighlight><br />
 
</syntaxhighlight><br />
 +
=[https://docs.grails.org/3.3.9/guide/commandLine.html#creatingCustomCommands Grails Befehle]=
  
= [https://docs.grails.org/3.3.9/guide/commandLine.html#creatingCustomCommands Grails Befehle] =
+
==Unterschied Befehl und Skript==
 
 
== Unterschied Befehl und Skript ==
 
 
Im Gegensatz zu Skripten bewirken Befehle den Start der Grails-Umgebung, d.H. man hat den vollen Zugriff auf den Anwendungskontext und die Laufzeit.
 
Im Gegensatz zu Skripten bewirken Befehle den Start der Grails-Umgebung, d.H. man hat den vollen Zugriff auf den Anwendungskontext und die Laufzeit.
  
=== Änderungen in Grails 3.2 ===
+
===Änderungen in Grails 3.2===
 
Seit Grails 3.2.0 haben Befehle ähnliche Fähigkeiten wie Skripte in Bezug auf das Abrufen von Argumenten, die Erzeugung von Vorlagen, den Dateizugriff und die Modellerstellung.
 
Seit Grails 3.2.0 haben Befehle ähnliche Fähigkeiten wie Skripte in Bezug auf das Abrufen von Argumenten, die Erzeugung von Vorlagen, den Dateizugriff und die Modellerstellung.
  
Zeile 724: Zeile 791:
 
</syntaxhighlight>Befehle, die in Grails 3.2.0 oder höher erstellt wurden, implementieren standardmäßig die Eigenschaft [https://docs.grails.org/latest/api/grails/dev/commands/GrailsApplicationCommand.html GrailsApplicationCommand], die erfordert, dass der Befehl die folgende Methode implementiert: (Auf diese Weise definierte Befehle haben über eine Variable namens <code>executionContext</code> weiterhin Zugriff auf den [https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ConfigurableApplicationContext.html Ausführungskontext].)<syntaxhighlight lang="groovy">
 
</syntaxhighlight>Befehle, die in Grails 3.2.0 oder höher erstellt wurden, implementieren standardmäßig die Eigenschaft [https://docs.grails.org/latest/api/grails/dev/commands/GrailsApplicationCommand.html GrailsApplicationCommand], die erfordert, dass der Befehl die folgende Methode implementiert: (Auf diese Weise definierte Befehle haben über eine Variable namens <code>executionContext</code> weiterhin Zugriff auf den [https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ConfigurableApplicationContext.html Ausführungskontext].)<syntaxhighlight lang="groovy">
 
boolean handle()
 
boolean handle()
</syntaxhighlight><br />
+
</syntaxhighlight>
 
+
==Befehle erstellen==
== Befehle erstellen ==
 
 
Ähnlich wie bei Skripten kann man mit dem Befehl <code>$ grails create-command NAME</code> das Skelett eines Skriptes unter <code>src/main/commands/</code> erstellen lassen.
 
Ähnlich wie bei Skripten kann man mit dem Befehl <code>$ grails create-command NAME</code> das Skelett eines Skriptes unter <code>src/main/commands/</code> erstellen lassen.
  
== Befehl ausführen ==
+
==Befehl ausführen==
 
Selbst-geschriebene Befehle können  
 
Selbst-geschriebene Befehle können  
  
* mithilfe von <code>$ grails run-command NAME</code> oder
+
*mithilfe von <code>$ grails run-command NAME</code> oder
* mithilfe des Gradle-Tasks "runCommand" <code>gradle runCommand -Pargs="NAME"</code>  
+
*mithilfe des Gradle-Tasks "runCommand" <code>gradle runCommand -Pargs="NAME"</code>  
** Wenn der Grails-Server ein Unterprojekt ist (z.B. in einem Projekt, das mit Angular erstellt wurde), kann der Unterprojekt-Befehl immer noch aus dem Gradle-Wrapper im übergeordneten Projekt aufgerufen werden: <code>./gradlew server:runCommand -Pargs="my-example"</code>
+
**Wenn der Grails-Server ein Unterprojekt ist (z.B. in einem Projekt, das mit Angular erstellt wurde), kann der Unterprojekt-Befehl immer noch aus dem Gradle-Wrapper im übergeordneten Projekt aufgerufen werden: <code>./gradlew server:runCommand -Pargs="my-example"</code>
  
 
aufgerufen werden.
 
aufgerufen werden.
  
= [https://docs.grails.org/3.3.9/guide/commandLine.html#gradleBuild Gradle und Grails] =
 
TODO
 
  
  
 +
<br />
 +
=[https://docs.grails.org/3.3.9/guide/commandLine.html#gradleBuild Gradle und Grails]=
 +
Grails 3.1 verwendet das [https://gradle.org/ Gradle Build System] für Aufgaben, die mit dem Bauen zusammenhängen, wie z.B:;
 +
 +
*der Kompilierung,
 +
*das ausführen von Tests und
 +
*die Erstellung von Binärdistributionen des Projekts.
  
 +
Es wird empfohlen, Gradle 2.2 oder höher mit Grails 3.1 zu verwenden.
 +
Die Gradle-Konfiguration wird in der <code>build.gradle</code>-Datei realisiert. Hier steht u.A. die aktuelle Version der Anwendung, die benötigten Abhängigkeiten (Dependencies) und die Quell-Adressen woher diese Abhängigkeiten abgerufen werden.
  
 +
Wenn man den Befehl <code>grails</code> ausführt, wird die Version von Gradle, die mit Grails 3.1 (derzeit 2.9) ausgeliefert wird, durch den grails-Prozess über die [http://www.gradle.org/docs/current/userguide/embedding.html Gradle Tooling API] aufgerufen:<syntaxhighlight lang="bash">
 +
# Macht das gleiche wie 'gradle classes'
 +
$ grails compile
 +
</syntaxhighlight>
 +
Man kann auch <code>gradle</code> selbst aufrufen um seine auf dem System installierte Version von Gradle nutzen. Dabei sollte man aber beachten, dass ab Grails 3.0 eine Mindestanforderung von Gradle 2.2 besteht.<syntaxhighlight lang="bash">
 +
$ gradle assemble
 +
</syntaxhighlight>
  
 +
==Abhängigkeiten bestimmen==
 +
Abhängigkeiten für das Projekt werden im <code>dependencies</code>-Block definiert. Im Allgemeinen kann man hierbei der [http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html offiziellen Gradle-Dokumentation zur Verwaltung von Abhängigkeiten] folgen, um zu verstehen, wie man zusätzliche Abhängigkeiten konfigurieren kann.
  
 +
Ein Beispiel anhand des <code>web</code>-Profils:<syntaxhighlight lang="groovy">
 +
dependencies {
 +
  compile 'org.springframework.boot:spring-boot-starter-logging'
 +
  compile('org.springframework.boot:spring-boot-starter-actuator')
 +
  compile 'org.springframework.boot:spring-boot-autoconfigure'
 +
  compile 'org.springframework.boot:spring-boot-starter-tomcat'
 +
  compile 'org.grails:grails-dependencies'
 +
  compile 'org.grails:grails-web-boot'
  
 +
  compile 'org.grails.plugins:hibernate'
 +
  compile 'org.grails.plugins:cache'
 +
  compile 'org.hibernate:hibernate-ehcache'
  
=[https://docs.grails.org/3.3.10/guide/single.html#theWebLayer Die Web-Ebene]=
+
  runtime 'org.grails.plugins:asset-pipeline'
==Controller==
+
  runtime 'org.grails.plugins:scaffolding'
Ein [http://docs.grails.org/latest/guide/theWebLayer.html#controllers Controller] verarbeitet Anforderungen und erstellt oder bereitet die Antwort vor.
 
Ein Controller kann die Antwort direkt generieren oder an eine Ansicht (View, .gsp) delegieren.
 
  
===Controller erstellen===
+
  testCompile 'org.grails:grails-plugin-testing'
Mit dem Befehl <code>$ grails create-controller (PACKET.)KLASSEN-NAME </code> erstellt man das Skelett eines Controllers,
+
  testCompile 'org.grails.plugins:geb'
welches in <code>grails-app/controllers/APP-NAME/DEIN/PACKET/KLASSEN-NAME.groovy</code> gespeichert wird.  
 
''(Dieser Befehl ist nur zur vereinfachten Erstellung, man kann es auch manuell oder mit einer IDE machen)''
 
  
===Aktionen===
+
  // Note: It is recommended to update to a more robust driver (Chrome, Firefox etc.)
Die Standard [http://docs.grails.org/latest/guide/single.html#urlmappings URL-Mapping]-Konfiguration versichert dass der Name des Controllers
+
  testRuntime 'org.seleniumhq.selenium:selenium-htmlunit-driver:2.44.0'
sowie jede Methode zum entsprechendem URI-Pfad gebunden wird.<br>
 
Das folgende Beispiel ist hiermit unter ".../mahlzeit/index" erreichbar:
 
<syntaxhighlight lang="groovy">
 
package myapp
 
  
class MahlzeitController {
+
  console 'org.grails:grails-console'
    def index() { }
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Ein Controller kann mehrere öffentliche Methoden haben, welche sich (Wie oben beschrieben) jeweils zur einer URI binden.  
+
Beachte: Die Versionsnummern sind in der Mehrzahl der Abhängigkeiten nicht vorhanden. Dies ist dem Abhängigkeitsverwaltungs-Plugin zu verdanken, welches eine [https://www.codeflow.site/de/article/spring-maven-bom Maven BOM] konfiguriert, welche die Standard-Abhängigkeitsversionen für bestimmte häufig verwendete Abhängigkeiten und Plugins definiert:<syntaxhighlight lang="groovy">
 
+
dependencyManagement {
====Standard-Aktion====
+
    imports {
Wenn der Nutzer keine bestimmte Aktion in seiner Anfrage stehen hat ''(Also nur den Namen des Controllers, wie "/mahlzeit/" anstatt zB. "/mahlzeit/login") ''
+
        mavenBom 'org.grails:grails-bom:' + grailsVersion
versucht Grails eine Standard-Aktion ausfindig zu machen, welche diese Anfrage annimmt.
+
    }
 +
    applyMavenExclusions false
 +
}
 +
</syntaxhighlight>
 +
==Gradle Tasks verstehen==
 +
Wie bereits erwähnt, verwendet der <code>grails</code>-Befehl eine eingebettete Version von Gradle und bestimmte <code>grails</code>-Befehle, die in früheren Versionen von Grails existierten, werden nun mithilfe ihrer Gradle-Äquivalente abgebildet. Die folgende Tabelle zeigt, welcher Grails-Befehl welche Gradle-Aufgabe aufruft:
 +
{| class="wikitable"
 +
|+Grails Befehle und dessen Gradle Task Äquivalent
 +
!Grails Befehl
 +
!Gradle Task
 +
|-
 +
|clean
 +
|clean
 +
|-
 +
|compile
 +
|classes
 +
|-
 +
|package
 +
|assemble
 +
|-
 +
|run-app
 +
|bootRun
 +
|-
 +
|test-app
 +
|test
 +
|-
 +
|test-app --integration
 +
|integrationTest
 +
|-
 +
|war
 +
|assemble
 +
|}
 +
Um auch andere Gradle-Tasks mithilfe der in Grails integrierten Version von Gradle auszuführen, bietet Grails den Befehl <code>$ grails gradle <<task>></code>. Es wird wie immer empfohlen, diesen Befehl im interaktiven-Modus auszuführen, um auf zusätzliche Privilegien wie Auto-Vervollständigung von Tasks und schnelles ausführen Zugriff zu bekommen. Tipp: <code>gradle tasks</code> zeigt alle verfügbaren Tasks an.<syntaxhighlight>
 +
$ grails
 +
| Enter a command name to run. Use TAB for completion:
 +
grails> gradle tasks
 +
</syntaxhighlight>
 +
==(Übersicht) Offizielle Grails-Plugins für Gradle==
 +
Wenn man ein neues Projekt mit dem <code>$ grails create-app</code> erstellt wird eine standardmäßige <code>build.gradle</code> generiert. Die standardmäßige <code>build.gradle</code> konfiguriert den Build mit einem Satz von Gradle-Plugins, die es Gradle ermöglichen, das Grails-Projekt zu erstellen:<syntaxhighlight lang="groovy">
 +
apply plugin:"war"
 +
apply plugin:"org.grails.grails-web"
 +
apply plugin:"org.grails.grails-gsp"
 +
apply plugin:"asset-pipeline"
 +
</syntaxhighlight>
 +
Die Standard-Plugins sind wie folgt:
  
*Wenn es nur eine Aktion ''(aka. Methode)'' gibt, wird diese als Standard-Aktion anerkannt
+
*<code>[https://docs.gradle.org/current/userguide/war_plugin.html war]</code> - Das WAR-Plugin ändert den Packaging-Prozess, so dass Gradle aus der Anwendung eine [https://maven.apache.org/plugins/maven-war-plugin/ WAR-Datei] erzeugt. Man ''kann'' dieses Plugin auskommentieren, wenn man nur eine lauffähige JAR-Datei für den eigenständigen Einsatz erstellen möchten.
*Wenn es eine Aktion namens "index" gibt, wird diese als Standard-Aktion anerkannt
+
 
 +
*<code>[https://grails.org/plugin/asset-pipeline asset-pipeline]</code> - Das Asset-Pipeline-Plugin ermöglicht die Zusammenstellung von statischen Assets (JavaScript, CSS usw.)
  
Alternativ kann man auch eine eigenen Standard setzten, indem man <code>static defaultAction = "DEINE-METHODE"</code> in den Quellcode des Controllers einfügt.
+
Viele davon sind in Plugins eingebaut, die von Gradle- oder Drittanbieter-Plugins bereitgestellt werden. Die Gradle-Plugins, die Grails zur Verfügung stellt, sind die folgenden:
  
==Scope-Variablen==
+
*<code>[https://github.com/grails/grails-core org.grails.grails-core]</code> - Das primäre Grails-Plugin für Gradle, das von allen anderen Plugins eingebunden wird und für den Betrieb mit allen Profilen ausgelegt ist.
"Scope-Variablen" sind HashMap ähnliche Strukturen, in welche man Variablen speichern kann. Für Controller stehen folgende Scope-Variablen zur Verfügung:
 
  
===[http://docs.grails.org/latest/ref/Controllers/servletContext.html servletContext]: Statisch, für alle gleich===
+
*<code>[https://github.com/grails/grails-gsp org.grails.grails-gsp]</code> - Das Grails GSP-Plugin fügt die Vorkompilierung von GSP-Dateien für den Produktionseinsatz hinzu.
Dieser Scope lässt uns Daten speichern, welche '''über die gesamte Web-App gleich statisch erreichbar sind'''.<br>
 
Das [http://docs.grails.org/latest/ref/Controllers/servletContext.html servletContext]-Objekt ist eine Instanz
 
vom Standardmäßigen Java(EE)-Objekt [https://docs.oracle.com/javaee/7/api/javax/servlet/ServletContext.html ServletContext].
 
  
Es ist nützlich um:
+
*<code>[https://github.com/grails/grails-doc org.grails.grails-doc]</code> - Ein Plugin für Gradle zur Verwendung der Dokumentationsengine von Grails 2.0.
  
*'''Applikations-Eigenschaften''' zu speichern
+
*<code>[https://github.com/grails/grails-gradle-plugin org.grails.grails-plugin]</code> - Ein Plugin für Gradle zur Erstellung von Grails-Plugins.
*'''lokale Server-Ressourcen zu laden''' und
 
*Informationen vom Servlet-Container zu erhalten.
 
  
===[http://docs.grails.org/latest/ref/Controllers/session.html session]: Für jede Sitzung anders===
+
*<code>org.grails.grails-plugin-publish</code> - Ein Plugin für die Veröffentlichung von Grails-Plugins im zentralen Repository.
Das [http://docs.grails.org/latest/ref/Controllers/session.html session]-Objekt ist eine Instanz
 
von [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSession.html] der Java(EE) Servlet-API.
 
  
Es ist nützlich um '''Attribute der derzeitigen Sitzung eines Klientens zu speichern, wie zB. der Login (Name/Passwort).'''
+
*<code>org.grails.grails-profile</code> - Ein Plugin zur Verwendung beim Erstellen von Grails-Profilen.
  
===[http://docs.grails.org/latest/ref/Servlet%20API/request.html request]: Mitgesendete Informationen===
+
*<code>org.grails.grails-profile-publish</code> - Ein Plugin zur Veröffentlichung von Grails-Profilen im zentralen Repository.
Das [http://docs.grails.org/latest/ref/Servlet%20API/request.html request]-Objekt ist eine Instanz
+
 
von [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html HttpServletRequest] der Java(EE) Servlet API.
+
*<code>[https://mvnrepository.com/artifact/org.grails/grails-web org.grails.grails-web]</code> - Das Grails-Web-Gradle-Plugin konfiguriert Gradle so, dass es die Grails-Konventionen und die Verzeichnisstruktur versteht.
  
Es ist nützlich um:
 
  
*'''Mitgesendete''' [https://de.wikipedia.org/wiki/Liste_der_HTTP-Headerfelder '''Request-Header''']-'''Felderdaten''' zu bekommen
 
*'''Anfragen-bezogene Attribute zwischen-zu-speichern''' und
 
*Informationen des aktuellen Klienten zu erhalten
 
  
Grails fügt einige zusätzliche Funktionen zum [http://docs.grails.org/latest/ref/Servlet%20API/request.html request]-Objekt hinzu,
 
''die das standardmäßige [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html HttpServletRequest]-Objekt nicht hat,'' hinzu:
 
  
*<code>XML</code> - Eine Instanz der [http://groovy.codehaus.org/api/groovy/util/slurpersupport/GPathResult.html GPathResult] welches erlaubt einkommende XML-Anfragen zu verarbeiten (Parsen) - Nützlich für REST
+
=[http://gorm.grails.org/6.1.x/hibernate/manual/#introduction Objekt-Relationales Mapping (ORM)]=
*<code>JSON</code> - Eine Instanz des [http://docs.grails.org/3.2.x/apiorg/codehaus/groovy/grails/web/json/JSONObject.html JSONObject]s welches erlaubt einkommente JSON-Anfragen zu verarbeiten (Parsen) - Nützlich für JSON und REST
+
Domänenklassen sind der Kern jeder Geschäftsanwendung. Sie halten Zustand über Geschäftsprozesse und implementieren hoffentlich auch Verhalten. Sie sind durch Beziehungen miteinander verbunden; eins-zu-eins, eins-zu-viele oder viele-zu-viele.
*<code>isRedirected()</code> - Gibt <code>true</code> zurück wenn diese Anfrage nach einem Redirect fordert.
 
*<code>get</code> - Gibt <code>true</code> zurück wenn die Anfrage ein GET-Request ist
 
*<code>post</code> - Gibt <code>true</code> zurück wenn die Anfrage ein POST-Request ist
 
*<code>each</code> - Implementation von Groovys <code>each</code>-Methode um über gewisse Request-Objekte zu iterieren.
 
*<code>find</code> - Implementation von Groovys <code>find</code>-Methode um ein gewisses Request-Objekt zu finden (Ohne komplett richtigen Hashkey-Namen)
 
*<code>findAll</code> - Implementation von Groovys <code>findAll</code>-Methode um gewisse Request-Objekte zu finden (Ohne komplett richtigen Hashkey-Namen)
 
*<code>xhr</code> - Gibt <code>true</code> zurück wenn die Anfrage ein AJAX-Request ist.
 
  
Würde der Body der Anfrage folgenden XML-Code besitzen:
 
<syntaxhighlight lang="xml">
 
<book>
 
  <title>The Stand</title>
 
</book>
 
</syntaxhighlight>
 
Können wir dies ganz einfach mithilfe des <code>request</code>-Objekts serialisieren:
 
<syntaxhighlight lang="groovy">
 
def title = request.XML?.book.title // Mit dem "?" versichern wir uns, dass wenn die Variable "XML" "null" ist es zu keiner NullPointerException kommt und "title" einfach auch auf "null" gesetzt wird (Groovy feature :D)
 
render "The Title is $title"
 
</syntaxhighlight>
 
  
===([http://docs.grails.org/latest/ref/Controllers/params.html params]): (Veränderbare,) Mitgesendete CGI Informationen===
+
'''GORM ist die [https://de.wikipedia.org/wiki/Objektrelationale_Abbildung objektrelationale Mapping (ORM)]-Implementierung von Grails.'''
Eine veränderbare, mehrdimensionale HashMap von Anforderungsparametern (CGI).<br>
 
Obwohl das [http://docs.grails.org/latest/ref/Servlet%20API/request.html request]-Objekt auch Methoden zum lesen der Anforderungsparametern enthält,
 
ist diese manchmal nützlich für [http://docs.grails.org/latest/guide/theWebLayer.html#dataBinding Daten-Bindung an Objekte].
 
  
Nehmen wir an wir haben folgende Anfrage: <code>hello?foo=bar</code>. In unserer Controll-Action "hello" können wir den Wert von <code>foo</code> nun ganz einfach
+
Unter der Haube verwendet es '''Hibernate''' (eine sehr beliebte und flexible Open-Source-ORM-Lösung), und dank der dynamischen Natur von Groovy mit seiner statischen und dynamischen Typisierung sowie der Konvention von Grails ist bei der Erstellung von Grails-Domänenklassen '''weitaus weniger Konfiguration erforderlich'''.
ausgeben lassen:
 
<syntaxhighlight lang="groovy">
 
println params.foo
 
</syntaxhighlight>
 
===[http://docs.grails.org/latest/ref/Controllers/flash.html flash]: Speicher zwischen 2 Anfragen===
 
Eine temporäre Speicherzuordnung, in der Objekte innerhalb der Sitzung für die nächste Anforderung und nur für die nächste Anforderung gespeichert werden.
 
Die darin enthaltenen Objekte werden nach Abschluss der nächsten Anforderung automatisch gelöscht.
 
  
Mit diesem Muster jk Sie HTTP-Weiterleitungen verwenden (Nützlich für [http://www.theserverside.com/tt/articles/article.tss?l=RedirectAfterPost Redirect after Post])
 
und Werte beibehalten, die vom Flash-Objekt abgerufen werden können. Beispiel:
 
<syntaxhighlight lang="groovy">
 
class BookController {
 
  
    def index() {
+
'''In dieser Anleitung befasse ich mich mit''' ''(den in meinen Augen wichtigsten Wissen/..)'' '''der originalen Implementierung "GORM for Hibernate" in der Version 6.1.x (die mit Grails 3.3.9 ausgeliefert wird).'''  
        flash.message = "Welcome!"
 
        redirect(action: 'home')
 
    }
 
  
    def home() {}
+
''Es gibt auch noch andere Implementation wie "[http://gorm.grails.org/latest/mongodb/manual GORM for MongoDB]".''
}
 
</syntaxhighlight>
 
==Scope eines Controllers==
 
Neu erstellte Controller haben den Standard-Scope-Wert "singleton". Verfügbare Controller-Scopes sind:
 
  
*<code>prototype</code> (Standard) - Für jeden Request wird ein neuer Controller erstellt
+
Manche für mich eher unwichtige Kapitel werde ich hier nur insofern ansprechen, in dem ich einen Hyperlink mit dem Text "Siehe Offizielle Dokumentation" einfüge.
*<code>session</code> - Für jede Sitzung wird nur ein Controller-Objekt erstellt
 
*<code>singleton</code> - Für die gesamte Zeit gibt es nur eine Instanz des Controllers (Wird geraten für Aktionen mit Methoden)
 
  
Dieser Standard-Wert kann man unter <code>grails-app/conf/application.yml</code> wie folgt abändern:
+
<br />
<syntaxhighlight lang="yml">
 
grails:
 
    controllers:
 
        defaultScope: singleton
 
</syntaxhighlight>
 
Man kann den Scope eines Individuellen Controllers auch manuell ändern, indem man <code>static scope = "DEIN-SCOPE"</code> in den Quellcode des Controllers einfügt.
 
  
==[https://docs.grails.org/3.3.10/guide/single.html#modelsAndViews Model und Ansicht (Model and View)]==
+
==Versionsverlauf==
Ein "Model" ist eine Map, die die Ansicht (View) beim Rendern verwendet.
+
Siehe [http://gorm.grails.org/6.1.x/hibernate/manual/#releaseHistory Offizielle Dokumentation]
  
*Die Schlüssel der Map korrespondieren mit dem Variablen-Namen über dessen sie von zB. einer <code>.gsp</code>-Datei erreichbar sind.<br>
+
==Upgrade-Hinweise==
 +
Siehe [http://gorm.grails.org/6.1.x/hibernate/manual/#upgradeNotes Offizielle Dokumentation]
 +
<br />
  
Es gibt einige Wege um ein Model zu übergeben:
+
==[http://gorm.grails.org/6.1.x/hibernate/manual/#gettingStarted GORM einrichten]==
 +
Falls nicht bereits bei der Erstellung der Applikation getan, kann man "GORM 6.1.12 for Hibernate" wie folgt zu seinem Grails-Projekt hinzufügen:
  
*Explizit: Wenn seine Methode gleich wie die View-Datei heißt, übergibt man das Model einfach wie beim returnen mit folgendem Syntax: <code>[Var1: Data1, Var2: Data2, ...]</code>
+
*Folgende Abhängigkeiten in <code>build.gradle</code> einfügen:
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
def VIEW_NAME() {
+
// build.gradle
  [book: Book.get(params.id)] // Gleich wie render(view: "VIEW_NAME", model: [book: Book.get(params.id)])
+
dependencies {
 +
    compile "org.grails.plugins:hibernate5:6.1.12"
 +
    compile "org.hibernate:hibernate-ehcache"
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Eine erweiterte/bessere Weiße wäre es:
+
*''Falls man eine Grails-Version unter 3.3 nutzt ist man eventuell dazu gezwungen, die GORM-Version explizit zu setzen.''
 
+
**Bei der Nutzung von Grails 3.2.7 oder höher kann dies durch das setzen der <code>gormVersion</code>-Variable innerhalb von <code>gradle.properties</code> realisiert werden:<syntaxhighlight lang="properties">
*Springs [https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/ModelAndView.html ModelAndView]
+
# gradle.properties
<syntaxhighlight lang="groovy">
+
gormVersion=6.1.12.RELEASE
import org.springframework.web.servlet.ModelAndView
+
</syntaxhighlight><br />
 
+
**''Anderenfalls muss das setzen der Version durch das einfügen des folgenden Codes '''überhalb''' des <code>dependencies</code>-Block innerhalb der <code>build.gradle</code>-Datei realisiert werden:''<syntaxhighlight lang="groovy">
def index() {
+
// build.gradle
    // get some books just for the index page, perhaps your favorites
+
configurations.all {
    def favoriteBooks = ...
+
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
 
+
        if( details.requested.group == 'org.grails' &&
     // forward to the list view to show them
+
            details.requested.name.startsWith('grails-datastore')) {
     return new ModelAndView("/book/list", [ bookList : favoriteBooks ])
+
            details.useVersion("6.1.12.RELEASE")
 +
        }
 +
     }
 +
}
 +
dependencies {
 +
     ...
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
zu benutzen.
 
  
Zu beachten ist, dass bestimmte Variablennamen in Ihrem Modell nicht verwendet werden können:
+
===[http://gorm.grails.org/6.1.x/hibernate/manual/#_common_problems Häufige Probleme]===
 +
Wenn man einen Fehler erhält, der darauf hinweist, dass die <code>grails-datastore-simple</code>-Abhängigkeit nicht aufgelöst werden kann, muss man möglicherweise den folgenden Codeblock direkt '''''überhalb''' den <code>dependencies</code>-Block innerhalb der <code>build.gradle</code>-Datei einfügen:''<syntaxhighlight lang="groovy">
 +
// build.gradle
 +
configurations.all {
 +
    exclude module:'grails-datastore-simple'
 +
}
 +
dependencies {
 +
    ...
 +
}
 +
</syntaxhighlight><br />
  
*<code>attributes</code>
+
==Eine andere Hibernate-Version definieren==
*<code>application</code>
+
Siehe [http://gorm.grails.org/6.1.x/hibernate/manual/#hibernateVersions Offizielle Dokumentation]<br />
  
===Views selber selektieren mit der render()-Methode===
+
==GORM in einer Spring-Boot-Applikation verwenden==
In den vorherhigen 2 Beispielen haben wir nirgendwo angegeben an welche [http://docs.grails.org/latest/guide/single.html#gsp view] die Map-Daten übergeben werden soll.<br>
+
Siehe [http://gorm.grails.org/6.1.x/hibernate/manual/#springBoot Offizielle Dokumentation]<br />
Dies liegt an Grails Konventionen. Im folgenden Beispiel würde Grails unter <code>grails-app/views/book/show.gsp</code> nachsuchen: ''(Nach dem Schema <code>grails-app/views/CONTROLLER/AKTION.gsp</code>)''
+
 
<syntaxhighlight lang="groovy">
+
==[http://gorm.grails.org/6.1.x/hibernate/manual/#outsideGrails GORM ausserhalb von Grails oder Spring verwenden]==
class BookController {
+
Wenn man GORM für Hibernate außerhalb einer Grails-Anwendung verwenden möchten, sollte man die notwendigen Abhängigkeiten für GORM und die von einem verwendete Datenbank deklarieren, z.B. in Gradle:<syntaxhighlight lang="groovy">
     def show() {
+
compile "org.grails:grails-datastore-gorm-hibernate5:6.1.12.RELEASE"
        [book: Book.get(params.id)]
+
runtime "com.h2database:h2:1.4.192"
 +
runtime "org.apache.tomcat:tomcat-jdbc:8.5.0"
 +
runtime "org.apache.tomcat.embed:tomcat-embed-logging-log4j:8.5.0"
 +
runtime "org.slf4j:slf4j-api:1.7.10"
 +
</syntaxhighlight><blockquote>Das obere Beispiel verwendet die [https://de.wikipedia.org/wiki/H2_Database H2-Datenbank] und den Tomcat-Verbindungspool. Es werden jedoch auch andere Pool-Implementierungen unterstützt, einschließlich <code>[https://commons.apache.org/proper/commons-dbcp/ commons-dbcp]</code>, <code>[https://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#Introduction tomcat pool]</code> oder <code>[https://github.com/brettwooldridge/HikariCP hikari]</code>. 
 +
 
 +
Wenn kein Verbindungspool angegeben ist, wird <code>org.springframework.jdbc.datasource.DriverManagerDataSource</code> verwendet, die bei jeder Verbindungsanforderung eine neue Verbindung zur Datenbank herstellt. Dies wird wahrscheinlich Probleme mit einer H2-In-Memory-Datenbank verursachen, da bei jeder Verbindungsanforderung eine neue In-Memory-Datenbank erstellt wird, wodurch zuvor erstellte Tabellen verloren gehen. Normale Datenbanken (MySql, Postgres oder sogar dateibasierte H2-Datenbanken) sind nicht betroffen. </blockquote>
 +
 
 +
Erstellen Sie dann Ihre Entities im Verzeichnis <code>src/main/groovy</code> und annotieren Sie sie mit der Annotation <code>grails.gorm.annotation.Entity</code>:<syntaxhighlight lang="groovy">
 +
// Die Verwendung von GormEntity dient lediglich der Unterstützung der IDE außerhalb von Grails.
 +
// Bei Verwendung innerhalb eines Grails-Kontextes verwenden einige IDEs die Position der grails-app/domain als Hinweis, um die Code-Vervollständigung zu ermöglichen.
 +
@Entity
 +
class Person implements GormEntity<Person> {  
 +
     String firstName
 +
    String lastName
 +
    static constraints = {
 +
        firstName blank:false
 +
        lastName blank:false
 
     }
 
     }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Falls wir aber nicht wollen, das Grails sich den Namen der View-Datei mithilfe des Methoden-Namens sucht, können wir die vielfältige [http://docs.grails.org/latest/ref/Controllers/render.html render()]-Methode einsetzen:
+
Schlussendlich muss man die Bootstrap-Logik in seine Applikation einfügen, die <code>HibernatDatastore</code> verwendet: (Siehe die [http://gorm.grails.org/6.1.x/hibernate/manual/#configuration Offzielle Dokumentation] für mehr Informationen zur Konfiguration von GORM)<syntaxhighlight lang="groovy">
<syntaxhighlight lang="groovy">
+
import org.grails.orm.hibernate.HibernateDatastore
def show() {
+
Map configuration = [
    def map = [book: Book.get(params.id)]
+
    'hibernate.hbm2ddl.auto':'create-drop',
    render(view: "display", model: map)
+
    'dataSource.url':'jdbc:h2:mem:myDB'
 +
]
 +
HibernateDatastore datastore = new HibernateDatastore( configuration, Person)
 +
</syntaxhighlight><br />
 +
==[http://gorm.grails.org/6.1.x/hibernate/manual/#configuration Konfiguration]==
 +
Siehe [[Groovy als Web-Backend#DataSource|das bereits besprochene Kapitel "DataSource"]] und die [http://gorm.grails.org/6.1.x/hibernate/manual/#configuration Offizielle Dokumentation] <br />
 +
 
 +
===Standard-Mappings und Constraints===
 +
Besondere Erwähnung verdienen die Einstellungen <code>grails.gorm.default.mapping</code> und <code>grails.gorm.default.constraints</code>. Diese definieren die Standard-ORM-Zuordnungen/DSL's und die von jeder Entität verwendeten Standard-Validierungseinschränkungen.
 +
 
 +
<br />
 +
====Ändern der Standard-Datenbankzuordnung====
 +
Möglicherweise hat man den Grund, die Art und Weise zu ändern, wie '''alle''' Domänenklassen auf die Datenbank abgebildet werden. 
 +
 
 +
Beispielsweise verwendet GORM standardmäßig die <code>native</code> "ID-Generierungsstrategie" der Datenbank, unabhängig davon, ob es sich dabei um eine Spalte mit automatischer Erhöhung oder eine Sequenz handelt.
 +
 
 +
Wenn man alle Domänenklassen global ändern möchten, um eine uuid-Strategie zu verwenden, kann man dies in der Standardzuordnung angeben:<syntaxhighlight lang="groovy">
 +
// grails-app/conf/application.groovy (Da es sich bei der Einstellung um eine Groovy-Konfiguration handelt, muss sie in ein Groovy-bewusstes Konfigurationsformat gehen.)
 +
grails.gorm.default.mapping = {
 +
        cache true
 +
        id generator:'uuid'
 +
}
 +
</syntaxhighlight>Wie man sieht kann man eine Closure zuweisen die [http://gorm.grails.org/6.1.x/hibernate/manual/#ormdsl dem <code>mapping</code>-Block entspricht, mit dem man normalerweise die Zuordnung einer Domänenklasse zu einer Datenbanktabelle anpassen würde].
 +
<br />
 +
 
 +
====Ändern der Standard-Validierungseinschränkungen====
 +
Zur Validierung wendet GORM einen Standardsatz von [http://gorm.grails.org/6.1.x/hibernate/manual/#constraints Einschränkungen] auf alle Domänenklassen an.
 +
 
 +
Beispielsweise sind standardmäßig alle Eigenschaften von GORM-Klassen nicht <code>nullable</code>. Das bedeutet, dass für jede Eigenschaft ein Wert angegeben werden muss (Ansonsten kommt es zu einem Validierungsfehler).
 +
 
 +
Man kann die Standardbeschränkungen über die Groovy-Konfiguration mit der Einstellung <code>grails.gorm.default.constraints</code> ändern:<syntaxhighlight lang="groovy">
 +
// grails-app/conf/application.groovy
 +
grails.gorm.default.constraints = {
 +
    '*'(nullable: true, size: 1..20) // '*' makes EVERY PROPERTY have these contraints
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
''(Anmerkung: Wenn Grails keine <code>.gsp</code>-Datei finden kann, sucht es auch nach <code>.jsp</code>-Dateien.)''
 
  
===Views für Namespaced-Controller selektieren===
+
==Quick-Start Anleitung (Grundlegends CRUD)==
Wenn ein Controller einen eigenen [http://docs.grails.org/latest/guide/single.html#namespacedControllers namespace] gesetzt hat (In diesem Beispiel '<code>business</code>'), schaut Grails
+
Siehe [http://gorm.grails.org/6.1.x/hibernate/manual/#quickStartGuide Offizielle Dokumentation]
  
*zuerst nach ob die View-Datei unter <code>grails-app/views/business/...</code> zu finden ist
+
<br />
*und falls nicht sucht es unter dem normalen Namespace <code>grails-app/views/...</code> nach.
+
==[http://gorm.grails.org/6.1.x/hibernate/manual/#domainClasses Domain-Modelling in GORM: Einführung]==
  
Beispiel:
+
Beim Erstellen von Anwendungen muss man die Problemdomäne berücksichtigen, die man zu lösen versucht. Wenn man zum Beispiel eine Buchhandlung im Amazon-Stil aufbaut denkt man an Bücher, Autoren, Kunden und Verleger, um nur einige zu nennen.
<syntaxhighlight lang="groovy">
 
class ReportingController {
 
    static namespace = 'business'
 
  
    def humanResources() {
+
'''Diese werden in GORM als Groovy-Klassen modelliert''', so dass eine Buchklasse einen Titel, ein Erscheinungsdatum, eine ISBN-Nummer usw. haben kann. Die nächsten Abschnitte zeigen, wie Domänen in GORM modelliert werden können.
        // This will render grails-app/views/business/reporting/humanResources.gsp
 
        // if it exists.
 
  
        // If grails-app/views/business/reporting/humanResources.gsp does not
+
Betrachte man z.B. die folgende Domänenklasse:
        // exist the fallback will be grails-app/views/reporting/humanResources.gsp.
+
{| class="wikitable"
 +
|+Beispiel einer simplen Domänenklasse - ohne Assoziationen zu anderen Klassen. Nur simple Java-Eigenschaften.
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
|-
 +
|<syntaxhighlight lang="groovy">
 +
package org.bookstore
  
        // The namespaced GSP will take precedence over the non-namespaced GSP.
+
class Book {
 +
    String title
 +
    Date releaseDate
 +
    String ISBN
 +
}
 +
</syntaxhighlight>
 +
|[[Datei:Grails-gorm-simple book example-db schema view.png|zentriert]]
 +
|}
 +
Diese Klasse wird automatisch auf eine Tabelle in der Datenbank namens <code>book</code> '''(derselbe Name wie die Klasse)''' abgebildet. (Dieses Verhalten ist über die [http://gorm.grails.org/6.1.x/hibernate/manual/#ormdsl ORM-Domänenspezifische Sprache (ORM-DSL)] anpassbar)
  
        [numberOfEmployees: 9]
+
'''Jede Java-Eigenschaft wird auf eine Spalte in der Datenbank abgebildet''', wobei die Konvention für Spaltennamen "Kleinbuchstaben die durch Unterstriche getrennt sind" lautet.
    }
 
  
 +
*'''Zum Beispiel bildet <code>releaseDate</code> auf eine Spalte <code>release_date</code> ab.'''
 +
*'''Die SQL-Typen werden automatisch von den Java-Typen erkannt''', können aber mit [http://gorm.grails.org/6.1.x/hibernate/manual/#constraints Constraints] oder der [http://gorm.grails.org/6.1.x/hibernate/manual/#ormdsl ORM-DSL] angepasst werden.
  
    def accountsReceivable() {
+
<br />
        // This will render grails-app/views/business/reporting/numberCrunch.gsp
+
==[http://gorm.grails.org/6.1.x/hibernate/manual/#gormAssociation Domain-Modelling in GORM: Einführung Assoziationen]==
        // if it exists.
+
Assoziationen definieren wie Domänenklassen miteinander interagieren. Wenn nicht an beiden Enden explizit angegeben, existiert eine Beziehung nur in der Richtung, in der sie definiert ist.
  
        // If grails-app/views/business/reporting/numberCrunch.gsp does not
+
===Unidirektionales Viele-zu-Eins ohne Kaskadierung===
        // exist the fallback will be grails-app/views/reporting/numberCrunch.gsp.
+
In diesem Fall haben wir eine '''uni'''direktionale Viele-zu-Eins-Beziehung von <code>Face</code>-to-<code>Nose</code>.
 
+
{| class="wikitable"
        // The namespaced GSP will take precedence over the non-namespaced GSP.
+
|+Beispiel einer Unidirektionalen Viele-zu-Eins Beziehung von <code>Face</code> zu <code>Nose</code>.
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
class Face {
 +
    Nose nose
 +
}
  
        render view: 'numberCrunch', model: [numberOfEmployees: 13]
+
class Nose {}
    }
 
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|Bemerke: Der Fremdschlüssel liegt beim Besitzer (vgl. Eins-zu-Eins mithilfe von hasOne)[[Datei:Grails-gorm-unidirectional many to one-db schema view.png|zentriert]]
 +
|[[Datei:Grails-gorm-unidirectional many to one-dependency association view.png|zentriert|alternativtext=|219x219px]]
 +
|}
 +
{| class="wikitable"
 +
|+Beispiel-Code der Unidirektionalen Viele-zu-Eins Beziehung von <code>Face</code> zu <code>Nose</code>, inklusive die daraus resultierende Datenbankansicht (IntelliJ).
 +
!Beispiel-Code
 +
!Resultat
 +
|-
 +
|<syntaxhighlight lang="groovy">
 +
Face erroredFace = new Face()
 +
erroredFace.save(flush:true)
 +
erroredFace.errors.allErrors.each { println it } // Prints "Field error in object 'grailstesting.Face' on field 'nose': rejected value [null]; codes [...(list of i18n-codes)...]"
  
==[https://docs.grails.org/3.3.10/guide/single.html#redirectsAndChaining Redirects]==
+
Nose myNose = new Nose()
Aktionen können mithilfe der Controller-Methode <code>[http://docs.grails.org/latest/ref/Controllers/redirect.html redirect()]</code> umgeleitet werden.
+
new Face(nose: myNose).save()
Beispiel:
 
<syntaxhighlight lang="groovy">
 
class OverviewController {
 
  
    def login() {}
+
Face myFace = new Face(nose: myNose).save() // Creating a new and valid Face-Instance and saving it
 
+
myFace.setNose(new Nose()) // Updating/Editing it
    def find() {
+
myFace.save() // Successfully saving new data also means bumping up the internal 'version' property.
        if (!session.user)
 
            redirect(action: 'login')
 
            return
 
        }
 
        ...
 
    }
 
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|[[Datei:Grails-gorm-unidirectional many to one-example code-resulting table view.png|zentriert]]
 +
|-
 +
|<syntaxhighlight lang="groovy">
 +
Face myFace = new Face()
 +
Nose myNose = new Nose()
 +
myFace.setNose(myNose)
  
Auf welche Seite <code>[http://docs.grails.org/latest/ref/Controllers/redirect.html redirect()]</code> umleitet, kann man Grails auf mehrere Weisen mitteilen:
+
myFace.save() // The saving of 'myFace' also triggers the saving of the associated 'nose' property
  
*Der Name der Aktion (Sowie den namens des Controllers, falls sich die Aktion in einem anderen Controller befindet)
+
myFace.delete() // Only 'myFace' gets deleted from the Database. 'myNose' stays because it isn't hardly bond to belong to any domain-class, meaning it is allowed to exist on its own and live a happy live.
<syntaxhighlight lang="groovy">
 
// Redirects to the "index()"-Action in the "home"-Controller
 
redirect(controller: 'home', action: 'index')
 
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|[[Datei:Grails-gorm-unidirectional many to one-example code2-resulting table view.png|alternativtext=|zentriert]]
 +
|}
  
*URI zu einer Resource relativ vom Kontext-Pfad:
 
<syntaxhighlight lang="groovy">
 
// Redirect to an explicit URI
 
redirect(uri: "/login.html")
 
</syntaxhighlight>
 
  
*Eine Komplette URL:
+
===Bidirektionales Viele-Zu-Eins mithilfe von belongsTo===
<syntaxhighlight lang="groovy">
+
Um die obige Beziehung '''bi'''direktional zu gestalten, definieren wir die andere Seite (Nose) wie folgt
// Redirect to a URL
+
{| class="wikitable"
redirect(url: "http://grails.org")
+
|+Beispiel einer Bidirektionalen Viele-zu-Eins Beziehung von <code>Face</code> zu <code>Nose</code>.
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
class Face {
 +
    Nose nose
 +
}
 +
 
 +
class Nose {
 +
    static belongsTo = [face:Face]
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|Bemerke: Die Struktur bleibt gleich wie beim unidirektionalen Viele-zu-Eins ohne Kaskadierung![[Datei:Grails-gorm-unidirectional many to one-db schema view.png|zentriert]]
 +
|Bemerke: Die Struktur bleibt gleich wie beim unidirektionalen Viele-zu-Eins ohne Kaskadierung![[Datei:Grails-gorm-unidirectional many to one-dependency association view.png|zentriert|alternativtext=|219x219px]]
 +
|}
 +
In diesem Fall verwenden wir die Einstellung <code>belongsTo</code>, um zu sagen, dass <code>Nose</code> zum <code>Face</code> "gehört": <blockquote><code>belongsTo</code> definiert eine "Zugehörigkeit zu" -Beziehung, in der die von <code>belongsTo</code> angegebene Klasse das Eigentum an der Beziehung übernimmt. Dadurch wird gesteuert, wie die Aktionen des Speicherns und Löschen kaskadieren. Das genaue Verhalten hängt von der Art der Beziehung ab:
 +
 +
*Viele-zu-Eins / Eins-zu-Eins: Die Aktionen '''Speichern und Löschen kaskadieren''' vom Eigentümer zum Abhängigen (die Klasse mit <code>belongsTo</code>).
 +
*Eins-zu-viele: Die Aktion des '''Speichern kaskadiert''' immer von der "Eins" Seite zur "Viele" Seite. Wenn die "Viele" Seite auch ein <code>belongsTo</code> besitzt, kaskadiert auch das Löschen in diese Richtung gelöscht.
 +
*Viele-zu-Viele: Nur die Aktion des '''Speichern kaskadiert''' vom Eigentümer zum Abhängigen, '''das Löschen kaskadiert hingegen nie.'''
 +
</blockquote>
 +
{| class="wikitable"
 +
|+Beispiel-Code der Bidirektionalen Viele-zu-Eins Beziehung von <code>Face</code> zu <code>Nose</code>, inklusive die daraus resultierende Datenbankansicht (IntelliJ).
 +
!Beispiel-Code
 +
!Resultat
 +
|-
 +
|<syntaxhighlight lang="groovy">
 +
Face myFace = new Face()
 +
Nose myNose = new Nose()
 +
myFace.setNose(myNose)
 +
 +
myFace.save() // The saving of 'myFace' also triggers the saving of the associated 'nose' property
  
Parameter können von einer zu der anderen Aktion mithilfe des <code>params</code>-Arguments übergeben werden:
+
myFace.delete() // BOTH 'myFace' and its associated 'nose' get deleted from the Database, because a 'Nose' can't exist without a 'Face' thanks to its 'belongsTo'-Instruction.
<syntaxhighlight lang="groovy">
 
redirect(action: 'myaction', params: [myparam: "myvalue"])
 
 
</syntaxhighlight>
 
</syntaxhighlight>
Diese werden dann in den Controller-Scope <code>[http://docs.grails.org/latest/ref/Controllers/params.html params]</code> (Siehe oben) gespeichert.
+
|[[Datei:Grails-gorm-bidirectional many to one-example code-resulting table view.png|alternativtext=|zentriert]]
 +
|-
 +
|Beachte: Die Umgekehrte Vorgehens-weiße des Löschens ist aufgrund des transienten <code>Face</code>'s ungültig und resultiert in einen Fehler:<syntaxhighlight lang="groovy">
 +
Nose myNose = new Nose()
 +
Face myFace = new Face(nose: myNose)
 +
myFace.save() // As above: The saving of 'myFace' also triggers the saving of the associated 'nose' property
  
===Aktionen aneinander-reihen===
+
myNose.delete() // Results in the error mentioned in the next column
<code>[http://docs.grails.org/3.1.1/ref/Controllers/chain.html chain()]</code> fungiert ähnlich wie <code>redirect()</code>,
+
</syntaxhighlight>Jedoch:
indem es auf eine andere Aktion springt und diese ausführt. <br>
+
|<syntaxhighlight>
Chaining hingegen erlaubt es uns das Model beizubehalten und Model-Daten '''zusätzlich''' zu übergeben.
+
org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [grailstesting.Nose#1]
 +
</syntaxhighlight>
 +
|-
 +
|Durch das Hinzufügen des folgenden Code's in die Domänenklasse <code>Face</code>...<syntaxhighlight lang="groovy">
 +
static mapping = {
 +
    // See http://docs.grails.org/latest/ref/Database%20Mapping/cascade.html
 +
    nose cascade: 'none'
 +
}
 +
static constraints = { nose(nullable: true) }
 +
</syntaxhighlight>...Kann die obige <code>myFace</code>-Instanz immer-noch bestehen bleiben wenn dessen <code>nose</code>-Instanz sich selber löscht.
 +
|[[Datei:Grails-gorm-bidirectional many to one-example code2-resulting table view.png|alternativtext=|zentriert]]
 +
|-
 +
|<syntaxhighlight lang="groovy">
 +
Nose myNose = new Nose();
 +
new Face(nose: myNose).save();
  
Beispiel:
+
// Thanks to the bidirectionality, a Nose can also access the Face it's associated to/it belongs to:
<syntaxhighlight lang="groovy">
+
println(myNose.face) // "grailstesting.Face : 1"
class ExampleChainController {
+
println(myNose.faceId) // "1" (Variable injected by GORM DSL for convencience)
 +
</syntaxhighlight>
 +
|
 +
|}<br />
 +
===Unidirektionales Viele-Zu-Eins mithilfe von belongsTo===
 +
Eine Sache, die Menschen oft verwirrt, ist, dass  <code>belongsTo</code> zwei verschiedene Syntaxen unterstützt.
  
    def first() {
+
Die oben verwendete Syntax definiert nicht nur eine einfache Kaskadierung zwischen zwei Klassen, sondern fügt auch eine entsprechende Rückrefrenz zum Modell hinzu, wodurch die Beziehung automatisch in eine bidirektionale Beziehung umgewandelt wird.
        chain(action: second, model: [one: 1])
 
    }
 
  
    def second () {
+
Um einfach nur das Kaskadierungsverhalten zu definieren (ohne Rückrefrenz auf den "Eigentümer") kann man folgende Syntax benutzen:
        chain(action: third, model: [two: 2])
+
{| class="wikitable"
    }
+
|+Beispiel einer Unidirektionalen Viele-zu-Eins Beziehung von <code>Face</code> zu <code>Nose</code>.
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
class Face {
 +
    Nose nose
 +
}
 +
 
 +
class Nose {
 +
    static belongsTo = Face
 +
}
 +
</syntaxhighlight>
 +
|Bemerke: Die Struktur bleibt gleich wie beim bidirektionalen Many-to-One![[Datei:Grails-gorm-unidirectional many to one-db schema view.png|zentriert]]
 +
|Bemerke: Die Struktur bleibt gleich wie beim bidirektionalen Many-to-One![[Datei:Grails-gorm-unidirectional many to one-dependency association view.png|zentriert|alternativtext=|219x219px]]
 +
|}
 +
 +
 
 +
===Unidirektionales Eins-zu-Eins mithilfe von belongsTo===
 +
Beim ersten Beispiel der "Viele-zu-Eins" hat man sich vielleicht gefragt, warum diese Assoziation eine Viele-zu-Eins und keine Eins-zu-Eins ist. Der Grund ist, dass es möglich ist, das man mehrere Instanzen von <code>Face</code> dieselbe Instanz von <code>Nose</code> zuordnen kann. Wenn man diese Zuordnung als echte Eins-zu-Eins-Zuordnung definieren möchten, ist eine <code>unique</code> Einschränkung erforderlich:
 +
{| class="wikitable"
 +
|+Beispiel einer Unidirektionalen Eins-zu-Eins Beziehung von <code>Face</code> zu <code>Nose</code>.
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
class Face {
 +
}
  
     def third() {
+
class Nose {
         [three: 3]
+
     static belongsTo = [face: Face]
 +
    static constraints = {
 +
         face unique:true
 
     }
 
     }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Resultiert beim Aufrufen von "/examplechain/first" in das Model
+
|[[Datei:Grails-GORM-unidirectional one to one-db schema view.png|alternativtext=|zentriert]]
<syntaxhighlight lang="groovy">
+
|[[Datei:Grails-GORM-unidirectional one to one-association view.png|alternativtext=|zentriert|215x215px]]
[one: 1, two: 2, three: 3]
+
|}
</syntaxhighlight>
+
 +
 
 +
===Bidirektionales Eins-zu-Eins mithilfe von hasOne===
 +
<blockquote><code>[http://docs.grails.org/3.3.9/ref/Domain%20Classes/hasOne.html hasOne]</code> definiert eine bidirektionale Eins-zu-Eins-Zuordnung zwischen zwei Klassen, in denen sich der Fremdschlüssel im Kind befindet.</blockquote>
 +
{| class="wikitable"
 +
|+Beispiel einer Bidirektionalen Eins-zu-Eins Beziehung von <code>Face</code> zu <code>Nose</code>.
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
class Face {
 +
    static hasOne = [nose:Nose]
 +
}
  
Wie beim redirect kann man hier auch weitere Daten in das <code>[http://docs.grails.org/latest/ref/Controllers/params.html params]</code>-Scope einspeisen:
+
class Nose {
<syntaxhighlight lang="groovy">
+
    Face face
chain(action: "action1", model: [one: 1], params: [myparam: "param1"])
+
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|[[Datei:Grails-gorm-bidirectional one to one-db schema view.png|alternativtext=|zentriert]]
 +
|[[Datei:Grails-gorm-bidirectional one to one-dependency association view.png|alternativtext=|zentriert|224x224px]]
 +
|}
 +
Beachte: Die Verwendung dieser Eigenschaft verlagert den Fremdschlüssel auf die Kindes-Tabelle, so dass in diesem Fall die Fremdschlüsselspalte in der <code>nose</code>-Tabelle innerhalb einer Spalte namens <code>face_id</code> gespeichert wird.
  
==Die [https://docs.grails.org/latest/ref/Controllers/render.html render()]-Methode==
 
Die render()-Methode ist sehr flexibel und kann viele Argumente hingegennehmen (Vom anzeigen eines einfachen Textes bis hin zur render eines Views/Templates.).<br>
 
<syntaxhighlight lang="groovy">
 
// renders text to response
 
render "some text"
 
  
// renders text for a specified content-type/encoding
+
(Alle Beispiel-Codes vom obigen "Bidirektionale Many-To-One" verhalten sich auch hier gleich)
render(text: "<xml>some xml</xml>", contentType: "text/xml", encoding: "UTF-8")
 
  
// render a template to the response for the specified model
+
Letztendlich ist es eine gute Idee, eine <code>unique</code>-Einschränkung auf einer Seite der Eins-zu-Eins-Beziehung hinzuzufügen:<syntaxhighlight lang="groovy">
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
+
class Face {
render(template: "book", model: [book: theShining])
+
    static hasOne = [nose:Nose]
  
// render each item in the collection using the specified template
+
    static constraints = {
render(template: "book", collection: [b1, b2, b3])
+
        nose unique: true
 +
    }
 +
}
 +
</syntaxhighlight>
  
// render a template to the response for the specified bean
 
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
 
render(template: "book", bean: theShining)
 
  
//! render the view with the specified model
+
===[http://gorm.grails.org/6.1.x/hibernate/manual/#_controlling_the_ends_of_the_association Kontrollieren der Enden von Assoziationen]===
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
+
Gelegentlich kann es vorkommen, dass man sich mit Domänen-klassen konfrontiert sieht, die '''mehrere Eigenschaften desselben Typs''' haben. Sie können sogar '''selbst-referenziell''' sein, d.h. die Assoziationseigenschaft hat denselben Typ wie die Domänen-klasse, in der sie sich befindet. '''Solche Situationen können Probleme verursachen, weil GORM den Typ der Assoziation möglicherweise falsch errät.''' Betrachten Sie diese einfache Klasse
render(view: "viewName", model: [book: theShining])
+
{| class="wikitable"
 +
|+Beispiel einer Domänen-klasse die mehrere Eigenschaften des gleichen Typen trägt, die sogar noch vom selben Typen wie die Domänen-klasse selbst sind (selbst-referenziell)
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
class Person {
 +
    String name
 +
    Person parent
  
// render the view with the controller as the model
+
    // Bildet die rechts zu sehende, selbst-referenzielle Eins-zu-Eins Assoziation
render(view: "viewName")
+
    static belongsTo = [ supervisor: Person ]
  
// render some markup to the response
+
    static constraints = {  
render {
+
        supervisor nullable: true
    div(id: "myDiv", "some text inside the div")
+
        parent nullable: true
}
 
 
 
// render some XML markup to the response
 
render(contentType: "text/xml") {
 
    books {
 
        for (b in books) {
 
            book(title: b.title, author: b.author)
 
        }
 
 
     }
 
     }
 
}
 
}
 +
</syntaxhighlight>
 +
|[[Datei:Grails-GORM-self referential and multiple fields with same types-db schema view.png|alternativtext=|zentriert]]
 +
|[[Datei:Grails-GORM-self referential and multiple fields with same types-association view.png|alternativtext=|zentriert|203x203px]]
 +
|}
 +
Für GORM sind die Eigenschaften <code>parent</code> und <code>supervisor</code> zwei Richtungen derselben Zuordnung. Wenn man also die <code>parent</code> Eigenschaft für eine Personen-Instanz festlegt, legt GORM automatisch die <code>supervisor</code> -Eigenschaft für die andere Personen-Instanz fest. Dies mag ggf. das sein was man will, aber wenn man sich die Klasse ansieht haben wir tatsächlich zwei unidirektionale Beziehungen.
  
//! render a JSON ( http://www.json.org ) response with the builder attribute:
+
Um GORM zur richtigen Zuordnung zu führen kann man über die Eigenschaft <code>mappedBy</code> feststellen, dass eine bestimmte Zuordnung unidirektional ist:<blockquote>Mit der statischen Eigenschaft [http://docs.grails.org/3.3.9/ref/Domain%20Classes/mappedBy.html <code>mappedBy</code>] können Sie steuern, ob eine Zuordnung als unidirektional oder bidirektional zugeordnet wird und welche Eigenschaften bei bidirektionalen Zuordnungen die umgekehrte Richtung bilden.</blockquote>
render(contentType: "application/json") {
+
{| class="wikitable"
     book(title: b.title, author: b.author)
+
!Domänenklasse
}
+
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
class Person {
 +
     String name
 +
    Person parent
  
//! render with status code
+
    static belongsTo = [ supervisor: Person ]
render(status: 503, text: 'Failed to update book ${b.id}')
+
   
 +
    // Die Eigenschaft "none" wird als umgekehrte Richtung der Zuordnung (oder als "Rückverweis") behandelt.
 +
    static mappedBy = [ supervisor: "none", parent: "none" ]
  
//! render a file
+
    static constraints = {
render(file: new File(absolutePath), fileName: "book.pdf")
+
        supervisor nullable: true
 +
        parent nullable: true
 +
    }
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|Bemerke: Die Struktur bleibt gleich![[Datei:Grails-GORM-self referential and multiple fields with same types-db schema view.png|alternativtext=|zentriert]]
 +
|Bemerke: Die Struktur bleibt gleich![[Datei:Grails-GORM-self referential and multiple fields with same types-association view.png|alternativtext=|zentriert|203x203px]]
 +
|}
  
 +
Man kann "none" auch durch einen beliebigen Eigenschaftsnamen der Zielklasse ersetzen. Und dies funktioniert natürlich auch für normale Domänenklassen, nicht nur für selbst-referenzielle. Die Eigenschaft <code>mappedBy</code> ist auch nicht auf viele-zu-eins- und eins-zu-eins-Zuordnungen beschränkt - Sie funktioniert auch für Eins-zu-viele- und viele-zu-viele-Zuordnungen
  
==Data Binding==
 
"Data-Binding" ist das Verfahren, einkommende Request-Parameter an ein Objekt zu binden.
 
  
*"Data-Binding" sollte auch die Konversion von String auf den jeweiligen Typen übernehmen können, weil alle Request-Parameter ja nur Strings sind aber ein Objekt ja auch Integers (etc) haben kann.
+
===Kollektionen einer bidirektionalen Eins-zu-Viele-Klasse leeren/ersetzen===
 +
Betrachte man die folgende GORM-Entitäten:
 +
{| class="wikitable"
 +
|+Beispiel einer Bidirektionalen Eins-zu-Viele Beziehung von <code>Book</code> zu <code>Review</code>.
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
class Book {
 +
    String name
 +
    static hasMany = [reviews: Review]
 +
}
 +
class Review {
 +
    String author
 +
    String quote
 +
    static belongsTo = [book: Book]
 +
}
 +
</syntaxhighlight>
 +
|[[Datei:Grails-GORM-bidirectional one to many-db schema view.png|alternativtext=|zentriert]]
 +
|[[Datei:Grails-GORM-bidirectional one to many-association view.png|alternativtext=|zentriert|202x202px]]
 +
|}
  
<syntaxhighlight lang="groovy">
+
Mit dieser Struktur kann man wie folgt einen Beispiel-Datensatz aus einem Buch mit 2 Reviews anlegen:<syntaxhighlight lang="groovy" line="1">
// Klasse
+
new Book(name: 'Daemon')
class Person {
+
    .addToReviews(new Review(quote: 'Daemon does for surfing the Web what Jaws did for swimming in the ocean.', author: 'Chicago Sun-Times'))
     String firstName
+
    .addToReviews(new Review(quote: 'Daemon is wet-yourself scary, tech-savvy, mind-blowing!', author: 'Paste Magazine'))
     String lastName
+
    .save()
     Integer age
+
</syntaxhighlight>Einfach, simpel und logisch. Was jetzt aber, wenn man alle <code>reviews</code> löschen (und von der Assoziationen entfernen) oder durch eine neue Liste ersetzen möchte? Mit der jetzigen ORM-Definition würde man z.B. die folgenden Hilfsfunktionen basteln:<syntaxhighlight lang="groovy" line="1">
 +
Book replaceReviews(Serializable idParam, List<Review> newReviews) {
 +
     Book book = Book.where { id == idParam }.join('reviews').get()
 +
    clearReviews(book)
 +
     newReviews.each { book.addToReviews(it) }
 +
     book.save()
 
}
 
}
  
// Bindung
+
void clearReviews(Book book) {
def bindingMap = [firstName: 'Peter', lastName: 'Gabriel', age: 63]
+
    List<Serializable> ids = []
 +
    book.reviews.collect().each {
 +
        book.removeFromReviews(it)
 +
        ids << it.id
 +
    }
 +
    Review.executeUpdate("delete Review r where r.id in :ids", [ids: ids])
 +
}
 +
</syntaxhighlight>Alternativ könnte man auch mithilfe der <code>mapping</code>-Eigenschaft <code>cascade</code> das Verhalten des Kaskadierens einer Lösch/Speichern-Aktion ändern:<blockquote>Zur Verfügung stehen die Werte <code>all</code>, <code>merge</code>, <code>save-update</code>, <code>delete</code>, <code>lock</code>, <code>refresh</code>, <code>evict</code> (See GORM-Operation [https://docs.jboss.org/hibernate/core/3.6/javadocs/org/hibernate/Session.html#evict(java.lang.Object) Session#evict(Object object)]), <code>replicate</code> und <code>all-delete-orphan</code> (Nur gültig für Eins-zu-Viele Assoziationen). Man kann auch mehrere Werte angeben indem man diese durch ein Kommata trennt.
  
def person = new Person(bindingMap)
+
<u>Standardmäßig konfiguriert GORM eine Kaskadenrichtlinie von</u>
  
assert person.firstName == 'Peter'
+
*"all" für den Fall, dass eine Entität "zu einer anderen gehört" (belongsTo),
assert person.lastName == 'Gabriel'
+
*"save-update" für Szenarien ohne existierende "gehört zu" (belongsTo)-Anweisung
assert person.age == 63
+
</blockquote>
 +
{| class="wikitable"
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
class Book {
 +
    String name
 +
    static hasMany = [reviews: Review]
 +
    static mappping = {
 +
        reviews cascade: 'all-delete-orphan'
 +
    }
 +
}
 +
class Review {
 +
    String author
 +
    String quote
 +
    static belongsTo = [book: Book]
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|[[Datei:Grails-GORM-bidirectional one to many-db schema view.png|alternativtext=|zentriert]]
 +
|[[Datei:Grails-GORM-bidirectional one to many-association view.png|alternativtext=|zentriert|202x202px]]
 +
|}
  
===Mit Unterklassen===
+
Das Kaskadenverhalten <code>all-delete-orphan</code> sorgt dafür, dass jede verwaiste <code>Review</code> gelöscht wird. Das Aufrufen von <code>.clear()</code> reicht daher aus, um die vorherigen <code>Review</code>s des <code>Book</code>s zu entfernen.<syntaxhighlight lang="groovy" line="1">
Dieser Binder kann auch unterklassen verwalten:
+
Book replaceReviews(Serializable idParam, List<Review> newReviews) {
<syntaxhighlight lang="groovy">
+
     Book book = Book.where { id == idParam }.join('reviews').get()
// Klassen
+
     book.reviews.clear()
class Person {
+
     newReviews.each { book.addToReviews(it) }
     String firstName
+
     book.save()
     String lastName
 
     Integer age
 
     Address homeAddress
 
 
}
 
}
 +
</syntaxhighlight><br />
  
class Address {
+
===[http://gorm.grails.org/6.1.x/hibernate/manual/#oneToMany Mehr zur "Eins-zu-Viele"-Assoziation mit <code>hasMany</code>]===
     String county
+
Eine Eins-zu-Viele-Beziehung liegt vor, wenn eine Klasse, z.B. <code>Review</code>, viele Instanzen einer anderen Klasse hat, z.B. <code>Book</code>. Mit GORM definiert man eine solche Beziehung mit der Eigenschaft <code>hasMany</code>:
     String country
+
{| class="wikitable"
 +
|+Beispiel einer Unidirektionalen Eins-zu-Viele Beziehung von <code>Book</code> zu <code>Author</code>.
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
class Book {
 +
    String name
 +
    static hasMany = [reviews: Review]
 +
}
 +
class Review {
 +
     String author
 +
     String quote
 
}
 
}
 +
</syntaxhighlight>
 +
|Bemerke: GORM ordnet diese Art von Beziehung standardmäßig einer Join-Tabelle zu. (<code>book_review</code>)[[Datei:Grails-GORM-unidirectional one to many-db schema view.png|alternativtext=|zentriert]]
 +
|[[Datei:Grails-GORM-bidirectional one to many-association view.png|alternativtext=|zentriert|202x202px]]
 +
|}
 +
GORM fügt automatisch eine Eigenschaft vom Typ <code>java.util.Set</code> in die Domänenklasse ein, basierend auf der Einstellung <code>hasMany</code>. Dies kann verwendet werden, um die Sammlung zu durchlaufen:<syntaxhighlight lang="groovy">
 +
def a = Author.get(1)
 +
 +
for (book in a.books) {
 +
    println book.title
 +
}
 +
</syntaxhighlight>Das Standard-Kaskadenverhalten einer solchen Assoziation besteht darin, "Speichern" und "Aktualisieren" zu kaskadieren, jedoch nicht "Löschen". Es sei denn, es wird auch eine Zugehörigkeit/Abhängigkeit (<code>belongsTo</code>) definiert. 
 +
<br />
 +
====Mehrere Eigenschaften des gleichen Types auf der Vielen-Seite====
 +
Wenn man zwei Eigenschaften desselben Typs auf der "Vielen"-Seite eines Eins-zu-Viele hat, muss man "mappedBy" verwenden, um anzugeben, welche Sammlung zugeordnet werden soll:
 +
{| class="wikitable"
 +
|+Beispiel einer Unidirektionalen Eins-zu-Viele Beziehung von <code>Airport</code> zu <code>Flight</code>, bei dem
 +
die Klasse <code>Flight</code> zwei Spalten mit dem gleichen Typen von <code>Airport</code> besitzt
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
class Airport {
 +
    static hasMany = [flights: Flight]
 +
    static mappedBy = [flights: "departureAirport"]
 +
}
 +
class Flight {
 +
    Airport departureAirport
 +
    Airport destinationAirport
 +
}
 +
</syntaxhighlight>
 +
|Bemerke: Die Eigenschaften <code>flights</code> werden von GORM nicht als Datenbankspalte gemappt sondern sind nur logische Konstrukte die durch <code>mappedBy</code> eingestellt/definiert wurden.[[Datei:Grails-GORM-unidirectional one to many-multiple of same type recursive-db schema view.png|alternativtext=|zentriert]]
 +
|[[Datei:Grails-GORM-unidirectional one to many-multiple of same type-association view.png|alternativtext=|zentriert|206x206px]]
 +
|}
  
// Bindung
 
def bindingMap = [firstName: 'Peter', lastName: 'Gabriel', age: 63, homeAddress: [county: 'Surrey', country: 'England'] ]
 
  
def person = new Person(bindingMap)
+
Dies gilt auch, wenn man mehrere Sammlungen hat, die auf der "vielen" Seite unterschiedlichen Eigenschaften zugeordnet sind:
 +
{| class="wikitable"
 +
|+Beispiel einer Unidirektionalen Eins-zu-Viele Beziehung von <code>Airport</code> zu <code>Flight</code>, bei dem
 +
die Klasse <code>Airport</code> zwei Kollektionen des Typen <code>Flight</code>s besitzt und
  
assert person.firstName == 'Peter'
+
die Klasse <code>Flight</code> zwei Spalten mit dem gleichen Typen von <code>Airport</code> besitzt
assert person.lastName == 'Gabriel'
+
!Domänenklasse
assert person.age == 63
+
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
assert person.homeAddress.county == 'Surrey'
+
(dataSource: SQL)
assert person.homeAddress.country == 'England'
+
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
class Airport {
 +
    static hasMany = [outboundFlights: Flight, inboundFlights: Flight]
 +
    static mappedBy = [outboundFlights: "departureAirport",
 +
                      inboundFlights: "destinationAirport"]
 +
}
 +
class Flight {
 +
    Airport departureAirport
 +
    Airport destinationAirport
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|Bemerke: Die Eigenschaften <code>outboundFlights</code> und <code>inboundFlights</code> werden von GORM nicht als Datenbankspalte gemappt sondern sind nur logische Konstrukte die durch <code>mappedBy</code> eingestellt/definiert wurden.
 +
Daher sieht auch die Schema-Ansicht gleich '''wie beim oberen Beispiel''' aus.[[Datei:Grails-GORM-unidirectional one to many-multiple of same type recursive-db schema view.png|alternativtext=|zentriert]]
 +
|[[Datei:Grails-GORM-unidirectional one to many-multiple of same type recursive-association view.png|alternativtext=|zentriert|202x202px]]
 +
|}
  
===Mit Arrays===
+
 
Auch Arrays sind möglich:
+
===(Bidirektionales) Viele-zu-Viele===
<syntaxhighlight lang="groovy">
+
GORM unterstützt viele-zu-viele-Beziehungen, indem man auf beiden Seiten der Beziehung ein <code>hasMany</code> definiert und auf der besitzenden Seite der Beziehung ein "Gehört zu"  definiert:
// Klassen
+
 
class Band {
+
{| class="wikitable"
 +
|+Beispiel einer Bidirektionalen Viele-zu-Viele Beziehung von <code>Author</code> zu <code>Book</code>, bei dem
 +
die Klasse <code>Author</code> die "Besitzt-habende"-Seite ist
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
class Book {
 +
    static belongsTo = Author
 +
    static hasMany = [authors:Author]
 +
    String title
 +
}
 +
class Author {
 +
    static hasMany = [books:Book]
 
     String name
 
     String name
    static hasMany = [albums: Album] // One-To-Many Relation von Band-zu-Album (Für Datenbank-Relation wichtige Angabe!)
 
    List albums
 
 
}
 
}
 +
</syntaxhighlight>
 +
|Bemerke: GORM ordnet viele-zu-viele mithilfe einer Verknüpfungstabelle (Join-Table) auf Datenbankebene zu. [[Datei:Grails-GORM-bidirectional many to many-db schema view.png|alternativtext=|zentriert]]
 +
|[[Datei:Grails-GORM-bidirectional many to many-association view.png|alternativtext=|zentriert|210x210px]]
 +
|}
  
class Album {
+
Die besitzende Seite der Beziehung, in diesem Fall "Autor", übernimmt die Verantwortung für das Fortbestehen (Persisting) der Beziehung und ist die einzige Seite, die kaskadieren kann.
    String title
 
    Integer numberOfTracks
 
}
 
  
// Bindung
+
<u>Dies ist das erwartete Verhalten, da in GORM genau wie in "Hibernate" nur eine Seite der vielen-zu-vielen die Verantwortung für die Verwaltung der Beziehung übernehmen kann.</u>
def bindingMap = [name: 'Genesis',
+
{| class="wikitable"
                  'albums[0]': [title: 'Foxtrot', numberOfTracks: 6],
+
|+Beispiel-Code der Bidirektionalen Viele-zu-Viele Beziehung von <code>Author</code> zu <code>Book</code>, inklusive die daraus resultierende Datenbankansicht (IntelliJ).
                  'albums[1]': [title: 'Nursery Cryme', numberOfTracks: 7]]
+
!Beispiel-Code
 +
!Resultat
 +
|-
 +
|<syntaxhighlight lang="groovy">
 +
// Die `save`-Aktion der Besitz-Habenden-Seite (Author) kaskadiert, so dass auch die Book's in der Datenbank gespeichert werden/verbleiben.
 +
new Author(name:"Stephen King")
 +
        .addToBooks(new Book(title:"The Stand"))
 +
        .addToBooks(new Book(title:"The Shining"))
 +
        .save()
 +
</syntaxhighlight>
 +
|[[Datei:Grails-GORM-bidirectional many to many-example code-resulting table view.png|alternativtext=|zentriert]]
 +
|-
 +
|<syntaxhighlight lang="groovy">
 +
// Dies speichert nur das Book, und nicht dessen Author'en!!
 +
new Book(title:"Groovy in Action")
 +
        .addToAuthors(new Author(name:"Dierk Koenig"))
 +
        .addToAuthors(new Author(name:"Guillaume Laforge"))
 +
        .save()
 +
</syntaxhighlight>
 +
|[[Datei:Grails-GORM-bidirectional many to many-example code 2-resulting table view.png|alternativtext=|zentriert]]
 +
|}
  
def band = new Band(bindingMap)
 
  
assert band.name == 'Genesis'
+
===Grundlegende Kollektions-Typen  + Einstellen der Join-Table===
assert band.albums.size() == 2
+
Neben den Zuordnungen zwischen verschiedenen Domänenklassen unterstützt GORM auch die Zuordnung grundlegenden/primitiven Sammlungstypen. Die folgende Klasse erstellt beispielsweise eine Zuordnung von Spitznamen, bei der es sich um eine Reihe von String-Instanzen handelt:
assert band.albums[0].title == 'Foxtrot'
+
{| class="wikitable"
assert band.albums[0].numberOfTracks == 6
+
|+Beispiel einer Unidirektionalen Eins-zu-Viele Beziehung von <code>Person</code> zu <code>String</code>
assert band.albums[1].title == 'Nursery Cryme'
+
!Domänenklasse
assert band.albums[1].numberOfTracks == 7
+
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
class Person {
 +
    static hasMany = [nicknames: String]
 +
   
 +
   
 +
    // NUR ZU SCHAUZWECKEN ::: BITTE NICHT VERWENDEN!
 +
    String[] strings
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|Bemerke: GORM ordnet diese eins-zu-viele mithilfe einer Verknüpfungstabelle (Join-Table) auf Datenbankebene zu. [[Datei:Grails-GORM-unidirectional one to many-with primitive-example code-db schema table view.png|alternativtext=|zentriert]]
 +
|[[Datei:Grails-GORM-unidirectional one to many-with primitive-association view.png|alternativtext=|zentriert|207x207px]]
 +
|}
  
===Mit Maps===
+
{| class="wikitable"
Sowie auch mit Maps:
+
|+Beispiel-Code einer Unidirektionalen Eins-zu-Viele Beziehung von <code>Person</code> zu <code>String</code>, inklusive die daraus resultierende Datenbankansicht (IntelliJ).
<syntaxhighlight lang="groovy">
+
!Beispiel-Code
// Klassen
+
!Resultat
class Album {
+
|-
     String title
+
|<syntaxhighlight lang="groovy">
     static hasMany = [players: Player] // One-To-Many Relation von Album-zu-Player (Für Datenbank-Relation wichtige Angabe!)
+
new Person(
    Map players
+
    nicknames: ["Gandalf", "Josef"],
}
+
     strings: ["Lorem", "Ipsum"]
 +
).save()
 +
new Person(
 +
     nicknames: ["Gandalf", "Josef"],
 +
    strings: ["Lorem", "Ipsum"]
 +
).save()
 +
</syntaxhighlight>
 +
|[[Datei:Grails-GORM-unidirectional one to many-with primitive-example code-resulting table view.png|alternativtext=|zentriert]]
 +
|}
  
class Player {
+
Mit der Eigenschaft <code>joinTable</code> kann man die verschiedenen Aspekte der Join-Tabelle ändern:<blockquote>[http://docs.grails.org/3.3.9/ref/Database%20Mapping/joinTable.html joinTable] passt die Verknüpfungstabelle an, die für unidirektionale Eins-zu-Viele-, Viele-zu-Viele- und primitive Auflistungstypen verwendet wird.</blockquote>
     String name
+
{| class="wikitable"
}
+
|+Beispiel einer Unidirektionalen Eins-zu-Viele Beziehung von <code>Person</code> zu <code>String</code> mit veränderten <code>joinTable</code> Einstellungen
 +
!Beispiel-Code
 +
!Resultat
 +
|-
 +
|<syntaxhighlight lang="groovy">
 +
class Person {
 +
     static hasMany = [nicknames: String]
  
// Bindung
+
    static mapping = {
def bindingMap = [title: 'The Lamb Lies Down On Broadway',
+
      nicknames joinTable: [name: 'bunch_o_nicknames',
                  'players[guitar]': [name: 'Steve Hackett'],
+
                          key: 'person_id',
                  'players[vocals]': [name: 'Peter Gabriel'],
+
                          column: 'nickname',
                  'players[keyboards]': [name: 'Tony Banks']]
+
                          type: "text"]
 
+
    }
def album = new Album(bindingMap)
+
}
 
 
assert album.title == 'The Lamb Lies Down On Broadway'
 
assert album.players.size() == 3
 
assert album.players.guitar.name == 'Steve Hackett'
 
assert album.players.vocals.name == 'Peter Gabriel'
 
assert album.players.keyboards.name == 'Tony Banks'
 
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|[[Datei:Grails-GORM-unidirectional one to many-with primitive-joinTable example-db schema view.png|alternativtext=|zentriert]]
 +
|}<br />
 +
==[http://gorm.grails.org/6.1.x/hibernate/manual/#gormComposition Domain-Modelling in GORM: Komposition]==
 +
Neben Assoziationen unterstützt GORM das Konzept der Komposition/Zusammensetzung. In diesem Fall kann anstelle der Abbildung von Klassen auf separate Tabellen eine Klasse in die aktuelle Tabelle "eingebettet" werden. Zum Beispiel:
 +
{| class="wikitable"
 +
|+
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
// grails-app/domain/Person.groovy
 +
class Person {
 +
    Address homeAddress
 +
    Address workAddress
 +
    static embedded = ['homeAddress', 'workAddress']
 +
}
  
==File-Uploads==
+
// grails-app/domain/Address.groovy
<code>.gsp</code>-Code:
+
class Address {
<syntaxhighlight lang="grails">
+
    String number
Upload Form: <br />
+
    String code
<g:uploadForm action="upload">
+
}
  <input type="file" name="myFile" />
 
  <input type="submit" />
 
</g:uploadForm>
 
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|[[Datei:Grails-GORM-composition-db schema view.png|alternativtext=|zentriert]]
 +
|[[Datei:Grails-GORM-composition-association view.png|alternativtext=|zentriert|350x350px]]
 +
|}
 +
TIPP: Wenn man die Klasse <code>Address</code> in einer separaten Groovy-Datei im Verzeichnis <code>grails-app/domain</code> definiert, erhält man auch eine Tabelle <code>address</code> (wie im oberen Beispiel). Wenn dies nicht geschehen soll, kann man mithilfe der Funktion von Groovy mehrere Klassen pro Datei definieren und die Klasse <code>Address</code> unterhalb der Klasse <code>Person</code> in die Datei <code>grails-app/domain/Person.groovy</code> aufnehmen.
 +
{| class="wikitable"
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!IntelliJ's Domänenklassen-Abhängigkeiten-Ansicht
 +
|-
 +
|<syntaxhighlight lang="groovy" line="1">
 +
// grails-app/domain/Person.groovy
 +
class Person {
 +
    Address homeAddress
 +
    Address workAddress
 +
    static embedded = ['homeAddress', 'workAddress']
 +
   
 +
    class Address {
 +
        String number
 +
        String code
 +
    }
 +
}
 +
</syntaxhighlight>
 +
|[[Datei:Grails-GORM-composition without extra table-db schema view.png|alternativtext=|zentriert]]
 +
|Bei der Assoziations-Ansicht hat sich natürlich nichts geändert.. Das Groovy-Objekt <code>Address</code>, das ja in diesem Szenario nur zum halten/gruppieren von Daten zuständig ist (DRY), wird hierdurch einfach nicht als eigene Tabelle abgebildet![[Datei:Grails-GORM-composition-association view.png|alternativtext=|zentriert|350x350px]]
 +
|}<br />
 +
==[http://gorm.grails.org/6.1.x/hibernate/manual/#inheritanceInGORM Domain-Modelling in GORM: Vererbung]==
 +
[[Datei:Single Table Inheritance.svg|mini|Tabelle pro VererbungshierarchieQuelle: [https://commons.wikimedia.org/wiki/File:Single_Table_Inheritance.svg?uselang=de Wikimedia]]][[Datei:Class Table Inheritance.svg|mini|Tabelle pro UnterklasseQuelle: [https://commons.wikimedia.org/wiki/File:Class_Table_Inheritance.svg?uselang=de Wikimedia]]][[Datei:Concrete Table Inheritance.svg|mini|Tabelle pro konkrete KlasseQuelle: [https://commons.wikimedia.org/wiki/File:Concrete_Table_Inheritance.svg?uselang=de Wikimedia]]]
 +
GORM unterstützt die Vererbung sowohl von abstrakten Basisklassen als auch von konkreten persistenten GORM-Entitäten.
 +
 +
<br />
 +
===Abbildungsverfahren von Vererbungshierarchien===
 +
Quelle der Beschreibungen: [https://de.wikipedia.org/wiki/Objektrelationale_Abbildung#Abbildung_von_Vererbungshierarchien Wikipedia]
 +
 +
Es gibt im Wesentlichen drei verschiedene Verfahren, um Vererbungshierarchien auf Datenbanktabellen abzubilden.
  
*<code>[http://docs.grails.org/3.1.1/ref/Tags/uploadForm.html g:uploadForm]</code> fügt automatisch <code>enctype="multipart/form-data"</code> zum form-Tag hinzu.
+
;Tabelle pro Vererbungshierarchie
 +
:(auch Single Table, ''einzelne Tabelle'') Bei diesem Verfahren werden alle Attribute der Basisklasse und aller davon abgeleiteten Klassen in '''einer gemeinsamen Tabelle''' gespeichert. Zusätzlich wird ein sogenannter „Diskriminator“ in einer weiteren Spalte abgelegt, der festlegt, welcher Klasse das in dieser Zeile gespeicherte Objekt angehört. '''Attribute von abgeleiteten Klassen dürfen bei diesem Ansatz aber in den meisten Fällen nicht mit einem NOT-NULL-[[Constraint]] versehen werden.''' '''Außerdem können Beschränkungen der Anzahl erlaubter Spalten pro Tabelle diesen Ansatz bei großen Klassen bzw. Klassenhierarchien vereiteln.'''
 +
;Tabelle pro Unterklasse
 +
:(auch ''Joined'' oder Class Table) Bei diesem Verfahren wird '''eine Tabelle für die Basisklasse angelegt und für jede davon abgeleitete Unterklasse eine weitere Tabelle.''' Ein Diskriminator wird nicht benötigt, weil die Klasse eines Objekts durch eine 1-zu-1-Beziehung zwischen dem Eintrag in der Tabelle der Basisklasse und einem Eintrag in einer der Tabellen der abgeleiteten Klassen festgelegt ist.
 +
;Tabelle pro konkrete Klasse
 +
:(auch ''Table per Class'' oder Concrete Table) Hier werden die Attribute der abstrakten Basisklasse in die Tabellen für die konkreten Unterklassen mit aufgenommen. '''Die Tabelle für die Basisklasse entfällt.''' Der Nachteil dieses Ansatzes besteht darin, dass es nicht möglich ist, mit einer Abfrage Instanzen verschiedener Klassen zu ermitteln.
  
Nun gibt es viele Möglichkeiten mit dem File-Upload umzugehen. Eine davon ist mithilfe von  
+
 
[https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/multipart/MultipartFile.html Springs MultipartFile].
+
 
<br>(Diese Weise ist gut um Dateien zu transferieren und manipulieren weil man direkt die <code>InputStream</code>-Instanz bekommen kann.)
+
Die gewünschte Vererbungshierarchie kann mithilfe von ORM-DSL-Mapping wie folgt festgelegt werden:<syntaxhighlight lang="groovy">
<syntaxhighlight lang="groovy">
+
class Payment {
def upload() {
+
     int amount
     def f = request.getFile('myFile')
+
     static mapping = {
     if (f.empty) {
+
         tablePerHierarchy false
         flash.message = 'file cannot be empty'
+
         // ODER
         render(view: 'uploadForm')
+
         tablePerConcreteClass true
         return
 
 
     }
 
     }
 +
}
  
     f.transferTo(new File('/some/local/dir/myfile.txt'))
+
class CreditCardPayment extends Payment {
     response.sendError(200, 'Done')
+
     String cardNumber
 +
}
 +
</syntaxhighlight>
 +
===[http://gorm.grails.org/6.1.x/hibernate/manual/#_polymorphic_queries Polymorph-ische Abfragen]===
 +
<syntaxhighlight lang="groovy">
 +
class Content {
 +
    String author
 +
}
 +
class BlogEntry extends Content {
 +
    URL url
 +
}
 +
class Book extends Content {
 +
    String ISBN
 +
}
 +
class PodCast extends Content {
 +
     byte[] audioStream
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
===Upload-Größen-Limit von Grails===
+
Das Ergebnis der Vererbung ist, dass man die Möglichkeit hat, polymorph abzufragen. Wenn man beispielsweise die Methode <code>list()</code> für die Superklasse <code>Content</code> verwendet, werden auch alle Unterklassen von <code>Content</code> zurückgegeben:<syntaxhighlight lang="groovy">
Grails Standard-Wert für die maximale Dateigröße liegt bei 128KB. Wenn dieses Limit überschritten wird taucht folgender fehler auf:
+
def content = Content.list() // list all blog entries, books and podcasts
<syntaxhighlight>
+
content = Content.findAllByAuthor('Joe Bloggs') // find all by author
org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException
+
 
 +
def podCasts = PodCast.list() // list only podcasts
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Dieses Limit kann man in seiner <code>application.yml</code> folgendermaßen anpassen:
 
<syntaxhighlight lang="yaml">
 
grails:
 
    controllers:
 
        upload:
 
            maxFileSize: 2000000
 
            maxRequestSize: 2100000
 
</syntaxhighlight>
 
  
*<code>maxFileSize</code>: Maximale größe einer Datei bei einem Request
+
==[http://gorm.grails.org/6.1.x/hibernate/manual/#sets Domain-Modelling in GORM: <code>Set</code>s, <code>List</code>s und <code>Map</code>s]==
*<code>maxRequestSize</code>: Maximale Größe eines gesamten Requests
+
Wenn man eine Viele-Beziehung in GORM definiert, handelt es sich standardmäßig um eine <code>java.util.Set</code>.
  
==URL-Mappings==
+
===SortedSet===
In der [http://docs.grails.org/latest/guide/single.html Dokumentation] ist die Konvention des URL-Mappings (standartmäßig) wie folgt eingestellt:
+
<code>Set</code>s garantieren Einzigartigkeit, aber keine Reihenfolge, was möglicherweise nicht das ist, was man will. Um eine benutzerdefinierte Ordnung zu erhalten, konfigurieren Sie das Set als <code>SortedSet</code>:
<code>/controller/action/id</code>.<br>
+
{| class="wikitable"
Dies kann man aber auch sehr leicht für spezielle Seiten in der Datei <code>grails-app/conf/UrlMappings.groovy</code> umändern.
+
!Domänenklasse
Beispiel (URL "/product" auf die Aktion "list()" vom Controller "Product" leiten):
+
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
<syntaxhighlight lang="groovy">
+
(dataSource: SQL)
"/product"(controller: "product", action: "list")
+
|-
 +
|<syntaxhighlight lang="groovy">
 +
class Author {
 +
    SortedSet books
 +
    static hasMany = [books: Book]
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Man kann die URL-Mappings auch verschachteln:
+
|[[Datei:Grails-GORM-sortedset-db schema view.png|alternativtext=|zentriert]]
<syntaxhighlight lang="groovy">
+
|}
group "/store", {
+
In diesem Fall wird eine Implementierung von <code>java.util.SortedSet</code> für die injezierte Variable verwendet. Dies bedeutet, dass man <code>java.lang.Comparable</code> in seiner Book-Klasse implementieren müsste:<syntaxhighlight lang="groovy">
     group "/product", {
+
class Book implements Comparable {
         "/$id"(controller:"product")
+
     String title
 +
    Date releaseDate = new Date()
 +
 
 +
    @Override
 +
    int compareTo(obj) {
 +
         releaseDate.compareTo(obj.releaseDate)
 
     }
 
     }
 +
}
 +
</syntaxhighlight><br />
 +
===List===
 +
Um Objekte in der Reihenfolge zu halten, in der sie hinzugefügt wurden, und um sie wie ein Array nach Index zu referenzieren, kann man den Sammlungstyp als <code>List</code> definieren:
 +
{| class="wikitable"
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
|-
 +
|<syntaxhighlight lang="groovy">
 +
class Author {
 +
    List books
 +
    static hasMany = [books: Book]
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|[[Datei:Grails-GORM-list-db schema view.png|alternativtext=|zentriert]]
 +
|}
 +
Auf Datenbankebene funktioniert dies so, dass Hibernate eine Spalte <code>books_idx</code> erstellt, in der der Index der Elemente in der Sammlung gespeichert wird, um diese Reihenfolge auf Datenbankebene beizubehalten. (Siehe Bild)
  
 +
Bei Verwendung einer Liste müssen Elemente zur Sammlung hinzugefügt werden, bevor sie gespeichert werden. Andernfalls löst Hibernate eine Ausnahme aus (<code>org.hibernate.HibernateException: Nullindexspalte für die Sammlung</code>):<syntaxhighlight>
 +
// Wirft den genannten Fehler!
 +
def book = new Book(title: 'The Shining')
 +
book.save()
 +
author.addToBooks(book)
  
===(Explizites) REST-Mapping===
+
// Richtiger Weg
<syntaxhighlight lang="groovy">
+
def book = new Book(title: 'Misery')
"/books"(resources:'book')
+
author.addToBooks(book)
</syntaxhighlight>
+
author.save()
ist gleich wie
+
</syntaxhighlight><br />
<syntaxhighlight lang="groovy">
+
===Hibernate Bags (Collection)===
get "/books"(controller:"book", action:"index")
+
Wenn Ordnung und Eindeutigkeit keine Rolle spielen (oder wenn man diese explizit verwaltet), kann man den Typ <code>[http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/collections.html Bag]</code> von Hibernate verwenden, um zugeordnete Sammlungen darzustellen.
get "/books/create"(controller:"book", action:"create")
 
post "/books"(controller:"book", action:"save")
 
get "/books/$id"(controller:"book", action:"show")
 
get "/books/$id/edit"(controller:"book", action:"edit")
 
put "/books/$id"(controller:"book", action:"update")
 
delete "/books/$id"(controller:"book", action:"delete")
 
</syntaxhighlight>
 
  
 +
Die einzige dafür erforderliche Änderung besteht darin, den Sammlungstyp als <code>Collection</code> zu definieren<syntaxhighlight lang="groovy">
 +
class Author {
 +
  Collection books
 +
  static hasMany = [books: Book]
 +
}
 +
</syntaxhighlight>Da Eindeutigkeit und Reihenfolge nicht von Hibernate verwaltet werden, <u>löst das Hinzufügen oder Entfernen von Sammlungen</u>, die als Bag zugeordnet sind, <u>nicht das Laden aller vorhandenen Instanzen aus der Datenbank aus</u>. Daher ist dieser Ansatz leistungsfähiger und erfordert weniger Speicher als die Verwendung eines Sets oder eine Liste.
 +
<br />
 +
===Maps von Objekten===
 +
Wenn man eine einfache Zuordnung von Zeichenfolge / Wert-Paaren wünscht, kann GORM dies wie folgt zuordnen:
 +
{| class="wikitable"
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
!Resultat des Beispiel-Codes
 +
|-
 +
|<syntaxhighlight lang="groovy">
 +
class Author {
 +
    Map books // map of ISBN:book names
 +
}
  
<syntaxhighlight lang="groovy">
+
def a = new Author()
// Results in /books/1/authors/2
+
a.books = ['1590597583':"My Book"]
<g:link controller="author" action="show" method="GET" params="[bookId:1]" id="2">The Author</g:link>
+
a.save()
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|[[Datei:Grails-GORM-map of strings-db schema view.png|alternativtext=|zentriert]]
 +
|[[Datei:Grails-GORM-map of strings-example code-resulting table view.png|alternativtext=|zentriert]]
 +
|}
 +
In oberen Fall '''müssen''' der Schlüssel und der Wert der Map ein String sein.
  
===Redirects===
+
Wenn man eine Map mit Objekten möchte, kann man dies wie folgt tun:
<syntaxhighlight lang="groovy">
+
{| class="wikitable"
"/viewBooks"(redirect: '/books/list')
+
!Domänenklasse
"/viewAuthors"(redirect: [controller: 'author', action: 'list'])
+
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
"/viewPublishers"(redirect: [controller: 'publisher', action: 'list', permanent: true])
+
(dataSource: SQL)
 +
!Resultat des Beispiel-Codes
 +
|-
 +
|<syntaxhighlight lang="groovy">
 +
class Book {
 +
    Map authors
 +
    static hasMany = [authors: Author]
 +
}
 +
 
 +
def a = new Author(name:"Stephen King")
 +
 
 +
def book = new Book()
 +
book.authors = [stephen:a]
 +
book.save()
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
|[[Datei:Grails-GORM-map of objects-db schema view.png|alternativtext=|zentriert]]
 +
|[[Datei:Grails-GORM-map of objects-example code-resulting table view.png|alternativtext=|zentriert]]
 +
|}
 +
Die statische <code>hasMany</code>-Eigenschaft definiert den Typ der Elemente in der Map. Die Schlüssel für die Map '''müssen''' Zeichenfolgen sein.
  
 +
<br />
 +
===Hinweis zu Sammlungen und dessen Leistung===
 +
Der Java <code>Set</code>-Typ erlaubt keine Duplikate. Um die Eindeutigkeit beim Hinzufügen eines Eintrags zu einer festgelegten Zuordnung sicherzustellen, '''muss Hibernate die gesamten Zuordnungen aus der Datenbank laden.''' Wenn man eine große Anzahl von Einträgen in der Zuordnung hat, kann dies in Bezug auf die Leistung kostspielig sein.
  
===[https://docs.grails.org/3.3.10/guide/single.html#embeddedVariables Integrierte Variablen]===
+
'''Das gleiche Verhalten ist für den Typ <code>List</code> erforderlich''', da Hibernate die gesamte Zuordnung laden muss, um die Reihenfolge aufrechtzuerhalten. Wenn man eine große Anzahl von Datensätzen in der Zuordnung erwartet, sollte man die Zuordnung daher bidirektional machen, damit die Verknüpfung auf der umgekehrten Seite erstellt werden kann.<syntaxhighlight lang="groovy">
<syntaxhighlight lang="groovy">
+
def book = new Book(title:"New Grails Book")
"/product/$id"(controller: "product")
+
def author = Author.get(1)
 +
book.author = author
 +
book.save()
 +
</syntaxhighlight>Im oberen Beispiel wird der Zuordnungs-Link vom untergeordneten Element (Buch) erstellt. '''Daher ist es nicht erforderlich,''' '''die Sammlung''' direkt '''zu bearbeiten''', '''was zu weniger Abfragen und effizienterem Code führt'''. Wenn man bei einem Autor mit einer großen Anzahl zugeordneter Buchinstanzen Code wie den folgenden schreiben, '''wirkt sich dies auf die Leistung aus:'''<syntaxhighlight lang="groovy">
 +
def book = new Book(title:"New Grails Book")
 +
def author = Author.get(1)
 +
author.addToBooks(book)
 +
author.save()
 
</syntaxhighlight>
 
</syntaxhighlight>
Im obrigen Fall wird Grails automatisch beim Besuchen von <code>"/product/2"</code> die Zahl <code>2</code> in das <code>params</code>-Scope einsetzen, damit es vom Controller einfach genutzt werden kann:
+
 
<syntaxhighlight lang="groovy">
+
 
class ProductController {
+
==[http://gorm.grails.org/6.1.x/hibernate/manual/#persistenceBasics Persistenz Grundlagen]==
    def index() { render params.id }
+
<blockquote>
 +
'''Persistenz''' ist in der Informatik der Begriff, der die Fähigkeit bezeichnet, Daten (oder Objekte) oder ''logische Verbindungen'' über lange Zeit (insbesondere über einen Programmabbruch hinaus) bereitzuhalten.
 +
 
 +
Da ein Programm jederzeit unvorhergesehen unterbrochen werden kann, bedeutet persistente Datenhaltung insbesondere, dass jede Zustandsänderung der Daten sofort auf dem nichtflüchtigen Medium gespeichert werden muss.
 +
 
 +
„Persistent“ wird als ein im Kontext wohldefinierter Fachbegriff für „nicht unkontrolliert veränderlich“ verwendet. </blockquote>Eine wichtige Sache, an die man sich bei GORM erinnern sollte, ist, dass GORM unter der Oberfläche [http://www.hibernate.org/ Hibernate] für die Persistenz verwendet. Wenn man mit [http://api.rubyonrails.org/classes/ActiveRecord/Base.html ActiveRecord] oder [http://www.mybatis.org/ iBatis/MyBatis] gearbeitet hat, fühlt sich das "Sitzungs"-Modell von Hibernate möglicherweise etwas seltsam an.<br />
 +
 
 +
Wenn man Grails verwendet, bindet Grails automatisch eine Hibernate-Sitzung an die aktuell ausgeführte Anforderung. Auf diese Weise kann man die Methoden <code>save</code> und <code>delete</code> sowie andere GORM-Methoden transparent verwenden.
 +
 
 +
Wenn man Grails nicht verwendet, muss man sicherstellen, dass eine Sitzung an die aktuelle Anforderung gebunden ist. Eine Möglichkeit, dies zu erreichen, ist die <code>[http://gorm.grails.org/6.1.x/hibernate/api/org/grails/datastore/gorm/GormEntity.html#withNewSession(groovy.lang.Closure) withNewSession(Closure)]</code>-Methode:<syntaxhighlight lang="groovy">
 +
Book.withNewSession {
 +
        // your logic here
 +
}
 +
</syntaxhighlight>Eine weitere Option besteht darin, eine Transaktion mit der <code>[http://gorm.grails.org/6.1.x/hibernate/api/org/grails/datastore/gorm/GormEntity.html#withTransaction(groovy.lang.Closure) withTransaction(Closure)]</code>-Methode zu binden:<syntaxhighlight lang="groovy">
 +
Book.withTransaction {
 +
        // your logic here
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Natürlich kann man auch mehrere Variablen verwenden, wie bei diesem Beispiel eines Blogs:
 
<syntaxhighlight lang="groovy">
 
"/$blog/$year/$month/$day/$id"(controller: "blog", action: "show")
 
</syntaxhighlight>
 
  
====Optionale Variablen====
+
===Transaktions-Write-Behind===
Indem man ein Fragezeichen am ende des Variabl-Namens anhängt schlägt das URL-Mapping auch zu, wenn der Nutzer diese nicht angegeben hat.
+
Eine nützliche Funktion von Hibernate über direkte JDBC-Aufrufe und sogar andere Frameworks ist, dass beim Aufrufen von <code>save()</code> oder <code>delete()</code> zu diesem Zeitpunkt nicht unbedingt SQL-Vorgänge ausgeführt werden. '''Hibernate stapelt SQL-Anweisungen und führt sie so spät wie möglich aus, häufig am Ende der Anforderung, wenn die Sitzung geleert (flush) und geschlossen wird.'''''Wenn man Grails verwendet, wird dies normalerweise automatisch für einen erledigt. Wenn man GORM außerhalb von Grails verwendet, muss man die Sitzung möglicherweise am Ende des Vorgangs manuell leeren.''
Beispiel-Mapping:
+
 
<syntaxhighlight lang="groovy">
 
"/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show")
 
</syntaxhighlight>
 
  
Das obrige Beispiel-Mapping schlägt bei allen folgenden URLs zu:
+
'''Hibernate speichert Datenbankaktualisierungen nach Möglichkeit zwischen, wobei die Änderungen nur dann tatsächlich übertragen werden, wenn bekannt ist, dass ein Flush erforderlich ist, oder wenn ein Flush programmgesteuert ausgelöst wird.''' Ein häufiger Fall, in dem Hibernate zwischengespeicherte Aktualisierungen löscht, ist das Ausführen von Abfragen, da die zwischengespeicherten Informationen möglicherweise in den Abfrageergebnissen enthalten sind. Solange man jedoch konfliktfreie Speicherungen, Aktualisierungen und Löschvorgänge durchführt, werden diese gestapelt, bis die Sitzung gelöscht wird. Dies kann eine erhebliche Leistungssteigerung für Anwendungen sein, die viele Datenbankschreibvorgänge ausführen.
<syntaxhighlight lang="groovy">
+
 
/graemerocher/2007/01/10/my_funky_blog_entry
+
 
/graemerocher/2007/01/10
+
'''Zu beachten gilt, dass das Flushing nicht mit dem Commiten einer Transaktion identisch ist.''' Wenn die Aktionen im Kontext einer Transaktion ausgeführt werden, führt das Flushing SQL-Aktualisierungen aus, aber die Datenbank speichert die Änderungen in ihrer Transaktionswarteschlange und schließt die Aktualisierungen erst ab, wenn die Transaktion festgeschrieben wird.
/graemerocher/2007/01
+
 
/graemerocher/2007
+
===Speichern und Aktualisieren von Objekten===
/graemerocher
+
Ein Beispiel für die Verwendung der <code>save()</code>-Methode wäre:<syntaxhighlight lang="groovy">
 +
def p = Person.get(1)
 +
p.save()
 +
</syntaxhighlight>Die eigentliche Speicherung wird nicht sofort in die Datenbank übertragen, sondern beim nächsten Flush. Es gibt jedoch Fälle, in denen man steuern möchten, wann diese Anweisungen ausgeführt werden (oder in der Hibernate-Terminologie, wenn die Sitzung "geleert" wird). Dazu kann man das Argument <code>flush</code> für die <code>save()</code>-Methode verwenden:<syntaxhighlight lang="groovy">
 +
def p = Person.get(1)
 +
p.save(flush: true)
 +
</syntaxhighlight>Zu Beachten gilt, dass in diesem Fall '''alle''' ausstehenden SQL-Anweisungen einschließlich vorheriger Speicherungen, Löschungen usw. mit der Datenbank synchronisiert werden. Auf diese Weise kann man auch Ausnahmen abfangen, was in der Regel in Szenarien mit [http://gorm.grails.org/6.1.x/hibernate/manual/#locking optimistischem Sperren] hilfreich ist:<syntaxhighlight lang="groovy">
 +
def p = Person.get(1)
 +
try {
 +
    p.save(flush: true)
 +
}
 +
catch (org.springframework.dao.DataIntegrityViolationException e) {
 +
    // deal with exception
 +
}
 +
</syntaxhighlight>Außerdem gilt zu beachten dass GORM eine Domain-Instanz jedes Mal überprüft, wenn man sie speichern will. Wenn diese Überprüfung fehlschlägt, wird die Domäneninstanz nicht in der Datenbank gespeichert. '''Standardmäßig gibt <code>save()</code> in diesem Fall einfach null zurück. Wenn Sie jedoch eine Ausnahme auslösen möchten, können Sie das Argument <code>failOnError</code> verwenden:'''<syntaxhighlight lang="groovy">
 +
def p = Person.get(1)
 +
try {
 +
    p.save(failOnError: true)
 +
}
 +
catch (ValidationException e) {
 +
    // deal with exception
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
====Optionale File-Extension====
+
 
 +
===Löschen von Objekten===
 +
Ein Beispiel für die Verwendung der <code>delete()</code>-Methode wäre:<syntaxhighlight lang="groovy">
 +
def p = Person.get(1)
 +
p.delete()
 +
</syntaxhighlight>Wie beim Speichern verwendet Hibernate das Transaktions-Write-Behind-Konzept, um das Löschen durchzuführen. Um das Löschen an Ort und Stelle durchzuführen, kann man das Argument <code>flush</code> verwenden:<syntaxhighlight lang="groovy">
 +
def p = Person.get(1)
 +
p.delete(flush: true)
 +
</syntaxhighlight>Mit dem <code>flush</code>-Argument kann man alle Fehler abfangen, die beim Löschen auftreten. Ein häufiger Fehler, der auftreten kann, ist, wenn Sie eine Datenbankeinschränkung verletzt, obwohl dies normalerweise auf einen Programmier- oder Schemafehler zurückzuführen ist. Das folgende Beispiel zeigt, wie eine <code>DataIntegrityViolationException</code> abgefangen wird, die ausgelöst wird, wenn man die Datenbankeinschränkungen verletzt:<syntaxhighlight lang="groovy">
 +
import org.springframework.dao.*
 +
 
 +
def p = Person.get(1)
 +
 
 +
try {
 +
    p.delete(flush: true)
 +
}
 +
catch (DataIntegrityViolationException e) {
 +
    // handle the error
 +
}
 +
</syntaxhighlight>Um einen Stapel-Löschvorgang durchzuführen, gibt es verschiedene Möglichkeiten, dies zu erreichen. Eine Möglichkeit besteht darin, eine [http://gorm.grails.org/6.1.x/hibernate/manual/#whereQueries Where-Abfrage] zu verwenden:<syntaxhighlight lang="groovy">
 +
Person.where {
 +
        name == "Fred"
 +
}.deleteAll()
 +
</syntaxhighlight><br />
 +
 
 +
===Eager und Lazy Fetching===
 +
Zuordnungen in GORM sind standardmäßig faul. Dies lässt sich am besten anhand eines Beispiels erklären:<syntaxhighlight lang="groovy">
 +
class Airport {
 +
    String name
 +
    static hasMany = [flights: Flight]
 +
}
 +
 
 +
class Flight {
 +
    String number
 +
    Location destination
 +
    static belongsTo = [airport: Airport]
 +
}
 +
 
 +
class Location {
 +
    String city
 +
    String country
 +
}
 +
</syntaxhighlight>Angesichts der oben genannten Domänenklassen und des folgenden Codes:<syntaxhighlight lang="groovy">
 +
def airport = Airport.findByName("Gatwick")
 +
for (flight in airport.flights) {
 +
    println flight.destination.city
 +
}
 +
</syntaxhighlight>'''GORM führt einzelne SQL-Abfrage aus, um...'''
 +
 
 +
*die "Flughafen"-Instanz abzurufen,
 +
*eine andere, um ihre Flüge abzurufen,
 +
*und dann eine zusätzliche Abfrage für jede Iteration über die Flugzuordnung, um das aktuelle Flugziel abzurufen.
 +
 
 +
Mit anderen Worten: '''Man führt N+1-Anfragen aus!''' (wenn Sie die ursprüngliche ausschließen, um den Flughafen zu erhalten).
 +
<br />
 +
 
 +
<br />
 +
 
 +
===Eager Fetching konfigurieren===
 +
<syntaxhighlight lang="groovy">
 +
class Airport {
 +
    String name
 +
    static hasMany = [flights: Flight]
 +
    static mapping = {
 +
        flights lazy: false
 +
    }
 +
}
 +
</syntaxhighlight>Für weitere Informationen zu Eager Fetching (dessen Nachteile und Prinzipien) sowie zum Batch-Fetching Siehe die [http://gorm.grails.org/6.1.x/hibernate/manual/#_configuring_eager_fetching Offizielle Dokumentation]
 +
 
 +
 
 +
===Checken auf Modifikation===
 +
Sobald man eine persistente Domänenklasseninstanz geladen und möglicherweise geändert hat, ist es nicht einfach, die ursprünglichen Werte abzurufen. Wenn man versucht, die Instanz mit <code>get(id)</code> neu zu laden, gibt Hibernate die aktuell geänderte Instanz aus dem Sitzungscache zurück.
 +
 
 +
Das neuladen mit einer neuen Abfrage würde einen Flush auslösen, der Probleme verursachen kann, wenn seine Daten noch nicht zum Flush bereit sind. Daher bietet GORM einige Methoden zum Abrufen der ursprünglichen Werte, die Hibernate beim Laden der Instanz zwischenspeichert (die für die fehlerhafte Überprüfung verwendet wird)
 +
 
 +
*[http://gorm.grails.org/6.1.x/hibernate/api/org/grails/datastore/gorm/GormEntity.html#isDirty(java.lang.String) isDirty()]: Checkt ob überhaupt irgend-ein Feld verändert wurde (Funktioniert mit allen Persistenten Eigenschaften und Assoziationen, nur Collection-Assoziationen (Bags) funktionieren in der jetzigen Version noch nicht)
 +
*isDirty(fieldName): Checkt ob ein bestimmtes Feld verändert wurde
 +
*[http://gorm.grails.org/6.1.x/hibernate/api/org/grails/datastore/gorm/GormEntity.html#getDirtyPropertyNames() getDirtyPropertyNames()]: Gibt die Namen der Felder zurück die verändert wurden
 +
*[http://gorm.grails.org/6.1.x/hibernate/api/org/grails/datastore/gorm/GormEntity.html#getPersistentValue(java.lang.String) getPersistentValue(fieldName)]: Gibt den ursprünglichen / derzeitig persistenten Wert eines Felds zurück
 +
 
 +
 
 +
==[http://gorm.grails.org/6.1.x/hibernate/manual/#querying Abfragen erstellen (Querying)]==
 +
GORM unterstützt eine Reihe leistungsstarker Abfragemöglichkeiten, von dynamischen Findern, über Kriteriensuche bis hin zu eigens definierbaren '''HQL-Anfragen (Hibernate Object Oriented Query Language)'''. Abhängig von der Komplexität der Abfrage hat man die folgenden Optionen in der Reihenfolge der Flexibilität und Leistung:
 +
 
 +
*Dynamische Finder
 +
*"Wo/Bei Dem"-Abfragen
 +
*Kriterien Abfragen
 +
*Hibernate Query Language (HQL)
 +
 
 +
''Darüber hinaus führt die Fähigkeit von Groovy, Sammlungen mit [https://groovy-lang.org/processing-xml.html#_gpath GPath] und Methoden wie sort, findAll usw. in Kombination mit GORM zu bearbeiten, zu einer leistungsstarken Kombination.''
 +
 
 +
 
 +
===[http://gorm.grails.org/6.1.x/hibernate/manual/#_listing_instances Instanzen auflisten]===
 +
Die [http://gorm.grails.org/6.1.x/hibernate/api/org/grails/datastore/gorm/GormEntity.html#list() <code>list()</code>]-Methode gibt (ohne Argumente) alle Domäneninstanzen einer Klasse zurück:<syntaxhighlight lang="groovy">
 +
def books = Book.list()
 +
</syntaxhighlight>Die [http://gorm.grails.org/6.1.x/hibernate/api/org/grails/datastore/gorm/GormEntity.html#list(java.util.Map) <code>list(params)</code>]-Methode unterstützt Argumente zur Paginierung..<syntaxhighlight lang="groovy">
 +
def books = Book.list(offset:10, max:20)
 +
</syntaxhighlight>..und zum simplen sortieren. (Das argument <code>sort</code> trägt den Namen des Feldes der Domänenklasse nach der man sortieren will, und das <code>order</code>-Argument akzeptiert die Werte <code>asc</code> (ascending, aufsteigend) und <code>desc</code> (descenting, absteigend))<syntaxhighlight lang="groovy">
 +
def books = Book.list(sort:"title", order:"asc")
 +
</syntaxhighlight>
 +
 
 +
 
 +
===[http://gorm.grails.org/6.1.x/hibernate/manual/#_retrieval_by_database_identifier Instanz über ID ausfindig machen]===
 +
Die zweite Grundform eines Abrufs ist mithilfe die Datenbankkennung über der Methode <code>[http://gorm.grails.org/6.1.x/hibernate/api/org/grails/datastore/gorm/GormEntity.html#get(java.io.Serializable) get(id)]</code>:<syntaxhighlight lang="groovy">
 +
def book = Book.get(23)
 +
</syntaxhighlight>Mit der Methode [http://gorm.grails.org/6.1.x/hibernate/api/org/grails/datastore/gorm/GormEntity.html#getAll(java.io.Serializable) getAll(ids)] kann man auch gleich mehrere Instanzen nach der ID ausfindig machen.<syntaxhighlight lang="groovy">
 +
def books = Book.getAll(23, 93, 81)
 +
</syntaxhighlight>
 +
 
 +
 
 +
===[http://gorm.grails.org/6.1.x/hibernate/manual/#finders Dynamische Finder]===
 +
'''G'''ORM unterstützt das Konzept der dynamischen Finder. Dynamische Finder sieht aus wie ein statischer Methodenaufruf, aber die Method selbst sind auf Codeebene in keiner Form vorhanden.
 +
 
 +
Stattdessen wird eine Methode mithilfe von [https://docs.groovy-lang.org/docs/next/html/documentation/core-metaprogramming.html Codesynthese zur Laufzeit] auf magische Weise generiert, basierend auf den Eigenschaften einer bestimmten Klasse. Nehmen wir zum Beispiel die Klasse <code>Book</code>:<syntaxhighlight lang="groovy">
 +
class Book {
 +
    String title
 +
    Date releaseDate
 +
    Author author
 +
}
 +
class Author {
 +
    String name
 +
}
 +
</syntaxhighlight>Die Klasse <code>Book</code> hat Eigenschaften wie <code>title</code>, <code>releaseDate</code> und <code>author</code>. Diese können mithilfe der dynamischen <code>findBy*</code> und <code>findAllBy*</code>-Methoden in form von '''Methodenausdrücken''' genutzt werden:<syntaxhighlight lang="groovy">
 +
def book = Book.findByTitle("The Stand")
 +
 
 +
book = Book.findByTitleLike("Harry Pot%")
 +
 
 +
book = Book.findByReleaseDateBetween(firstDate, secondDate)
 +
 
 +
book = Book.findByReleaseDateGreaterThan(someDate)
 +
 
 +
book = Book.findByTitleLikeOrReleaseDateLessThan("%Something%", someDate)
 +
</syntaxhighlight><br />
 +
====Methodenausdrücke====
 +
Ein Methodenausdruck in GORM besteht aus dem Präfix wie <code>findBy*</code>, gefolgt von einem Ausdruck, der eine oder mehrere Eigenschaften kombiniert. Die Grundform ist:<syntaxhighlight lang="groovy">
 +
Book.findBy(<<Property>><<Comparator>><<Boolean Operator>>)?<<Property>><<Comparator>>
 +
</syntaxhighlight>Die mit einem "?" Token sind optional. Jeder Komparator ändert die Art der Abfrage. Beispielsweise:<syntaxhighlight lang="groovy">
 +
def book = Book.findByTitle("The Stand")
 +
 
 +
book =  Book.findByTitleLike("Harry Pot%")
 +
</syntaxhighlight>Im obigen Beispiel entspricht die erste Abfrage der (exakten) Gleichheits-Abfrage, während die letztere aufgrund des <code>Like</code>-Komparators einem SQL-<code>Like</code>-Ausdruck entspricht.
 +
 
 +
 
 +
'''<u>Die verfügbaren Komperatoren (Comperators) sind:</u>'''
 +
 
 +
*<code>InList</code> - In der Liste der angegebenen Werte
 +
 
 +
*<code>LessThan</code> - weniger als der gegebener Wert
 +
*<code>LessThanEquals</code> - kleiner oder gleich als der gegeben Wert
 +
*<code>GreaterThan</code> - größer als der gegebene Wert
 +
*<code>GreaterThanEquals</code> - Größer als oder gleich dem gegeben Wert
 +
*<code>Like</code> - Entspricht einem SQL-Like-Ausdruck
 +
*<code>Ilike</code> - Wie <code>Like</code>, außer dass Groß- und Kleinschreibung nicht berücksichtigt wird
 +
*<code>NotEqual</code> - Negiert die Gleichheitsbedingung
 +
*<code>InRange</code> - Zwischen den <code>from</code> und <code>to</code> Werten einer [http://grails.asia/groovy-range-examples Groovy-Range]
 +
*<code>Rlike</code> - Führt eine Regexp-LIKE in MySQL oder Oracle aus, anderenfalls wird auf <code>Like</code> zurückgefallen
 +
*<code>Between</code> - Zwischen zwei Werten (erfordert zwei Argumente)
 +
*<code>IsNotNull</code> - Kein Nullwert (nimmt kein Argument an)
 +
*<code>IsNull</code> - Ist ein Nullwert (nimmt kein Argument an)
 +
 
 +
Beachte: Die letzten drei Komperatoren fordern im Vergleich zu den anderen Komperatoren eine unterschiedliche Anzahl von Methodenargumenten an, wie das folgende Beispiel demonstrieren sollte:<syntaxhighlight lang="groovy">
 +
def now = new Date()
 +
def lastWeek = now - 7
 +
def book = Book.findByReleaseDateBetween(lastWeek, now)
 +
 
 +
books = Book.findAllByReleaseDateIsNull()
 +
books = Book.findAllByReleaseDateIsNotNull()
 +
</syntaxhighlight><br />
 +
====Boolesche Logik====
 +
Methodenausdrücke können auch einen booleschen Operator verwenden, um zwei oder mehr Kriterien zu kombinieren:<syntaxhighlight lang="groovy">
 +
def books = Book.findAllByTitleLikeAndReleaseDateGreaterThan("%Java%", new Date() - 30)
 +
</syntaxhighlight>In diesem Fall verwenden wir <code>And</code> in der Mitte der Abfrage, um sicherzustellen, dass beide Bedingungen erfüllt sind. Man kann jedoch auch <code>Or</code> verwenden:<syntaxhighlight lang="groovy">
 +
def books = Book.findAllByTitleLikeOrReleaseDateGreaterThan("%Java%", new Date() - 30)
 +
</syntaxhighlight>Man kann so viele Kriterien kombinieren wie man möchten, aber alle müssen mit <code>And</code> oder <code>Or</code> kombiniert werden. Wenn man <code>And</code> und <code>Or</code> kombinieren möchte oder wenn die Anzahl der Kriterien einen sehr langen Methodennamen ergibt, wäre es gut stattdessen einfach in eine Kriterien- oder HQL-Abfrage zu verwenden (siehe später).
 +
<br />
 +
====Assoziationen Abfragen====
 +
<syntaxhighlight lang="groovy">
 +
def author = Author.findByName("Stephen King")
 +
 
 +
def books = author ? Book.findAllByAuthor(author) : []
 +
</syntaxhighlight>In diesem Fall verwenden wir die <code>Author</code>-Instanz wenn sie nicht null ist um alle <code>Book</code>-Instanzen für den angegebenen <code>Author</code> abzurufen.
 +
<br />
 +
====Pagination und Sortierung der Ergebnisse====
 +
Wie bei der <code>list()</code>-Methode funktionieren hier auch die genannten Argumente zur Pagination und Sortierung der Ergebnisse. Diese werden als finales Argument in einer Map angegeben.<syntaxhighlight lang="groovy">
 +
def books = Book.findAllByTitleLike("Harry Pot%",
 +
              [max: 3, offset: 2, sort: "title", order: "desc"])
 +
</syntaxhighlight>
 +
 
 +
 
 +
===[http://gorm.grails.org/6.1.x/hibernate/manual/#whereQueries "Where" Abfragen]===
 +
Die <code>[http://gorm.grails.org/6.1.x/hibernate/api/org/grails/datastore/gorm/GormEntity.html#where(groovy.lang.Closure) where()]</code>-Methode baut auf der Unterstützung von [http://gorm.grails.org/6.1.x/hibernate/manual/#detachedCriteria Detached Criteria] auf, indem sie eine erweiterte DSL-Abfrage zur Kompilierungszeit für allgemeine Abfragen bereitstellt. Die <code>where</code>-Methode ist flexibler als dynamische Finder, weniger ausführlich als Kriterien und bietet dennoch einen leistungsstarken Mechanismus zum Erstellen von Abfragen.
 +
 
 +
====Grundlegende Abfragen====
 +
Die <code>[http://gorm.grails.org/6.1.x/hibernate/api/org/grails/datastore/gorm/GormEntity.html#where(groovy.lang.Closure) where()]</code> Methode akzeptiert eine Closure, die den regulären <code>Collection</code>-Methoden von Groovy sehr ähnlich sieht. Die Closure sollte die logischen Kriterien im regulären Groovy-Syntax definieren:<syntaxhighlight lang="groovy">
 +
def query = Person.where {
 +
  firstName == "Bart"
 +
}
 +
Person bart = query.find()
 +
</syntaxhighlight>Das zurückgegebene Objekt ist eine [http://gorm.grails.org/6.1.x/hibernate/api/grails/gorm/DetachedCriteria.html DetachedCriteria]-Instanz. Dies bedeutet, dass es keiner bestimmten Datenbankverbindung oder Sitzung zugeordnet ist. Dies bedeutet, dass man die <code>where</code>-Methode verwenden könnte, um allgemeine Abfragen auf Klassenebene zu definieren:<syntaxhighlight lang="groovy">
 +
import grails.gorm.*
 +
 
 +
class Person {
 +
    static DetachedCriteria<Person> simpsons = where {
 +
        lastName == "Simpson"
 +
    }
 +
    ...
 +
}
 +
...
 +
Person.simpsons.each { Person p ->
 +
    println p.firstname
 +
}
 +
</syntaxhighlight>'''Die Ausführung von where-Abfragen ist verzögert und erfolgt nur bei Verwendung der <code>DetachedCriteria</code>-Instanz'''. '''Wenn man eine Abfrage im <code>where</code>-Stil sofort ausführen möchten, gibt es Variationen von <code>findAll</code> und <code>find</code>-Methoden, um dies zu erreichen:'''<syntaxhighlight lang="groovy">
 +
def results = Person.findAll {
 +
    lastName == "Simpson"
 +
}
 +
def results = Person.findAll(sort:"firstName") {
 +
    lastName == "Simpson"
 +
}
 +
Person p = Person.find { firstName == "Bart" }
 +
</syntaxhighlight>Jeder Groovy-Operator kann einer regulären Kriterien-methode zugeordnet werden. Die folgende Tabelle enthält eine Zuordnung der Groovy-Operatoren zu den Methoden:
 +
{| class="wikitable"
 +
!Operator
 +
!Abgebildet als Kriterien <u>Methode</u>
 +
!Beschreibung
 +
|-
 +
|'''<code>==</code>'''
 +
|eq
 +
|Gleich wie
 +
|-
 +
|'''<code>!=</code>'''
 +
|ne
 +
|Nicht gleich wie
 +
|-
 +
|'''<code>></code>'''
 +
|gt
 +
|Größer als
 +
|-
 +
|'''<code><</code>'''
 +
|lt
 +
|Kleiner als
 +
|-
 +
|'''<code>>=</code>'''
 +
|ge
 +
|Größer als oder gleich wie
 +
|-
 +
|'''<code><=</code>'''
 +
|le
 +
|Kleiner als oder gleich wie
 +
|-
 +
|'''<code>in</code>'''
 +
|inList
 +
|In der gegebenen Liste enthalten
 +
|-
 +
|'''<code>==~</code>'''
 +
|like
 +
|Ähnlich wie dem gegeben String
 +
|-
 +
|'''<code>=~</code>'''
 +
|ilike
 +
|<code>like</code>, aber ohne Acht auf Groß-und-Kleinschreibung
 +
|}
 +
Es ist möglich, reguläre Groovy-Vergleichsoperatoren und -Logik zu verwenden, um komplexe Abfragen zu formulieren:<syntaxhighlight lang="groovy">
 +
def query = Person.where {
 +
    (lastName != "Simpson" && firstName != "Fred") || (firstName == "Bart" && age > 9)
 +
}
 +
def results = query.list(sort:"firstName")
 +
</syntaxhighlight>Die Groovy-Regex-Matching-Operatoren werden <code>like</code> und <code>ilike</code>-Abfragen zugeordnet, es sei denn, der Ausdruck auf der rechten Seite ist ein <code>Pattern</code>-Objekt. In diesem Fall wird sie einer <code>rlike</code> Abfrage zugeordnet:<syntaxhighlight lang="groovy">
 +
def query = Person.where {
 +
    firstName ==~ ~/B.+/
 +
}
 +
</syntaxhighlight>Der von den dynamischen Findern bekannte <code>between</code>-Ausdruck lässt sich in einer <code>where</code>-Abfrage mit dem Schlüsselwort <code>in</code> durchsetzen:<syntaxhighlight lang="groovy">
 +
def query = Person.where {
 +
    age in 18..65
 +
}
 +
</syntaxhighlight>Die von den dynamischen Findern bekannte <code>isNull</code> und <code>isNotNull</code> Abfrage kann durch einen simplen Vergleich mit <code>null</code> realisiert werden<syntaxhighlight lang="groovy">
 +
def query = Person.where {
 +
    middleName == null
 +
}
 +
</syntaxhighlight><br />
 +
 
 +
====Komposition von Abfragen====
 +
Da der Rückgabewert der <code>where</code>-Methode eine [http://gorm.grails.org/6.1.x/hibernate/manual/#detachedCriteria DetachedCriteria]-Instanz ist, kann man aus der ursprünglichen Abfrage neue Abfragen erstellen (<u>aneinanderreihen</u>):<syntaxhighlight lang="groovy">
 +
DetachedCriteria<Person> query = Person.where {
 +
    lastName == "Simpson"
 +
}
 +
DetachedCriteria<Person> bartQuery = query.where {
 +
    firstName == "Bart"
 +
}
 +
Person p = bartQuery.find()
 +
</syntaxhighlight>Beachten: Eine als Variable definierte Closure kann nur dann an die <code>where</code>-Methode übergeben können, wenn er explizit in eine DetachedCriteria-Instanz umgewandelt wurde. Mit anderen Worten, Folgendes führt zu einem Fehler:<syntaxhighlight lang="groovy">
 +
def callable = {
 +
    lastName == "Simpson"
 +
}
 +
def query = Person.where(callable)
 +
</syntaxhighlight>Das Obige muss wie folgt geschrieben werden:<syntaxhighlight lang="groovy">
 +
import grails.gorm.DetachedCriteria
 +
 
 +
def callable = {
 +
    lastName == "Simpson"
 +
} as DetachedCriteria<Person>
 +
def query = Person.where(callable)
 +
</syntaxhighlight>Wie man sieht, wird die Closure-Definition (unter Verwendung des Schlüsselworts <code>as</code>) in eine DetachedCriteria-Instanz umgewandelt (ge-castet), die auf die Person-Klasse abzielt.
 +
 
 +
<br />
 +
 
 +
====''Konjunktion, Disjunktion und Negation''====
 +
Wie bereits erwähnt, kann man reguläre logische Groovy-Operatoren (<code>||</code> und <code>&&</code>) zu Konjunktionen und Disjunktionen kombinieren:<syntaxhighlight lang="groovy">
 +
def query = Person.where {
 +
    (lastName != "Simpson" && firstName != "Fred") || (firstName == "Bart" && age > 9)
 +
}
 +
</syntaxhighlight>Man kann logische Komparationen wie gewohnt mit einem <code>!</code> negieren:<syntaxhighlight lang="groovy">
 +
def query = Person.where {
 +
    firstName == "Fred" && !(lastName == 'Simpson')
 +
}
 +
</syntaxhighlight><br />
 +
 
 +
====Eigenschaftenvergleichsabfragen====
 +
Wenn man einen Eigenschaftsnamen sowohl auf der linken als auch auf der rechten Seite eines Vergleichsausdrucks verwendet, werden automatisch die entsprechenden Eigenschaftsvergleichs<u>kriterien</u> verwendet:<syntaxhighlight lang="groovy">
 +
def query = Person.where {
 +
  firstName == lastName
 +
}
 +
</syntaxhighlight>In der folgenden Tabelle wird beschrieben, wie jeder Vergleichsoperator den Eigenschaftenvergleichs<u>methoden</u> der einzelnen Kriterien zugeordnet wird:
 +
{| class="wikitable"
 +
!Operator
 +
!Abgebildet als Kriterien <u>Methode</u>
 +
!Beschreibung
 +
|-
 +
|'''=='''
 +
|eqProperty
 +
|Gleich wie
 +
|-
 +
|'''!='''
 +
|neProperty
 +
|Nicht gleich wie
 +
|-
 +
|'''>'''
 +
|gtProperty
 +
|Größer als
 +
|-
 +
|'''<'''
 +
|ltProperty
 +
|Kleiner als
 +
|-
 +
|'''>='''
 +
|geProperty
 +
|Größer als oder gleich wie
 +
|-
 +
|'''<='''
 +
|leProperty
 +
|Kleiner als oder gleich wie
 +
|}
 +
<br />
 +
 
 +
====Assoziationen====
 +
Zuordnungen können mithilfe des Punktoperators abgefragt werden, um den Eigenschaftsnamen der abzufragenden Zuordnung anzugeben:<syntaxhighlight lang="groovy">
 +
def query = Pet.where {
 +
    owner.firstName == "Joe" || owner.firstName == "Fred"
 +
}
 +
</syntaxhighlight>Man kann mehrere Kriterien in einem Closure-Methodenaufruf gruppieren, wobei der Name der Methode mit dem Zuordnungsnamen übereinstimmt:<syntaxhighlight lang="groovy">
 +
def query = Person.where {
 +
    pets { name == "Jack" || name == "Joe" }
 +
}
 +
</syntaxhighlight>Diese Technik kann mit anderen Kriterien der obersten Ebene kombiniert werden:<syntaxhighlight lang="groovy">
 +
def query = Person.where {
 +
    pets { name == "Jack" } || firstName == "Ed"
 +
}
 +
</syntaxhighlight>Bei Assoziationen mit Sammlungen können Abfragen auf die Größe der Sammlung angewendet werden:<syntaxhighlight lang="groovy">
 +
def query = Person.where {
 +
      pets.size() == 2
 +
}
 +
</syntaxhighlight>Die folgende Tabelle zeigt, welcher Operator für jeden <code>size()</code>-Vergleich auf welche Kriterien<u>methode</u> abgebildet wird:
 +
{| class="wikitable"
 +
!Operator
 +
!Abgebildet als Kriterien <u>Methode</u>
 +
!Beschreibung
 +
|-
 +
|'''=='''
 +
|sizeEq
 +
|Die Größe der Sammlung ist gleich wie
 +
|-
 +
|'''!='''
 +
|sizeNe
 +
|Die Größe der Sammlung ist nicht gleich wie
 +
|-
 +
|'''>'''
 +
|sizeGt
 +
|Die Größe der Sammlung ist größer als
 +
|-
 +
|'''<'''
 +
|sizeLt
 +
|Die Größe der Sammlung ist kleiner als
 +
|-
 +
|'''>='''
 +
|sizeGe
 +
|Die Größe der Sammlung ist größer als oder gleich wie
 +
|-
 +
|'''<='''
 +
|sizeLe
 +
|Die Größe der Sammlung ist kleiner als oder gleich wie
 +
|}
 +
<br />
 +
 
 +
====Aliase und Sortierung====
 +
Wenn man eine Abfrage für eine Assoziation definiert wird automatisch ein Alias für die Abfrage generiert. Zum Beispiel die folgende Abfrage:<syntaxhighlight lang="groovy">
 +
def query = Pet.where {
 +
    owner.firstName == "Fred"
 +
}
 +
</syntaxhighlight>Generiert einen Alias für die <code>owner</code>-Assoziation, z.B. <code>owner_alias_0</code>. Diese generierten Aliase sind in den meisten Fällen in Ordnung, aber nicht nützlich, wenn man die Ergebnisse später sortieren oder mit einer Projektion versehen möchten. Die folgende Abfrage schlägt beispielsweise fehl:<syntaxhighlight lang="groovy">
 +
// fails because a dynamic alias is used
 +
Pet.where {
 +
    owner.firstName == "Fred"
 +
}.list(sort:"owner.lastName")
 +
</syntaxhighlight>Wenn man die Ergebnisse sortieren möchte, sollte ein expliziter Alias verwendet werden, der durch einfaches Deklarieren einer Variablen in der <code>where</code>-Abfrage definiert werden kann:<syntaxhighlight lang="groovy">
 +
def query = Pet.where {
 +
    // Define an alias called o1
 +
    def o1 = owner
 +
    // Use the alias in the query itself
 +
    o1.firstName == "Fred"
 +
// Use the alias to sort the results
 +
}.list(sort:'o1.lastName')
 +
</syntaxhighlight>Durch Zuweisen des Namens einer Zuordnung zu einer lokalen Variablen wird diese automatisch zu einem Alias, der innerhalb der Abfrage selbst und auch zum Sortieren oder Projizieren der Ergebnisse verwendet werden kann.
 +
 
 +
<br />
 +
 
 +
====Unterabfragen====
 +
Es ist möglich, Unterabfragen innerhalb von Abfragen auszuführen. Um beispielsweise alle Personen zu finden, die älter als das Durchschnittsalter sind, kann die folgende Abfrage verwendet werden:<syntaxhighlight lang="groovy">
 +
final query = Person.where {
 +
  age > avg(age)
 +
}
 +
</syntaxhighlight><br />
 +
{| class="wikitable"
 +
!Methode
 +
!Beschreibung
 +
|-
 +
|'''avg'''
 +
|Der Durchschnitt aller Werte
 +
|-
 +
|'''sum'''
 +
|Die Summe aller Werte
 +
|-
 +
|'''max'''
 +
|Der maximale Wert
 +
|-
 +
|'''min'''
 +
|Der minimale Wert
 +
|-
 +
|'''count'''
 +
|Die Anzahl an Werten
 +
|-
 +
|'''property'''
 +
|Gibt eine Eigenschaft der resultierenden Entitäten zurück
 +
|}
 +
Man kann zusätzliche Kriterien auf jede Unterabfrage anwenden, indem man die <code>of</code>-Methode verwendet und eine Closure übergibt, der die Kriterien enthält:<syntaxhighlight lang="groovy">
 +
def query = Person.where {
 +
  age > avg(age).of { lastName == "Simpson" } && firstName == "Homer"
 +
}
 +
</syntaxhighlight>Da die <code>property</code>-Unterabfrage mehrere Ergebnisse zurückgibt, vergleicht das verwendete Kriterium alle Ergebnisse. Die folgende Abfrage findet beispielsweise alle Personen, die jünger als Personen mit dem Nachnamen "Simpson" sind:<syntaxhighlight lang="groovy">
 +
Person.where {
 +
    age < property(age).of { lastName == "Simpson" }
 +
}
 +
</syntaxhighlight><br />
 +
 
 +
====Erweiterte Unterabfragen in GORM====
 +
Die Unterstützung für Unterabfragen wurde erweitert. Man kann jetzt mit '''verschachtelten''' '''Unterabfragen''' rumwerken:<syntaxhighlight lang="groovy">
 +
def results = Person.where {
 +
    firstName in where { age < 18 }.firstName
 +
}.list()
 +
</syntaxhighlight>Kriterien und <code>where</code> Abfragen können nahtlos '''gemischt''' werden:<syntaxhighlight lang="groovy">
 +
def results = Person.withCriteria {
 +
    notIn "firstName", Person.where { age < 18 }.firstName
 +
}
 +
</syntaxhighlight>Unterabfragen können mit '''Projektionen''' verwendet werden:<syntaxhighlight lang="groovy">
 +
def results = Person.where {
 +
    age > where { age > 18 }.avg('age')
 +
}
 +
</syntaxhighlight>'''Korrelierte Abfragen, die zwei Domänenklassen umfassen''', können verwendet werden:<syntaxhighlight lang="groovy">
 +
def employees = Employee.where {
 +
    region.continent in ['APAC', "EMEA"]
 +
}.id()
 +
def results = Sale.where {
 +
    employee in employees && total > 100000
 +
}.employee.list()
 +
</syntaxhighlight>Unterstützung für Aliase (Querverweise) mithilfe einfacher Variablendeklarationen wurde hinzugefügt:<syntaxhighlight lang="groovy">
 +
def query = Employee.where {
 +
    def em1 = Employee
 +
    exists Sale.where {
 +
        def s1 = Sale
 +
        def em2 = employee
 +
        return em2.id == em1.id
 +
    }.id()
 +
}
 +
def results = query.list()
 +
</syntaxhighlight><br />
 +
 
 +
====Andere Funktionen====
 +
Im Rahmen einer Abfrage stehen einem verschiedene Funktionen zur Verfügung. Diese sind in der folgenden Tabelle zusammengefasst:
 +
{| class="wikitable"
 +
!Method
 +
!Description
 +
|-
 +
|'''second'''
 +
|Die Sekunde einer <code>Date</code>-Eigenschaft
 +
|-
 +
|'''minute'''
 +
|Die Minute einer <code>Date</code>-Eigenschaft
 +
|-
 +
|'''hour'''
 +
|Die Stunde einer <code>Date</code>-Eigenschaft
 +
|-
 +
|'''day'''
 +
|Der Tag einer <code>Date</code>-Eigenschaft
 +
|-
 +
|'''month'''
 +
|Den Monat einer <code>Date</code>-Eigenschaft
 +
|-
 +
|'''year'''
 +
|Der Jahr einer <code>Date</code>-Eigenschaft
 +
|-
 +
|'''lower'''
 +
|Konvertiert eine String-Eigenschaft in kleinbuchstaben
 +
|-
 +
|'''upper'''
 +
|Konvertiert eine String-Eigenschaft in Großbuchstaben
 +
|-
 +
|'''length'''
 +
|Die Länge einer String-Eigenschaft
 +
|-
 +
|'''trim'''
 +
|[http://grails.asia/groovy-trim-examples Trimmt] eine String-Eigenschaft
 +
|}
 +
'''HINWEIS: Derzeit können Funktionen nur auf Eigenschaften oder Zuordnungen von Domänenklassen angewendet werden. Man kann beispielsweise keine Funktion für ein Ergebnis einer Unterabfrage verwenden.'''
 +
 
 +
Die folgende Abfrage kann beispielsweise verwendet werden, um alle 2011 geborenen <code>Pet</code>s zu finden:<syntaxhighlight lang="groovy">
 +
def query = Pet.where {
 +
    year(birthDate) == 2011
 +
}
 +
</syntaxhighlight>Man kann Funktionen auch auf Assoziationen anwenden:<syntaxhighlight lang="groovy">
 +
def query = Person.where {
 +
    year(pets.birthDate) == 2009
 +
}
 +
</syntaxhighlight><br />
 +
 
 +
====Batch-Updates und Löschungen====
 +
Da jeder <code>where</code>-Methodenaufruf eine DetachedCriteria-Instanz zurückgibt kann man <code>where</code>-Abfragen verwenden, um Stapelvorgänge wie Stapelaktualisierungen und -löschungen auszuführen. Mit der folgenden Abfrage werden beispielsweise alle Personen mit dem Nachnamen "Simpson" aktualisiert, um den Nachnamen "Bloggs" zu erhalten:<syntaxhighlight lang="groovy">
 +
DetachedCriteria<Person> query = Person.where {
 +
    lastName == 'Simpson'
 +
}
 +
int total = query.updateAll(lastName:"Bloggs")
 +
</syntaxhighlight>HINWEIS: Join-Abfragen (Abfragen, die Zuordnungen abfragen) sind nicht zulässig sind.
 +
 
 +
Um die abgefragten/gefunden Datensätze stapelweisen zu Löschen kann man die Methode <code>deleteAll</code> verwenden:<syntaxhighlight lang="groovy">
 +
DetachedCriteria<Person> query = Person.where {
 +
    lastName == 'Simpson'
 +
}
 +
int total = query.deleteAll()
 +
</syntaxhighlight>
 +
 
 +
 
 +
===[http://gorm.grails.org/6.1.x/hibernate/manual/#criteria (Angehängte/Attached) Kriterien]===
 +
Kriterien sind eine erweiterte/komplexere Methode, bei der mithilfe eines Groovy-Builders potenziell komplexe Abfragen erstellt werden. Kriterien-Abfragen zu nutzen ist ein viel besserer Ansatz als das Erstellen von Query-Strings mit einem StringBuilder.
 +
 
 +
Kriterien können entweder mit den Methoden [http://gorm.grails.org/6.1.x/hibernate/api/org/grails/datastore/gorm/GormEntity.html#createCriteria() createCriteria()] oder [http://gorm.grails.org/6.1.x/hibernate/api/org/grails/datastore/gorm/GormEntity.html#withCriteria(groovy.lang.Closure) withCriteria(Closure)] verwendet werden.
 +
 
 +
Der Builder verwendet die Kriterien-API von Hibernate. Die Knoten in diesem Builder ordnen die statischen Methoden zu, die in der [http://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/criterion/Restrictions.html Restrictions]-Klasse der Hibernate Criteria-API gefunden werden kann. Beispielsweise:<syntaxhighlight lang="groovy">
 +
def c = Account.createCriteria()
 +
def results = c {
 +
    between("balance", 500, 1000)
 +
    eq("branch", "London")
 +
    or {
 +
        like("holderFirstName", "Fred%")
 +
        like("holderFirstName", "Barney%")
 +
    }
 +
    maxResults(10)
 +
    order("holderLastName", "desc")
 +
}
 +
</syntaxhighlight>Mit diesem Kriterium werden bis zu 10 <code>Account</code>-Objekte in einer Liste ausgewählt, die den folgenden Kriterien entsprechen:
 +
 
 +
*<code>balance</code> liegt zwischen 500 und 1000
 +
*<code>branche</code> ist London
 +
 
 +
*<code>holderFirstName</code> beginnt mit Fred oder Barney
 +
 
 +
Die Ergebnisse werden in absteigender Reihenfolge nach <code>holderLastName</code> sortiert.
 +
 
 +
Wenn keine Datensätze mit den oben genannten Kriterien gefunden werden, wird eine leere Liste zurückgegeben.
 +
<br />
 +
 
 +
====Konjunktionen und Disjunktionen====
 +
Wie im vorherigen Beispiel gezeigt können Kriterien in einem logischen ODER mit einem <code>or {}</code>-Block gruppiert werden:<syntaxhighlight lang="groovy">
 +
or {
 +
    between("balance", 500, 1000)
 +
    eq("branch", "London")
 +
}
 +
</syntaxhighlight>Dies funktioniert auf die gleiche Angehensweiße mit einem logischen UND:<syntaxhighlight lang="groovy">
 +
and {
 +
    between("balance", 500, 1000)
 +
    eq("branch", "London")
 +
}
 +
</syntaxhighlight>Auch Negierungen können mithilfe einer logischen NOT-Anweisung durchgesetzt werden:<syntaxhighlight lang="groovy">
 +
not {
 +
    between("balance", 500, 1000)
 +
    eq("branch", "London")
 +
}
 +
</syntaxhighlight>Alle Bedingungen der obersten Ebene sind implizit mit einem logischen UND verbunden.
 +
 
 +
<br />
 +
 
 +
====Assoziationen====
 +
Assoziationen können abgefragt werden, indem ein Knoten vorhanden ist, der dem Eigenschaftsnamen entspricht. Angenommen, die <code>Account</code>-Klasse hatte viele <code>Transaction</code>-Objekte:<syntaxhighlight lang="groovy">
 +
class Account {
 +
    //...
 +
    static hasMany = [transactions: Transaction]
 +
    //...
 +
}
 +
</syntaxhighlight>Wir können diese Zuordnung abfragen, indem wir den Eigenschaftsnamen <code>transactions</code> als Builder-Knoten verwenden:<syntaxhighlight lang="groovy">
 +
def c = Account.createCriteria()
 +
def now = new Date()
 +
def results = c.list {
 +
    transactions {
 +
        between('date', now - 10, now)
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
Mit dem obigen Code werden alle <code>Account</code>-Instanzen gefunden, die in den letzten 10 Tagen Transaktionen ausgeführt haben. Man kann solche Assoziations-Abfragen auch in logischen Blöcken verschachteln:<syntaxhighlight lang="groovy">
 +
def c = Account.createCriteria()
 +
def now = new Date()
 +
def results = c.list {
 +
    or {
 +
        between('created', now - 10, now)
 +
        transactions {
 +
            between('date', now - 10, now)
 +
        }
 +
    }
 +
}
 +
</syntaxhighlight>In diesem Beispiel finden wir alle Konten, die entweder Transaktionen in den letzten 10 Tagen ausgeführt haben oder in den letzten 10 Tagen kürzlich erstellt wurden.
 +
 
 +
<br />
 +
 
 +
====Projections====
 +
Projektionen können verwendet werden, um die Ergebnisse anzupassen. Hierzu definiert man einen Knoten namens <code>projections</code> im Baum des Kriterienerstellers. Innerhalb des Projektionsknotens gibt es äquivalente Methoden zu den Methoden die man in der Klasse <code>[http://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/criterion/Projections.html Projections]</code> findet:<syntaxhighlight lang="groovy">
 +
def c = Account.createCriteria()
 +
 
 +
def numberOfBranches = c.get {
 +
    projections {
 +
        countDistinct('branch')
 +
    }
 +
}
 +
</syntaxhighlight>Wenn in der Projektion mehrere Felder angegeben sind, wird eine Werte<u>liste</u> zurückgegeben. Andernfalls wird ein einzelner Wert zurückgegeben.
 +
<br />
 +
 
 +
====Projection-Ergebnisse transformieren====
 +
Wenn der von der Kriterienmethode zurückgegebene Rohwert oder das einfache Objektarray nicht den Anforderungen entspricht, kann das Ergebnis mit einem [http://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/transform/ResultTransformer.html ResultTransformer] transformiert werden. Angenommen, man möchte die Kriterienergebnisse in eine Karte umwandeln, damit wir die Werte einfach nach Schlüssel referenzieren können:<syntaxhighlight lang="groovy">
 +
def c = Account.createCriteria()
 +
 
 +
def accountsOverview = c.get {
 +
    // ALIAS_TO_ENTITY_MAP: Each row of results is a Map from alias to entity instance
 +
    resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)
 +
    projections {
 +
        sum('balance', 'allBalances')
 +
        countDistinct('holderLastName', 'lastNames')
 +
    }
 +
}
 +
 
 +
// accountsOverview.allBalances
 +
// accountsOverview.lastNames
 +
</syntaxhighlight>Beachte: Wir haben jeder Projektion einen Alias als zusätzlichen Parameter hinzugefügt, der als Schlüssel verwendet werden soll. Damit dies funktioniert, müssen für alle Projektionen Aliase definiert sein, da sonst der entsprechende Karteneintrag nicht erstellt wird.
 +
 
 +
Wir können das Ergebnis auch über die [http://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/transform/Transformers.html#aliasToBean-java.lang.Class- Transformers.aliasToBean()]-Methode in ein Objekt unserer Wahl umwandeln. In diesem Fall verwandeln wir es in ein <code>AccountOverview</code>-Objekt:<syntaxhighlight lang="groovy">
 +
class AccountsOverview {
 +
    Number allBalances
 +
    Number lastNames
 +
}
 +
 
 +
 
 +
</syntaxhighlight><syntaxhighlight lang="groovy">
 +
def c = Account.createCriteria()
 +
 
 +
def accountsOverview = c.get {
 +
    // aliasToBean: Creates a resulttransformer that will inject aliased values into instances of Class via property methods or fields.
 +
    resultTransformer(Transformers.aliasToBean(AccountsOverview))
 +
    projections {
 +
        sum('balance', 'allBalances')
 +
        countDistinct('holderLastName', 'lastNames')
 +
    }
 +
}
 +
 
 +
// accountsOverview instanceof AccountsOverview
 +
</syntaxhighlight>Jeder Alias muss eine entsprechende Eigenschaft oder einen expliziten Setter für die Bean haben, andernfalls wird eine Ausnahme ausgelöst.
 +
<br />
 +
====SQL-Projektionen====
 +
Die Kriterien-DSL bietet Zugriff auf die SQL-Projektions-API von Hibernate.
 +
 
 +
Das erste Argument für die <code>[https://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/criterion/Projections.html#sqlProjection-java.lang.String-java.lang.String:A-org.hibernate.type.Type:A- sqlProjection]</code>-Methode ist das SQL-Fragment, das die Projektionen definiert.
 +
 
 +
Das zweite Argument ist eine Liste von Zeichenfolgen, die die Aliasnamen der Spalten darstellen, die den in SQL ausgedrückten projizierten Werten entsprechen.
 +
 
 +
Das dritte Argument ist eine Liste von <code>org.hibernate.type.Type</code>-Instanzen, die den in SQL ausgedrückten projizierten Werten entsprechen. Die API unterstützt alle <code>org.hibernate.type.Type</code>-Objekte, aber Konstanten wie INTEGER, LONG, FLOAT usw. werden vom DSL bereitgestellt, die allen in <code>org.hibernate.type.StandardBasicTypes</code> definierten Typen entsprechen.
 +
 
 +
 
 +
 
 +
Betrachte man die folgenden Domänenklasse..<syntaxhighlight>
 +
// Box is a domain class...
 +
class Box {
 +
    int width
 +
    int height
 +
}
 +
 
 +
 
 +
</syntaxhighlight>..und führt die folgende Projektionsanweisung..<syntaxhighlight lang="groovy">
 +
// Use SQL projections to retrieve the perimeter and area of all of the Box instances...
 +
def c = Box.createCriteria()
 +
 
 +
def results = c.list {
 +
    projections {
 +
      sqlProjection '(2 * (width + height)) as perimeter, (width * height) as area', ['perimeter', 'area'], [INTEGER, INTEGER]
 +
    }
 +
}
 +
</syntaxhighlight>...mit diesem Datensatz aus..
 +
{| class="wikitable"
 +
!width
 +
!height
 +
|-
 +
|2
 +
|7
 +
|-
 +
|2
 +
|8
 +
|-
 +
|2
 +
|9
 +
|-
 +
|4
 +
|9
 +
|}
 +
..bekommt man folgendes Ergebniss:<syntaxhighlight lang="groovy">
 +
[[18, 14], [20, 16], [22, 18], [26, 36]]
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
Wenn nur 1 Wert projiziert wird, müssen der Alias und der Typ nicht in eine Liste aufgenommen werden:<syntaxhighlight lang="groovy">
 +
def results = c.list {
 +
    projections {
 +
      sqlProjection 'sum(width * height) as totalArea', 'totalArea', INTEGER
 +
    }
 +
}
 +
</syntaxhighlight>Diese Abfrage würde ein einzelnes Ergebnis mit dem Wert 84 als Gesamtfläche aller Box-Instanzen zurückgeben.
 +
 
 +
DSL unterstützt gruppierte Projektionen mit der Methode <code>[https://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/criterion/Projections.html#sqlGroupProjection-java.lang.String-java.lang.String-java.lang.String:A-org.hibernate.type.Type:A- sqlGroupProjection]</code>.<syntaxhighlight lang="groovy">
 +
def results = c.list {
 +
    projections {
 +
        sqlGroupProjection 'width, sum(height) as combinedHeightsForThisWidth', 'width', ['width', 'combinedHeightsForThisWidth'], [INTEGER, INTEGER]
 +
    }
 +
}
 +
</syntaxhighlight>Das erste Argument für die Methode <code>sqlGroupProjection</code> ist das SQL-<code>SELECT</code>-Fragment, das die Projektionen definiert.
 +
 
 +
Das zweite Argument repräsentiert die SQL <code>GROUP BY</code>-Klausel, die Teil der Abfrage sein sollte. Diese Zeichenfolge kann ein einzelner Spaltenname oder eine durch Kommas getrennte Liste von Spaltennamen sein.
 +
 
 +
Das dritte Argument ist eine Liste von Zeichenfolgen, die Spaltenaliasnamen darstellen, die den in SQL ausgedrückten projizierten Werten entsprechen.
 +
 
 +
Das vierte Argument ist eine Liste von <code>org.hibernate.type.Type</code>-Instanzen, die den in SQL ausgedrückten projizierten Werten entsprechen.
 +
 
 +
Die obige Abfrage projiziert die kombinierten Höhen von Feldern, die nach Breite gruppiert sind, und liefert Ergebnisse, die wie folgt aussehen:<syntaxhighlight lang="groovy">
 +
[[2, 24], [4, 9]]
 +
</syntaxhighlight>Jede der inneren Listen enthält 2 Werte. Der erste Wert ist eine Breite und der zweite Wert ist die Summe der Höhen aller Boxen, die diese Breite haben.
 +
 
 +
 
 +
====''SQL-Restriktionen benutzen''====
 +
''Man kann auch auf die von Hibernate bereitgestellten SQL-Restriktions-Funktion zugreifen.''<syntaxhighlight lang="groovy">
 +
def c = Person.createCriteria()
 +
 
 +
def peopleWithShortFirstNames = c.list {
 +
    sqlRestriction "char_length(first_name) <= 4"
 +
}
 +
</syntaxhighlight>''SQL-Einschränkungen können parametrisiert werden, um SQL-Injection-Schwachstellen im Zusammenhang mit dynamischen Einschränkungen zu beheben.''<syntaxhighlight lang="groovy">
 +
def c = Person.createCriteria()
 +
 
 +
def peopleWithShortFirstNames = c.list {
 +
    sqlRestriction "char_length(first_name) < ? AND char_length(first_name) > ?", [maxValue, minValue]
 +
}
 +
</syntaxhighlight>''Beachte: Der Parameter ist SQL. Das im Beispiel referenzierte Attribut <code>first_name</code> bezieht sich auf das Persistenzmodell und nicht auf das Objektmodell wie in HQL-Abfragen. Die Person-Eigenschaft mit dem Namen <code>firstName</code> wird der Spalte <code>first_name</code> in der Datenbank zugeordnet, und man muss in der Zeichenfolge <code>sqlRestriction</code> darauf verweisen.''
 +
 
 +
''Das hier verwendete SQL ist nicht unbedingt datenbankübergreifend portierbar.''
 +
<br />
 +
 
 +
====Skrollbare Ergebnisse====
 +
Man kann die [http://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/ScrollableResults.html ScrollableResults]-Funktion von Hibernate verwenden, indem man die <code>scroll</code>-Methode aufruft:<syntaxhighlight lang="groovy">
 +
def results = crit.scroll {
 +
    maxResults(10)
 +
}
 +
def f = results.first()
 +
def l = results.last()
 +
def n = results.next()
 +
def p = results.previous()
 +
 
 +
def future = results.scroll(10)
 +
def accountNumber = results.getLong('number')
 +
</syntaxhighlight>Um die Dokumentation zu quotieren:<blockquote>Ein Ergebnisiterator, mit dem Sie sich in beliebigen Schritten innerhalb der Ergebnisse bewegen können. Das Query / ScrollableResults-Muster ist dem JDBC PreparedStatement / ResultSet-Muster sehr ähnlich, und die Semantik der Methoden dieser Schnittstelle ähnelt den ähnlich benannten Methoden in ResultSet.</blockquote>Im Gegensatz zu JDBC sind die Ergebnisspalten von Null an nummeriert.
 +
<br />
 +
 
 +
====Festlegen von Eigenschaften in der Criteria-Instanz====
 +
Wenn ein Knoten in der Builder-Struktur nicht mit einem bestimmten Kriterium übereinstimmt, wird versucht, eine Eigenschaft für das Criteria-Objekt selbst festzulegen. Dies ermöglicht den vollständigen Zugriff auf alle Eigenschaften in dieser Klasse. In diesem Beispiel werden <code>[https://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/Criteria.html#setMaxResults-int- setMaxResults]</code>, <code>[https://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/Criteria.html#setFirstResult-int- setFirstResult]</code> und <code>[https://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/Criteria.html#setFetchMode-java.lang.String-org.hibernate.FetchMode- setFetchMode]</code> für die [http://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/Criteria.html Criteria]-Instanz aufgerufen:<syntaxhighlight lang="groovy">
 +
import org.hibernate.FetchMode as FM
 +
...
 +
def results = c.list {
 +
    maxResults(10)
 +
    firstResult(50)
 +
    fetchMode("aRelationship", FM.JOIN)
 +
}
 +
</syntaxhighlight><br />
 +
 
 +
====Abfragen mit Eager-Fetching durchführen====
 +
Die <code>join</code>-Methode weist die Kriterien-API an, einen JOIN zu verwenden, um die benannten Zuordnungen zu den <code>Task</code>-Instanzen abzurufen. <syntaxhighlight lang="groovy">
 +
def criteria = Task.createCriteria()
 +
def tasks = criteria.list{
 +
    eq "assignee.id", task.assignee.id
 +
    join 'assignee'
 +
    join 'project'
 +
    order 'priority', 'asc'
 +
}
 +
</syntaxhighlight>
 +
 
 +
=====Nicht empfohlen für Eins-zu-Viele=====
 +
Es ist wahrscheinlich am besten, dies nicht für Eins-zu-Viele-Assoziationen zu verwenden, da man höchstwahrscheinlich doppelte Ergebnisse erzielen wird. Verwende stattdessen den <code>select</code> Fetch-Modus:<syntaxhighlight lang="groovy">
 +
import org.hibernate.FetchMode as FM
 +
...
 +
def results = Airport.withCriteria {
 +
    eq "region", "EMEA"
 +
    fetchMode "flights", FM.SELECT
 +
}
 +
</syntaxhighlight>Obwohl dieser Ansatz eine zweite Abfrage auslöst, um die <code>flights</code>-Assoziationen zu erhalten, erhält man zuverlässige Ergebnisse - auch mit der Option <code>maxResults</code>. <code>fetchMode</code> und <code>join</code> sind allgemeine Einstellungen der Abfrage und können nur auf der obersten Ebene angegeben werden, d. h. Sie können nicht in Projektionen oder Zuordnungsbeschränkungen verwendet werden.
 +
 
 +
=====Implizites Eager-Fetching=====
 +
Ein wichtiger Punkt, den man berücksichtigen sollte, ist, dass Assoziationen automatisch geladen werden, wenn man Assoziationen in die Abfrageeinschränkungen afunimmt. Zum Beispiel in dieser Abfrage..<syntaxhighlight lang="groovy">
 +
def results = Airport.withCriteria {
 +
    eq "region", "EMEA"
 +
    flights {
 +
        like "number", "BA%"
 +
    }
 +
}
 +
</syntaxhighlight>..würde die <code>flights</code>-Sammlung eifrig über einen <code>JOIN</code> geladen, obwohl der Abrufmodus nicht explizit festgelegt wurde.
 +
<br />
 +
 
 +
====Methoden-Referenz====
 +
Wenn man den Builder ohne Methodennamen aufgerufen wird..<syntaxhighlight lang="groovy">
 +
c { /* */ }
 +
</syntaxhighlight>.. ist die Standard-Methode alle Ergebnisse zurückzugeben - was das obere Beispiel äquivalent zu dem folgenden Beispiel macht:<syntaxhighlight lang="groovy">
 +
c.list { /* */ }
 +
</syntaxhighlight>
 +
{| class="wikitable"
 +
!Methode
 +
!Beschreibung
 +
|-
 +
|'''list'''
 +
|Dies ist die Standardmethode. Es werden alle übereinstimmenden Zeilen zurückgegeben.
 +
|-
 +
|'''get'''
 +
|Gibt eine eindeutige Ergebnismenge zurück, d. H. Nur eine Zeile. Die Kriterien müssen so formuliert sein, dass nur eine Zeile abgefragt wird. Diese Methode ist nicht mit einer Beschränkung auf die erste Zeile zu verwechseln.
 +
|-
 +
|'''scroll'''
 +
|Gibt eine scrollbare Ergebnismenge zurück.
 +
|-
 +
|'''listDistinct'''
 +
|Wenn Unterabfragen oder Zuordnungen verwendet werden, kann es sein, dass in der Ergebnismenge mehrere Male dieselbe Zeile angezeigt wird. Dies ermöglicht das Auflisten nur bestimmter Entitäten und entspricht DISTINCT_ROOT_ENTITY der CriteriaSpecification-Klasse.
 +
|-
 +
|'''count'''
 +
|Gibt die Anzahl der übereinstimmenden Zeilen zurück.
 +
|}
 +
 
 +
====Kriterien kombinieren====
 +
Man kann mehrere (angehängte) Kriterien-Closures wie folgt definieren:<syntaxhighlight lang="groovy">
 +
def emeaCriteria = {
 +
    eq "region", "EMEA"
 +
}
 +
 
 +
def results = Airport.withCriteria {
 +
    emeaCriteria.delegate = delegate
 +
    emeaCriteria()
 +
    flights {
 +
        like "number", "BA%"
 +
    }
 +
}
 +
</syntaxhighlight>
 +
Diese Technik erfordert, dass sich jedes Kriterium auf dieselbe Domänenklasse bezieht. Ein flexiblerer Ansatz ist die Verwendung von getrennten Kriterien, wie im folgenden Abschnitt beschrieben wird:
 +
 
 +
===[http://gorm.grails.org/6.1.x/hibernate/manual/#detachedCriteria getrennte Kriterien]===
 +
Getrennte Kriterien sind Kriterienabfragen, die keiner bestimmten Datenbanksitzung / -verbindung zugeordnet sind. Abgelöste Kriterienabfragen werden seit Grails 2.0 unterstützt und haben viele Verwendungsmöglichkeiten, einschließlich der Möglichkeit, '''allgemeine Abfragen für wiederverwendbare Kriterien zu erstellen, Unterabfragen auszuführen und Stapelaktualisierungen / -löschungen auszuführen.'''
 +
<br />
 +
 
 +
====getrennte Kriterien definieren====
 +
Der primäre Einstiegspunkt für die Verwendung der getrennten Kriterien ist die [http://gorm.grails.org/6.1.x/hibernate/api/grails/gorm/DetachedCriteria.html DetachedCriteria]-Klasse, die eine Domänenklasse als einziges Argument für ihren Konstruktor akzeptiert:<syntaxhighlight lang="groovy">
 +
import grails.gorm.*
 +
...
 +
def criteria = new DetachedCriteria(Person)
 +
</syntaxhighlight>Sobald man einen Verweis auf eine getrennte Kriterieninstanz erhalten hat, kann man <code>where</code> oder Kriterien -Abfragen ausführen, um die entsprechende Abfrage aufzubauen. Um eine normale Kriterienabfrage zu erstellen, kann man die Methode <code>build</code> verwenden:<syntaxhighlight lang="groovy">
 +
def criteria = new DetachedCriteria(Person).build {
 +
    eq 'lastName', 'Simpson'
 +
}
 +
</syntaxhighlight>'''Die Methoden in der DetachedCriteria-Instanz mutieren das ursprüngliche Objekt nicht''', sondern geben stattdessen eine neue Abfrage zurück. Mit anderen Worten: Man muss den Rückgabewert der <code>build</code> -Methode verwenden, um das mutierte Kriterienobjekt zu erhalten:<syntaxhighlight lang="groovy">
 +
def criteria = new DetachedCriteria(Person).build {
 +
    eq 'lastName', 'Simpson'
 +
}
 +
def bartQuery = criteria.build {
 +
    eq 'firstName', 'Bart'
 +
}
 +
 
 +
 
 +
</syntaxhighlight><br />
 +
 
 +
====getrennte Kriterien ausführen====
 +
'''Im Gegensatz zu regulären Kriterien sind getrennte Kriterien insofern faul, als zum Zeitpunkt der Definition keine Abfrage ausgeführt wird.''' Sobald eine Abfrage mit getrennten Kriterien erstellt wurde, gibt es eine Reihe nützlicher Abfragemethoden, die in der folgenden Tabelle zusammengefasst sind:
 +
{| class="wikitable"
 +
!Method
 +
!Description
 +
|-
 +
|'''list'''
 +
|Listet alle übereinstimmenden Entitäten auf
 +
|-
 +
|'''get'''<nowiki> | </nowiki>'''find'''
 +
|Gibt ein einzelnes übereinstimmendes Ergebnis zurück
 +
|-
 +
|'''count'''
 +
|Zählt alle übereinstimmenden Datensätze
 +
|-
 +
|'''exists'''
 +
|Gibt true zurück, wenn übereinstimmende Datensätze vorhanden sind
 +
|-
 +
|'''deleteAll'''
 +
|Löscht alle übereinstimmenden Datensätze
 +
|-
 +
|'''updateAll(Map)'''
 +
|Aktualisiert alle übereinstimmenden Datensätze mit den angegebenen Eigenschaften
 +
|}
 +
Im folgenden Code werden beispielsweise die ersten 4 übereinstimmenden Datensätze aufgelistet, die nach der Eigenschaft <code>firstName</code> sortiert werden:<syntaxhighlight lang="groovy">
 +
def criteria = new DetachedCriteria(Person).build {
 +
    eq 'lastName', 'Simpson'
 +
}
 +
def results = criteria.list(max:4, sort:"firstName")
 +
</syntaxhighlight>Man kann der <code>list</code>-Methode auch zusätzliche Kriterien übergeben:<syntaxhighlight lang="groovy">
 +
def results = criteria.list(max:4, sort:"firstName") {
 +
    gt 'age', 30
 +
}
 +
</syntaxhighlight>
 +
 
 +
 
 +
Die <code>'''DetachedCriteria'''</code>-Klasse selbst '''implementiert''' auch '''die <code>Iterable</code>-Schnittstelle''', was bedeutet, dass sie wie eine Liste behandelt werden kann.
 +
 
 +
In diesem Fall wird die Abfrage nur ausgeführt, wenn die einzelnen Methoden aufgerufen werden. Gleiches gilt für alle anderen Iterationsmethoden der Groovy-Sammlung.<syntaxhighlight lang="groovy">
 +
def criteria = new DetachedCriteria(Person).build {
 +
    eq 'lastName', 'Simpson'
 +
}
 +
criteria.each {
 +
    println it.firstName
 +
}
 +
</syntaxhighlight>
 +
 
 +
Man kann auch dynamische Finder auf <code>DetachedCriteria</code>-Objekten ausführen, genau wie bei Domänenklassen:<syntaxhighlight lang="groovy">
 +
def criteria = new DetachedCriteria(Person).build {
 +
    eq 'lastName', 'Simpson'
 +
}
 +
def bart = criteria.findByFirstName("Bart")
 +
</syntaxhighlight>
 +
 
 +
====getrennte Kriterien für Unterabfragen benutzen====
 +
'''Im Rahmen einer regulären/angehängten Kriterienabfrage kann man ein DetachedCriteria verwenden, um eine Unterabfrage auszuführen.''' Wenn man beispielsweise alle Personen finden möchten, die älter als das Durchschnittsalter sind, kann man dies zum Beispiel so lösen:<syntaxhighlight lang="groovy">
 +
def results = Person.withCriteria {
 +
    gt "age", new DetachedCriteria(Person).build {
 +
        projections {
 +
            avg "age"
 +
        }
 +
    }
 +
    order "firstName"
 +
}
 +
</syntaxhighlight>
 +
In diesem Fall ist die Unterabfrageklasse dieselbe wie die ursprüngliche Kriterienabfrageklasse (d. H. Person), und daher kann die Abfrage verkürzt werden auf:<syntaxhighlight lang="groovy">
 +
def results = Person.withCriteria {
 +
    gt "age", {
 +
        projections {
 +
            avg "age"
 +
        }
 +
    }
 +
    order "firstName"
 +
}
 +
</syntaxhighlight>
 +
Wenn sich die Unterabfrageklasse von der ursprünglichen Kriterienabfrage unterscheidet, müsste man die ursprüngliche Syntax verwenden.
 +
 
 +
Im vorherigen Beispiel stellte die Projektion sicher, dass '''nur ein einziges Ergebnis''' zurückgegeben wurde ('''das Durchschnittsalter'''). Wenn seine Unterabfrage mehrere Ergebnisse zurückgibt, müssen verschiedene Kriterienmethoden verwendet werden, um das Ergebnis zu vergleichen. Um beispielsweise alle Personen zu finden, die älter als 18 bis 65 Jahre sind, kann eine <code>gtAll</code>-Abfrage verwendet werden:<syntaxhighlight lang="groovy">
 +
def results = Person.withCriteria {
 +
    gtAll "age", {
 +
        projections {
 +
            property "age"
 +
        }
 +
        between 'age', 18, 65
 +
    }
 +
 
 +
    order "firstName"
 +
}
 +
</syntaxhighlight>
 +
In der folgenden Tabelle sind die '''Kriterienmethoden für die Bearbeitung von Unterabfragen''' zusammengefasst, die mehrere Ergebnisse zurückgeben:
 +
{| class="wikitable"
 +
!Methode
 +
!Beschreibung
 +
|-
 +
|'''gtAll'''
 +
|größer als alle Unterabfrageergebnisse
 +
|-
 +
|'''geAll'''
 +
|größer oder gleich allen Unterabfrageergebnissen
 +
|-
 +
|'''ltAll'''
 +
|weniger als alle Unterabfrageergebnisse
 +
|-
 +
|'''leAll'''
 +
|kleiner oder gleich allen Unterabfrageergebnissen
 +
|-
 +
|'''eqAll'''
 +
|gleich allen Unterabfrageergebnissen
 +
|-
 +
|'''neAll'''
 +
|nicht gleich allen Unterabfrageergebnissen
 +
|}
 +
<br />
 +
 
 +
===Hibernate Query Language (HQL)===
 +
Siehe [http://gorm.grails.org/6.1.x/hibernate/manual/#hql GORM-Dokumentation] für einen Überblick über HQL sowie die [http://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#hql Hibernate-Dokumentation] für eine ausführliche Version
 +
 
 +
 
 +
TODO
 +
 
 +
<nowiki>#</nowiki>8 Advanced GORM Features | Überfliegen und schauen was wichtig sein könnt
 +
 
 +
<nowiki>#</nowiki>9 Programmatic Transactions | ?
 +
 
 +
<nowiki>#</nowiki>10 Data Services
 +
 
 +
<nowiki>#</nowiki>11 Mutliple DataSources | auslassen und "Siehe Dokumentation"
 +
 
 +
<nowiki>#</nowiki>12 Multi-Tenancy | ???
 +
 
 +
<nowiki>#</nowiki>13 Constraints
 +
 
 +
<nowiki>#</nowiki>14 Testing
 +
<br />
 +
=[https://docs.grails.org/3.3.9/guide/traits.html Traits]=
 +
Grails stellt eine Reihe von <code>[http://docs.groovy-lang.org/next/html/documentation/core-traits.html Traits]</code> zur Verfügung, die Zugang zu Eigenschaften und Verhalten bieten, auf die von verschiedenen Grails-Artefakten sowie von beliebigen Groovy-Klassen aus zugegriffen werden kann, die Teil eines Grails-Projekts sind.
 +
 
 +
Viele dieser Eigenschaften werden automatisch zu den Grails-Artefakt-Klassen hinzugefügt (wie z.B. Controller und Taglibs) und lassen sich leicht zu anderen Klassen hinzufügen.
 +
 
 +
==[https://docs.grails.org/3.3.9/guide/traits.html#traitsprovided Von Grails bereitgestellten Traits]==
 +
Grails-Artefakte werden zur Kompilierungszeit automatisch mit bestimmten Traits ergänzt.
 +
 
 +
===Domain Class Traits===
 +
 
 +
*[https://docs.grails.org/3.3.9/api/grails/artefact/DomainClass.html grails.artefact.DomainClass]
 +
*[https://docs.grails.org/3.3.9/api/grails/web/databinding/WebDataBinding.html grails.web.databinding.WebDataBinding]
 +
*[http://gorm.grails.org/latest/api//org/grails/datastore/gorm/GormEntity.html org.grails.datastore.gorm.GormEntity]
 +
*[http://gorm.grails.org/latest/api//org/grails/datastore/gorm/GormValidateable.html org.grails.datastore.gorm.GormValidateable]
 +
 
 +
===Controller Traits===
 +
 
 +
*[http://gsp.grails.org/latest/api/grails/artefact/gsp/TagLibraryInvoker.html grails.artefact.gsp.TagLibraryInvoker]
 +
*[http://async.grails.org/latest/api/grails/artefact/AsyncController.html grails.artefact.AsyncController]
 +
*[https://docs.grails.org/3.3.9/api/grails/artefact/controller/RestResponder.html grails.artefact.controller.RestResponder]
 +
*[https://docs.grails.org/3.3.9/api/grails/artefact/Controller.html grails.artefact.Controller]
 +
 
 +
===Interceptor Trait===
 +
 
 +
*[https://docs.grails.org/3.3.9/api/grails/artefact/Interceptor.html grails.artefact.Interceptor]
 +
 
 +
===Tag Library Trait===
 +
 
 +
*[http://gsp.grails.org/latest/api/grails/artefact/TagLibrary.html grails.artefact.TagLibrary]
 +
 
 +
<br />
 +
 
 +
===Weitere Traits===
 +
Nachstehend findet man eine Liste weiterer Traits, die das Framework bietet. Die Javadocs bieten mehr Details über Methoden und Eigenschaften, die mit jedem Trait verbunden sind.
 +
{| class="wikitable"
 +
!'''Trait'''
 +
!'''Brief Description'''
 +
|-
 +
|[https://docs.grails.org/3.3.9/api/grails/web/api/WebAttributes.html '''grails.web.api.WebAttributes''']
 +
|Common Web Attributes
 +
|-
 +
|[https://docs.grails.org/3.3.9/api/grails/web/api/ServletAttributes.html '''grails.web.api.ServletAttributes''']
 +
|Servlet API Attributes
 +
|-
 +
|[https://docs.grails.org/3.3.9/api/grails/web/databinding/DataBinder.html grails.web.databinding.DataBinder]
 +
|Data Binding API
 +
|-
 +
|[https://docs.grails.org/3.3.9/api/grails/artefact/controller/support/RequestForwarder.html grails.artefact.controller.support.RequestForwarder]
 +
|Request Forwarding API
 +
|-
 +
|[https://docs.grails.org/3.3.9/api/grails/artefact/controller/support/ResponseRedirector.html grails.artefact.controller.support.ResponseRedirector]
 +
|Response Redirecting API
 +
|-
 +
|[https://docs.grails.org/3.3.9/api/grails/artefact/controller/support/ResponseRenderer.html grails.artefact.controller.support.ResponseRenderer]
 +
|Response Rendering API
 +
|-
 +
|[https://docs.grails.org/3.3.9/api/grails/validation/Validateable.html grails.validation.Validateable]
 +
|Validation API
 +
|}
 +
 
 +
==Beispiel einer Trait-Injektion==
 +
[https://docs.grails.org/3.3.9/api/grails/web/api/WebAttributes.html WebAttributes] ist eine der Traits, die das Framework zur Verfügung stellt. Jede Groovy-Klasse kann dieses Trait implementieren, um alle Eigenschaften und Verhaltensweisen zu erben, die von der Eigenschaft bereitgestellt werden.<syntaxhighlight lang="groovy">
 +
// src/main/groovy/demo/Helper.groovy
 +
package demo
 +
 
 +
import grails.web.api.WebAttributes
 +
import groovy.transform.CompileStatic
 +
 
 +
@CompileStatic // The traits are compatible with static compilation!
 +
class Helper implements WebAttributes {
 +
 
 +
    List<String> getControllerNames() {
 +
        // There is no need to pass grailsApplication as an argument
 +
        // or otherwise inject the grailsApplication property.  The
 +
        // WebAttributes trait provides access to grailsApplication.
 +
        grailsApplication.getArtefacts('Controller')*.name
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
<br />
 +
=[https://docs.grails.org/3.3.9/guide/theWebLayer.html Die Web-Ebene]=
 +
==[https://docs.grails.org/3.3.9/guide/theWebLayer.html#controllers Controller]==
 +
Ein Controller verarbeitet Anforderungen und erstellt oder bereitet die Antwort vor. Ein Controller kann die Antwort direkt generieren oder an eine Ansicht (View, z.B.<code>.gsp</code>) delegieren.
 +
 
 +
===Controller erstellen===
 +
Alle Controller befinden sich im Stammbaum unter <code>grails-app/controllers/APP_NAME</code>. Die einzige Voraussetzung für eine Klasse innerhalb dieser Hierarchie ist es, mit <code>Controller.groovy</code> zu enden.
 +
 
 +
 
 +
Mit dem Befehl <code>$ grails create-controller (PAKET.)KLASSEN_NAME </code> erstellt man das Skelett eines Controllers, welches dann entsprechend unter <code>grails-app/controllers/APP_NAME/PAKET/KLASSEN_NAME.groovy</code> gespeichert wird.
 +
 
 +
*''Dieser Befehl ist nur zur vereinfachten Generierung gedacht, man kann es auch manuell oder mit einer IDE machen.''<br />
 +
 
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_creating_actions Aktionen]===
 +
Die Standard [http://docs.grails.org/latest/guide/single.html#urlmappings URL-Mapping]-Konfiguration versichert, dass der Name des Controllers sowie jede Methode zum entsprechendem URI-Pfad gebunden wird.<br>Das folgende Beispiel ist hiermit unter <code>localhost:8080/mahlzeit/index</code> und <code>localhost:8080/mahlzeit/list</code> erreichbar:
 +
<syntaxhighlight lang="groovy">
 +
package myapp
 +
 
 +
class MahlzeitController {
 +
    // Standard-Aktion
 +
    def index() { }
 +
   
 +
    // Aktion "/list"
 +
    def list {
 +
        //..
 +
        // Controller-Logik
 +
        //..
 +
        return model
 +
    }
 +
}
 +
</syntaxhighlight>
 +
Ein Controller kann mehrere öffentliche Methoden haben, welche sich (Wie oben beschrieben) jeweils zur einer URI binden.
 +
 
 +
====[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_the_default_action Standard-Aktion]====
 +
Wenn der Nutzer keine bestimmte Aktion in seiner Anfrage stehen hat ''(Also zB. nur den Namen des Controllers, wie "<code>/mahlzeit/</code>" anstatt zB. "<code>/mahlzeit/login</code>") ''versucht Grails eine Standard-Aktion ausfindig zu machen, welche sich dieser Anfrage zur Verfügung stellt.
 +
 
 +
*Wenn es nur eine Aktion ''(aka. Methode)'' gibt, wird diese als Standard-Aktion anerkannt
 +
*Wenn es eine Aktion namens "index" gibt, wird diese als Standard-Aktion anerkannt
 +
 
 +
Alternativ kann man auch eine eigenen Standard setzten, indem man <code>static defaultAction = "DEINE-METHODE"</code> in den Quellcode des Controllers einfügt.
 +
 
 +
<br />
 +
 
 +
===Namespaced-Controller===
 +
Wenn eine Anwendung mehrere Controller mit demselben Namen in verschiedenen Paketen definiert, müssen die Controller in einem Namensraum definiert werden. Die Art und Weise, einen Namensraum für einen Controller zu definieren, besteht darin, eine statische Eigenschaft namens <code>namespace</code> im Controller zu definieren und der Eigenschaft, die den Namensraum repräsentiert, einen String zuzuweisen.<syntaxhighlight lang="groovy">
 +
package com.app.reporting
 +
 
 +
class AdminController {
 +
    static namespace = 'reports'
 +
    // ...
 +
}
 +
</syntaxhighlight><syntaxhighlight lang="groovy">
 +
package com.app.security
 +
 
 +
class AdminController {
 +
    static namespace = 'users'
 +
    // ...
 +
}
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
Bei der Definition von URL-Mappings, die mit einem Namespace-Controller verknüpft werden sollen, muss die Namespace-Variable Teil des URL-Mappings sein.<syntaxhighlight lang="groovy">
 +
class UrlMappings {
 +
 
 +
    static mappings = {
 +
        '/userAdmin' {
 +
            controller = 'admin'
 +
            namespace = 'users'
 +
        }
 +
 
 +
        '/reportAdmin' {
 +
            controller = 'admin'
 +
            namespace = 'reports'
 +
        }
 +
 
 +
        "/$namespace/$controller/$action?"()
 +
    }
 +
}
 +
</syntaxhighlight><syntaxhighlight lang="jsp">
 +
<!-- Reverse URL mappings also require that the namespace be specified. -->
 +
<g:link controller="admin" namespace="reports">Click For Report Admin</g:link>
 +
<g:link controller="admin" namespace="users">Click For User Admin</g:link>
 +
</syntaxhighlight><br />
 +
==Scope-Variablen==
 +
"Scope-Variablen" sind HashMap ähnliche Strukturen, in welche man Variablen speichern kann. Für Controller stehen folgende Scope-Variablen zur Verfügung:
 +
 
 +
===[http://docs.grails.org/latest/ref/Controllers/servletContext.html servletContext]: Statisch, für alle gleich===
 +
Dieser Scope lässt uns Daten speichern, welche '''über die gesamte Web-App gleich statisch erreichbar sind (Applikationskontext).''' <br>Das [http://docs.grails.org/latest/ref/Controllers/servletContext.html servletContext]-Objekt ist eine Instanz vom Standardmäßigen Java(EE)-Objekt [https://docs.oracle.com/javaee/7/api/javax/servlet/ServletContext.html ServletContext]. ''[https://docs.grails.org/3.3.9/ref/Servlet&#x20;API/servletContext.html Grails injeziert zudem noch weitere Methoden.]''
 +
 
 +
Es ist nützlich um:
 +
 
 +
*'''Applikations-Eigenschaften''' zu speichern
 +
*'''lokale Server-Ressourcen zu laden''' und
 +
*Informationen vom Servlet-Container zu erhalten.
 +
 
 +
===[http://docs.grails.org/latest/ref/Controllers/session.html session]: Für jede Sitzung anders===
 +
Das [http://docs.grails.org/latest/ref/Controllers/session.html session]-Objekt ist eine Instanz der Klasse [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSession.html HttpSession] der Java(EE) Servlet-API.
 +
 
 +
Es ist nützlich um '''Attribute der derzeitigen Sitzung eines Klientens zu speichern, wie zB. der Login (Name/Passwort).'''
 +
 
 +
===[http://docs.grails.org/latest/ref/Servlet%20API/request.html request]: Mitgesendete Informationen===
 +
Das [http://docs.grails.org/latest/ref/Servlet%20API/request.html request]-Objekt ist eine Instanz von [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html HttpServletRequest] der Java(EE) Servlet API.
 +
 
 +
Es ist nützlich um:
 +
 
 +
*'''Mitgesendete''' [https://de.wikipedia.org/wiki/Liste_der_HTTP-Headerfelder '''Request-Header''']-'''Felderdaten''' zu bekommen
 +
*'''Anfragen-bezogene Attribute zwischen-zu-speichern''' und
 +
*Informationen des aktuellen Klienten zu erhalten
 +
 
 +
[http://docs.grails.org/latest/ref/Servlet%20API/request.html Grails injeziert zudem einige zusätzliche Methoden] in das <code>request</code>-Objekt, ''die das standardmäßige [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html HttpServletRequest]-Objekt nicht bietet. Darunter'':
 +
 
 +
*<code>XML</code> - Eine Instanz der [http://groovy.codehaus.org/api/groovy/util/slurpersupport/GPathResult.html GPathResult]-Klasse vom XMLSluper, welche es erlaubt, einkommende XML-Anfragen zu verarbeiten (parsen) - Nützlich für REST
 +
*<code>JSON</code> - Eine Instanz der [http://docs.grails.org/3.2.x/apiorg/codehaus/groovy/grails/web/json/JSONObject.html JSONObject]-Klasse, welche es erlaubt, einkommente JSON-Anfragen zu verarbeiten (parsen) - Nützlich für JSON und REST
 +
*<code>isRedirected()</code> - Gibt <code>true</code> zurück, wenn eine Weiterleitung für diesen Antrag ausgestellt wurde
 +
*<code>get</code> - Gibt <code>true</code> zurück wenn die Anfrage ein <code>GET</code>-Request ist
 +
*<code>post</code> - Gibt <code>true</code> zurück wenn die Anfrage ein <code>POST</code>-Request ist
 +
*<code>each</code> - Implementation von Groovys <code>each</code>-Methode zur Iteration über Anfrage-Attribute
 +
*<code>find</code> - Implementation von Groovys <code>find</code>-Methode um ein gewisses Anfrage-Attribut zu finden (Ohne komplett richtigen Hashkey-Namen)
 +
*<code>findAll</code> - Implementation von Groovys <code>findAll</code>-Methode um Anfrage-Attribut zu finden (Ohne komplett richtigen Hashkey-Namen)
 +
*''<code>format</code> - Das Anfrageformat, das für die [https://docs.grails.org/3.3.9/guide/theWebLayer.html#contentNegotiation Inhalts-Verhandlung (Content Negotiation)] verwendet wird.''
 +
*''<code>withFormat(Closure)</code> - Die <code>withFormat</code>-Methode, die für die [https://docs.grails.org/3.3.9/guide/theWebLayer.html#contentNegotiation Inhalts-Verhandlung (Content Negotiation)] verwendet wird.''
 +
*<code>xhr</code> - Gibt <code>true</code> zurück wenn die Anfrage ein AJAX-Request ist.
 +
 
 +
 
 +
Die XML- und JSON-Eigenschaften sind nützlich für XML-APIs und können zum Parsen eingehender XML- oder JSON-Pakete verwendet werden. Zum Beispiel kann der folgende Anfragekörper...
 +
<syntaxhighlight lang="xml">
 +
<book>
 +
  <title>The Stand</title>
 +
</book>
 +
</syntaxhighlight>
 +
ganz einfach mithilfe des <code>request</code>-Objekts serialisiert und im Code genutzt werden:
 +
<syntaxhighlight lang="groovy">
 +
def title = request.XML?.book.title // Mit dem "?" versichern wir uns, dass wenn die Variable "XML" "null" ist es zu keiner NullPointerException kommt und "title" einfach auch auf "null" gesetzt wird (Groovy feature :D)
 +
render "The Title is $title"
 +
</syntaxhighlight>
 +
 
 +
===([http://docs.grails.org/latest/ref/Controllers/params.html params]): (Veränderbare,) Mitgesendete CGI Informationen===
 +
Eine veränderbare, mehrdimensionale HashMap aller Anforderungsparametern (CGI).<br>Obwohl das [http://docs.grails.org/latest/ref/Servlet%20API/request.html request]-Objekt auch Methoden zum lesen der Anforderungsparametern verfügt, ist der <code>params</code>-Scope manchmal nützlich für die [http://docs.grails.org/latest/guide/theWebLayer.html#dataBinding Daten-Bindung an Objekte].
 +
 
 +
Beispiel:
 +
<syntaxhighlight lang="groovy">
 +
def save() {
 +
    def book = new Book(params) // bind request parameters onto properties of book
 +
}
 +
</syntaxhighlight>
 +
===[http://docs.grails.org/latest/ref/Controllers/flash.html flash]: Speicher zwischen 2 Anfragen===
 +
[[Datei:PostRedirectGet DoubleSubmitSolution.png|mini|Diagramm eines Anwendungsbeispiels der Post/Redirect/Get-Architektur. Quelle: [[commons:File:PostRedirectGet_DoubleSubmitSolution.png|Wikimedia]]|400x400px]]
 +
Eine temporäre Speicherzuordnung, in der Objekte innerhalb der Sitzung für die nächste Anforderung, und auch nur für die nächste Anforderung, gespeichert werden. Die darin enthaltenen Objekte werden nach Abschluss der nächsten Anforderung automatisch gelöscht.
 +
 
 +
Dies ist z.B. nützlich, um eine Nachricht direkt vor der Umleitung zu setzen: (Siehe [http://www.theserverside.com/tt/articles/article.tss?l=RedirectAfterPost Redirect after Post]-Konzept/Problemstellung)<syntaxhighlight lang="groovy">
 +
def delete() {
 +
    def b = Book.get(params.id)
 +
    if (!b) {
 +
        flash.message = "User not found for id ${params.id}"
 +
        redirect(action:list)
 +
    }
 +
    ... // remaining code
 +
}
 +
 
 +
def list(){
 +
    // use "flash.message" in the rendered gsp-template or something like that
 +
}
 +
</syntaxhighlight>Bemerke: Die Namen und Typen die man dem <code>flash</code>-Objekt setzt können willkürlich sein.
 +
<br />
 +
==[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_scoped_controllers Scope eines Controllers]==
 +
Neu erstellte Controller haben den Standard-Scope-Wert "singleton". Verfügbare Controller-Scopes sind:
 +
 
 +
*<code>prototype</code> (Standard) - Für jeden Request wird ein neuer Controller erstellt
 +
*<code>session</code> - Für jede Sitzung wird nur ein Controller-Objekt erstellt
 +
*<code>singleton</code> - Für die gesamte Zeit gibt es nur eine Instanz des Controllers (Wird geraten für Aktionen mit Methoden)
 +
 
 +
Dieser Standard-Wert kann man unter <code>grails-app/conf/application.yml</code> wie folgt abändern:
 +
<syntaxhighlight lang="yml">
 +
grails:
 +
    controllers:
 +
        defaultScope: singleton
 +
</syntaxhighlight>
 +
Man kann den Scope eines Individuellen Controllers auch manuell ändern, indem man <code>static scope = "DEIN-SCOPE"</code> in den Quellcode des Controllers einfügt.
 +
 
 +
 
 +
==[https://docs.grails.org/3.3.9/guide/theWebLayer.html#modelsAndViews Model und Ansicht (Model and View)]==
 +
[[Datei:MVC-Process.svg|mini|Diagramm der Interaktionen innerhalb des MVC-Musters.Quelle: [[commons:File:MVC-Process.svg|Wikimedia]]]]
 +
Das "Model" ist eine Map, die die Ansicht (View) beim Rendern verwendet.
 +
 
 +
*Die Schlüssel der Map korrespondieren mit dem Variablen-Namen über dessen sie innerhalb einer View (z.B. einer <code>.gsp</code>-Datei) erreichbar sind.
 +
*Zu beachten ist, dass bestimmte Variablennamen im Modell nicht verwendet werden können:
 +
**<code>attributes</code>
 +
**<code>application</code>
 +
 
 +
 
 +
Es gibt mehrere Wege um ein Model zu übergeben/zu erzeugen:
 +
 
 +
*Explizit: Wenn seine Methode gleich wie die View-Datei heißt, übergibt man das Model einfach wie beim returnen mit folgendem Syntax: <code>[Var1: Data1, Var2: Data2, ...]</code>
 +
<syntaxhighlight lang="groovy">
 +
def VIEW_NAME() {
 +
  [book: Book.get(params.id)] // Gleich wie render(view: "VIEW_NAME", model: [book: Book.get(params.id)])
 +
}
 +
</syntaxhighlight>
 +
 
 +
*Ein fortgeschrittenerer Ansatz ist die Rückgabe einer Instanz der Spring [https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/ModelAndView.html ModelAndView]-Klasse zu benutzen:
 +
<syntaxhighlight lang="groovy">
 +
import org.springframework.web.servlet.ModelAndView
 +
 
 +
def index() {
 +
    // get some books just for the index page, perhaps your favorites
 +
    def favoriteBooks = ...
 +
 
 +
    // forward to the list view to show them
 +
    return new ModelAndView("/book/list", [ bookList : favoriteBooks ])
 +
}
 +
</syntaxhighlight>
 +
<br />
 +
 
 +
===View-Datei selber selektieren mit der render()-Methode===
 +
In den vorherhigen 2 Beispielen haben wir nirgendwo angegeben an welche [http://docs.grails.org/latest/guide/single.html#gsp View] die Map-Daten übergeben werden soll.<br>Dies liegt an Grails Konventionen. Im folgenden Beispiel würde Grails unter <code>grails-app/views/book/show.gsp</code> nachsuchen: ''(Nach dem Schema <code>grails-app/views/CONTROLLER/AKTION.gsp</code>)''
 +
<syntaxhighlight lang="groovy">
 +
class BookController {
 +
    def show() {
 +
        [book: Book.get(params.id)]
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
Falls wir aber nicht wollen, das Grails sich den Namen der View-Datei mithilfe des Methoden-Namens sucht, können wir die vielfältige [http://docs.grails.org/latest/ref/Controllers/render.html render()]-Methode einsetzen:
 +
<syntaxhighlight lang="groovy">
 +
def show() {
 +
    def map = [book: Book.get(params.id)]
 +
    // Wenn man nur einen Dateinamen eingibt rechnet Grails damit, dass sich die Datei im Unterordner mit dem Namen des Controllers befindet.
 +
    render(view: "display", model: map)          // Datei "display.[gsp|jsp]" wird im "grails-app/views/books"-Ordner gesucht. (Methode befindet sich im "BookController")
 +
    // Selbsts den Unterordner bestimmen:
 +
    render(view: "/shared/display", model: map) // Datei "display.[gsp|jsp]" wird im "grails-app/views/shared"-Ordner gesucht
 +
}
 +
</syntaxhighlight>
 +
 
 +
''(Anmerkung: Wenn Grails keine <code>.gsp</code>-Datei finden kann, sucht es auch nach <code>.jsp</code>-Dateien.)''
 +
 
 +
<br />
 +
===Selektions-Verfahren von Controllern die einem speziellen Namensraum angehören===
 +
Wenn ein Controller einen eigenen [http://docs.grails.org/latest/guide/single.html#namespacedControllers Namensraum] gesetzt hat (In diesem Beispiel '<code>business</code>'), schaut Grails
 +
 
 +
*zuerst nach ob die View-Datei unter <code>grails-app/views/business/...</code> zu finden ist
 +
*und falls nicht sucht es unter dem normalen Namespace <code>grails-app/views/...</code> nach.
 +
 
 +
Beispiel:
 +
<syntaxhighlight lang="groovy">
 +
class ReportingController {
 +
    static namespace = 'business'
 +
 
 +
    def humanResources() {
 +
        // Diese Methode wird "grails-app/views/business/reporting/humanResources.gsp" rendern, falls die Datei existiert.
 +
        //              Falls "grails-app/views/business/reporting/humanResources.gsp" NICHT existiert, wird "grails-app/views/reporting/humanResources.gsp" versucht.
 +
        // Sprich: Die GSP im Namensraum hat Vorrang vor dem GSP ohne Namensraum.
 +
 
 +
        [numberOfEmployees: 9]
 +
    }
 +
 
 +
 
 +
    def accountsReceivable() {
 +
        // Diese Methode wird "grails-app/views/business/reporting/numberCrunch.gsp" rendern, falls die Datei existiert.
 +
        //              Falls "grails-app/views/business/reporting/numberCrunch.gsp" NICHT existiert, wird "grails-app/views/reporting/humanResources.gsp" versucht.
 +
        // Sprich: Die GSP im Namensraum hat Vorrang vor dem GSP ohne Namensraum.
 +
 
 +
        render view: 'numberCrunch', model: [numberOfEmployees: 13]
 +
    }
 +
}
 +
</syntaxhighlight><br />
 +
==[https://docs.grails.org/3.3.9/guide/theWebLayer.html#redirectsAndChaining Weiterleitungen]==
 +
Aktionen können mithilfe der Controller-Methode <code>[http://docs.grails.org/latest/ref/Controllers/redirect.html redirect()]</code> umgeleitet werden. Beispiel:<syntaxhighlight lang="groovy">
 +
class OverviewController {
 +
 
 +
    def login() {}
 +
 
 +
    def find() {
 +
        if (!session.user)
 +
            redirect(action: 'login')
 +
            return
 +
        }
 +
        ...
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
 
 +
Auf welche Seite <code>[http://docs.grails.org/latest/ref/Controllers/redirect.html redirect()]</code> umleitet, kann man Grails auf mehrere Weisen mitteilen:
 +
 
 +
*Der Name der Aktion (Sowie den namens des Controllers, falls sich die Aktion in einem anderen Controller befindet)
 +
<syntaxhighlight lang="groovy">
 +
// Weiterleitung zur "index()"-Aktion im "home"-Controller
 +
redirect(controller: 'home', action: 'index')
 +
</syntaxhighlight>
 +
 
 +
*URI zu einer Resource relativ vom Kontext-Pfad:
 +
<syntaxhighlight lang="groovy">
 +
// Weiterleitung zu einer explizit definierten URI
 +
redirect(uri: "/login.html")
 +
</syntaxhighlight>
 +
 
 +
*Eine Komplette URL:
 +
<syntaxhighlight lang="groovy">
 +
// Weiterleitung zu einer explizit definierten und vollständige URL
 +
redirect(url: "http://grails.org")
 +
</syntaxhighlight>
 +
 
 +
*Domain-Klassen-Instanz: (Grails konstruiert einen Link unter Verwendung der Domänenklassen-ID, falls vorhanden.)
 +
<syntaxhighlight lang="groovy">
 +
// Weiterleitung zur Seite für die Domainklassen-Instanz
 +
Book book = ... // ...Domainklassen-Instanz generieren/kriegen...
 +
redirect book
 +
</syntaxhighlight>
 +
 
 +
Parameter können von einer zu der anderen Aktion mithilfe des <code>params</code>-Arguments übergeben werden.
 +
 
 +
Diese Daten befinden sich dann auch im <code>params</code>-Scope-Objekt der durch den Redirects resultierenden Anfrage der Aktion <code>myaction</code>.
 +
<syntaxhighlight lang="groovy">
 +
redirect(action: 'myaction', params: [myparam: "myvalue"])
 +
</syntaxhighlight>
 +
<syntaxhighlight lang="groovy">
 +
redirect(action: 'myaction', params: params) // Da sowohl der "params"-Parameter als auch der "params"-Controller-Scope beides Map's sind, ist sowas auch möglich
 +
</syntaxhighlight>
 +
 
 +
Mithilfe von <code>fragment</code> kann der Hash-Abteil der URL angegeben werden: (Im Standard URL-Mapping würde die folgende Methode in eine Weiterleitung auf <code>/myapp/test/show#profile</code> resultieren)<syntaxhighlight lang="groovy">
 +
redirect(controller: "test", action: "show", fragment: "profile")
 +
</syntaxhighlight><br />
 +
===Aktionen aneinander-reihen===
 +
<code>[http://docs.grails.org/3.1.1/ref/Controllers/chain.html chain()]</code> fungiert ähnlich wie <code>redirect()</code>, indem es auf eine andere Aktion springt und diese ausführt. <br>Im Gegensatz zur Redirection erlaubt Chaining es uns das Model beizubehalten und Model-Daten '''zusätzlich''' zu übergeben.
 +
 
 +
Beispiel:
 +
<syntaxhighlight lang="groovy">
 +
class ExampleChainController {
 +
 
 +
    def first() {
 +
        chain(action: second, model: [one: 1])
 +
    }
 +
 
 +
    def second () {
 +
        chain(action: third, model: [two: 2])
 +
    }
 +
 
 +
    def third() {
 +
        [three: 3]
 +
    }
 +
}
 +
</syntaxhighlight>
 +
Resultiert beim Aufrufen von "/examplechain/first" in das Model
 +
<syntaxhighlight lang="groovy">
 +
[one: 1, two: 2, three: 3]
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
Auf das Modell kann in nachfolgenden Controller-Aktionen in der Kette über die <code>chainModel</code>-Map zugegriffen werden. Diese dynamische Eigenschaft ist nur in Aktionen vorhanden, die auf den Aufruf der Kettenmethode folgen:<syntaxhighlight lang="groovy">
 +
class ChainController {
 +
 
 +
    def nextInChain() {
 +
        def model = chainModel.myModel
 +
        ...
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
 
 +
Wie bei der Weiterleitung kann man auch hier weitere Daten in das <code>[http://docs.grails.org/latest/ref/Controllers/params.html params]</code>-Scope einspeisen:
 +
<syntaxhighlight lang="groovy">
 +
chain(action: "action1", model: [one: 1], params: [myparam: "param1"])
 +
</syntaxhighlight>
 +
 
 +
 
 +
==Die [https://docs.grails.org/latest/ref/Controllers/render.html render()]-Methode==
 +
Die render()-Methode ist sehr flexibel und kann viele Argumente entgegennehmen (Vom anzeigen eines einfachen Textes bis hin zur render eines Views/Templates.).<br>
 +
<syntaxhighlight lang="groovy">
 +
// Simplen Text rendern
 +
render "some text"
 +
 
 +
// Rendert Text für einen bestimmten Inhaltstyp/eine bestimmte Codierung
 +
render(text: "<xml>some xml</xml>", contentType: "text/xml", encoding: "UTF-8")
 +
 
 +
// Rendern einer Vorlage für die Antwort für das angegebene Modell
 +
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
 +
render(template: "book", model: [book: theShining])
 +
 
 +
// Rendert jedes Elements in der Sammlung unter Verwendung der angegebenen Vorlage
 +
render(template: "book", collection: [b1, b2, b3])
 +
 
 +
// eine Vorlage für die Antwort für die angegebene Bean rendern
 +
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
 +
render(template: "book", bean: theShining)
 +
 
 +
//! Rendern der Ansicht mit dem angegebenen Modell
 +
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
 +
render(view: "viewName", model: [book: theShining])
 +
 
 +
// Rendern der Ansicht mit dem Controller als Modell
 +
render(view: "viewName")
 +
 
 +
// Eigenen Markup zur Response hinzurendern
 +
render {
 +
    div(id: "myDiv", "some text inside the div")
 +
}
 +
 
 +
// etwas XML-Markup für die Antwort rendern
 +
render(contentType: "text/xml") {
 +
    books {
 +
        for (b in books) {
 +
            book(title: b.title, author: b.author)
 +
        }
 +
    }
 +
}
 +
 
 +
//! Rendern einer JSON ( http://www.json.org ) Antwort mit dem Builder-Attribut:
 +
render(contentType: "application/json") {
 +
    book(title: b.title, author: b.author)
 +
}
 +
 
 +
//! Rendern mit Statuscode
 +
render(status: 503, text: 'Failed to update book ${b.id}')
 +
 
 +
//! Rendern einer Datei
 +
render(file: new File(absolutePath), fileName: "book.pdf")
 +
</syntaxhighlight>
 +
 
 +
==[https://docs.grails.org/3.3.9/guide/theWebLayer.html#jsonResponses Mit JSON antworten]==
 +
Die <code>respond</code>-Methode ist der bevorzugte Weg zur Rückgabe von JSON und integriert sich mit [https://docs.grails.org/3.3.9/guide/theWebLayer.html#contentNegotiation Inhaltsverhandlung] und [http://views.grails.org/ JSON Views].
 +
 
 +
Die <code>respond</code>-Methode bietet inhaltliche Verhandlungsstrategien, um auf intelligente Weise eine angemessene Antwort für den jeweiligen Client zu produzieren.
 +
 
 +
Beispiel:<syntaxhighlight lang="groovy">
 +
package example
 +
 
 +
class BookController {
 +
    def index() {
 +
        respond Book.list()
 +
    }
 +
}
 +
</syntaxhighlight>
 +
Die <code>respond</code>-Methode sieht folgende Schritte vor:
 +
 
 +
*Falls der <code>Accept</code>-Header vorhanden ist (z.B. <code>application/json</code>), wird dieser verwendet
 +
 
 +
*Wenn die Dateierweiterung der URI (z.B. /books.json) ein Format enthält, das in der Eigenschaft <code>grails.mime.types</code> (<code>grails-app/conf/application.yml</code>) definiert ist, verwendet es den in der Konfiguration definierten Medientyp
 +
 
 +
Die <code>respond</code>-Methode sucht dann in der [https://docs.grails.org/3.3.9/api/grails/rest/render/RendererRegistry.html <code>RendererRegistry</code>] nach einem geeigneten <code>[https://docs.grails.org/3.3.9/api/grails/rest/render/Renderer.html Renderer]</code> für das Objekt und den berechneten Medientyp.
 +
 
 +
 
 +
Grails enthält eine Reihe von vorkonfigurierten Renderer-Implementierungen, die Standarddarstellungen von JSON-Antworten für das übergebene Argument erzeugen.
 +
 
 +
*Wenn Sie beispielsweise die URI /book.json aufrufen, werden JSON-Antworten wie folgt erzeugt:
 +
<syntaxhighlight lang="json">
 +
[
 +
    {id:1,"title":"The Stand"},
 +
    {id:2,"title":"Shining"}
 +
]
 +
</syntaxhighlight><br />
 +
 
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_controlling_the_priority_of_media_types Die Priorität von Medientypen bestimmen]===
 +
Wenn man einen Controller definiert, gibt es standardmäßig keine Priorität in Bezug darauf, welches Format an den Client zurückgeschickt wird, und Grails geht davon aus, dass man HTML als Antworttyp verwenden möchten.
 +
 
 +
Wenn seine Anwendung jedoch in erster Linie eine API ist, dann kann man die Priorität mit der Eigenschaft <code>respondFormats</code> festlegen:<syntaxhighlight lang="groovy">
 +
package example
 +
 
 +
class BookController {
 +
    static responseFormats = ['json', 'html']
 +
    def index() {
 +
        respond Book.list()
 +
    }
 +
}
 +
</syntaxhighlight>Im obigen Beispiel antwortet Grails standardmäßig mit json, wenn der Medientyp, mit dem geantwortet werden soll, nicht aus dem Accept-Header oder der Dateierweiterung berechnet werden kann.
 +
<br />
 +
 
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_using_views_to_output_json_responses Views zum ausgeben von JSON benutzen]===
 +
Wenn man eine Ansicht definiert (entweder GSP- oder eine [http://views.grails.org/ JSON]) rendert Grails bei Verwendung der <code>respond</code>-Methode eine Ansicht, indem er aus dem zur Antwort übergebenen Argument ein Modell berechnet.
 +
 
 +
Wenn man z.B. in der vorherigen Auflistung die Ansichten <code>grails-app/views/index.gson</code> und <code>grails-app/views/index.gsp</code> definiert, werden diese verwendet, wenn der Client die Medientypen <code>application/json</code> bzw. <code>text/html</code> anfordert. Auf diese Weise kann ein einziges Backend definiert werden, das in der Lage ist, Antworten an einen Webbrowser zu senden oder die API Ihrer Anwendung zu repräsentieren.
 +
 
 +
 
 +
Die folgende Tabelle fasst diese Konventionen zusammen:
 +
{| class="wikitable"
 +
!Beispiel
 +
!Argument-Typ
 +
!Name der berechneten Model-Variable
 +
|-
 +
|<code>respond Book.list()</code>
 +
|<code>java.util.List</code>
 +
|<code>bookList</code>
 +
|-
 +
|<code>respond( [] )</code>
 +
|<code>java.util.List</code>
 +
|<code>emptyList</code>
 +
|-
 +
|<code>respond Book.get(1)</code>
 +
|<code>example.Book</code>
 +
|<code>book</code>
 +
|-
 +
|<code>respond( [1,2] )</code>
 +
|<code>java.util.List</code>
 +
|<code>integerList</code>
 +
|-
 +
|<code>respond( [1,2] as Set )</code>
 +
|<code>java.util.Set</code>
 +
|<code>integerSet</code>
 +
|-
 +
|<code>respond( [1,2] as Integer[] )</code>
 +
|<code>Integer[]</code>
 +
|<code>integerArray</code>
 +
|}
 +
Mit dieser Konvention kann man auf das übergebene Argument verweisen, um aus der View zu antworten:
 +
 
 +
*Wenn <code>Book.list()</code> eine leere Liste zurückgibt, würde der Name der Modellvariable in <code>emptyList</code> übersetzt werden. Dies ist beabsichtigt, und man sollte in der Ansicht einen Standardwert angeben, wenn keine Modellvariable angegeben ist, wie z.B. im folgendem Beispiel:
 +
<syntaxhighlight lang="groovy">
 +
//grails-app/views/book/index.gson:
 +
 
 +
// Wenn die in der respond-Methode übergebe Liste leer wäre, und wir diese Zeile nicht hätten, würde die Variable nicht existieren. (Sondern nur eine Variable namens 'emptyList')
 +
// Falls die Variable nicht existiert erstellen wir Sie hiermit und setzen Ihren Wert direkt (Leere Liste, '[]').
 +
@Field List<Book> bookList = []
 +
 
 +
json bookList, { Book book ->
 +
    title book.title
 +
}
 +
</syntaxhighlight>
 +
 
 +
====Modell-Variablen-Namen selbst bestimmen====
 +
Es gibt Fälle, in denen man vielleicht expliziter sein und den Namen der Modellvariablen selbst kontrollieren möchten.
 +
 
 +
Wenn man z.B. eine Domänen-Vererbungs-Hierarchie hat, in der ein Aufruf von <code>list()</code> möglicherweise verschiedene Unterklassen zurückgibt, die auf automatischer Berechnung beruhen, ist dies möglicherweise nicht zuverlässig.
 +
 
 +
*In diesem Fall sollte man das Modell direkt mit Hilfe von Antwort und einem Map-Argument übergeben:
 +
<syntaxhighlight lang="groovy">
 +
respond bookList: Book.list()
 +
</syntaxhighlight>
 +
 
 +
====Modell selbst erweitern====
 +
Wenn man das berechnete Modell einfach nur erweitern möchten, können Sie dies tun, indem man zudem ein <code>model</code>-Argument mit-übergibt. Das folgende Beispiel würde gerendert so aussehen: <code>[bookList:books, bookCount:totalBooks]</code><syntaxhighlight lang="groovy">
 +
respond Book.list(), [model: [bookCount: Book.count()]]
 +
</syntaxhighlight><br />
 +
 
 +
===Die render-Methode zur Ausgabe von JSON verwenden===
 +
Die Render-Methode kann auch zur Ausgabe von JSON verwendet werden, '''sollte aber nur für einfache Fälle verwendet werden''', die die Erstellung einer JSON-Ansicht nicht rechtfertigen: (Die Logik sollte generell so-gut-wie möglich aus den Controller-Klassen extrahiert werden, und z.B. der View-Layer mithilfe von JSON-Views überlassen werden)<syntaxhighlight lang="groovy">
 +
// Aktion
 +
def list() {
 +
 
 +
    def results = Book.list()
 +
 
 +
    render(contentType: "application/json") {
 +
        books(results) { Book b ->
 +
            title b.title
 +
        }
 +
    }
 +
}
 +
</syntaxhighlight>Beispiel-Render:<syntaxhighlight lang="json">
 +
[
 +
    {"title":"The Stand"},
 +
    {"title":"Shining"}
 +
]
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
<br />
 +
==Data Binding==
 +
[[wikipedia:Data_binding|Datenbindung]] ist der Akt des "Bindens" eingehender Anforderungsparameter an die Eigenschaften eines Objekts oder eines ganzen Graphen von Objekten.
 +
 
 +
*Bei der Datenbindung sollten alle notwendigen Typkonvertierungen vorgenommen werden, da Anforderungsparameter, die typischerweise durch eine Formularübertragung geliefert werden, immer Zeichenketten (Strings) sind, während die Eigenschaften eines Groovy- oder Java-Objektes dies möglicherweise nicht sind.
 +
 
 +
 
 +
 
 +
Der Datenbinder ist in der Lage, Werte in einer Map zu konvertieren und Eigenschaften eines Objekts zuzuordnen. Der Datenbinder ordnet Einträge in der Map den Eigenschaften des Objekts zu, indem er die Schlüssel in der Map verwendet, deren Werte den Eigenschaftsnamen des Objekts entsprechen. Der folgende Code veranschaulicht die Grundlagen:<syntaxhighlight lang="groovy">
 +
// Klasse
 +
class Person {
 +
    String firstName
 +
    String lastName
 +
    Integer age
 +
}
 +
 
 +
// Bindung
 +
def bindingMap = [firstName: 'Peter', lastName: 'Gabriel', age: 63]
 +
 
 +
def person = new Person(bindingMap)
 +
 
 +
assert person.firstName == 'Peter'
 +
assert person.lastName == 'Gabriel'
 +
assert person.age == 63
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
Um die Eigenschaften eines Domänenobjekts zu aktualisieren, kann man der Domänenklassen-Eigenschaft <code>properties</code>eine Map zuweisen:<syntaxhighlight lang="groovy">
 +
def bindingMap = [firstName: 'Peter', lastName: 'Gabriel', age: 63]
 +
 
 +
def person = Person.get(someId)
 +
person.properties = bindingMap
 +
 
 +
assert person.firstName == 'Peter'
 +
assert person.lastName == 'Gabriel'
 +
assert person.age == 63
 +
</syntaxhighlight><br />
 +
===Mit Unterklassen===
 +
Der Binder kann mit Hilfe von verschachtelten Maps ein vollständiges Diagramm von Objekten füllen.
 +
<syntaxhighlight lang="groovy">
 +
// Klassen
 +
class Person {
 +
    String firstName
 +
    String lastName
 +
    Integer age
 +
    Address homeAddress
 +
}
 +
 
 +
class Address {
 +
    String county
 +
    String country
 +
}
 +
 
 +
// Bindung
 +
def bindingMap = [firstName: 'Peter', lastName: 'Gabriel', age: 63, homeAddress: [county: 'Surrey', country: 'England'] ]
 +
 
 +
def person = new Person(bindingMap)
 +
 
 +
assert person.firstName == 'Peter'
 +
assert person.lastName == 'Gabriel'
 +
assert person.age == 63
 +
assert person.homeAddress.county == 'Surrey'
 +
assert person.homeAddress.country == 'England'
 +
</syntaxhighlight>
 +
 
 +
===Mit Arrays===
 +
Auch Arrays sind möglich:
 +
 
 +
Der Binder kann auch <code>Collection</code>s und <code>Map</code>s füllen und aktualisieren. Der folgende Code zeigt ein einfaches Beispiel für das Auffüllen einer Liste von Objekten in einer Domänenklasse: (Dieser Code würde auf dieselbe Weise funktionieren, wenn <code>albums</code> ein <code>Array</code> statt einer <code>List</code> wäre.)
 +
<syntaxhighlight lang="groovy">
 +
// Klassen
 +
class Band {
 +
    String name
 +
    static hasMany = [albums: Album] // One-To-Many Relation von Band-zu-Album (Für Datenbank-Relation wichtige Angabe!)
 +
    List albums
 +
}
 +
 
 +
class Album {
 +
    String title
 +
    Integer numberOfTracks
 +
}
 +
 
 +
// Bindung
 +
def bindingMap = [name: 'Genesis',
 +
                  'albums[0]': [title: 'Foxtrot', numberOfTracks: 6],
 +
                  'albums[1]': [title: 'Nursery Cryme', numberOfTracks: 7]]
 +
 
 +
def band = new Band(bindingMap)
 +
 
 +
assert band.name == 'Genesis'
 +
assert band.albums.size() == 2
 +
assert band.albums[0].title == 'Foxtrot'
 +
assert band.albums[0].numberOfTracks == 6
 +
assert band.albums[1].title == 'Nursery Cryme'
 +
assert band.albums[1].numberOfTracks == 7
 +
</syntaxhighlight>Beachte:<blockquote>Die Struktur einer <code>Map</code> die an ein <code>Set</code> gebunden wird ist dieselbe wie die einer <code>Map</code> die an eine <code>Liste</code> gebunden wird. Aber da ein <code>Set</code> ungeordnet ist, müssen die Indexe der Elemente in der <code>Map</code> nicht unbedingt  mit denen des <code>Set</code>s übereinstimmen.
 +
 
 +
Wenn im obigen Codebeispiel <code>albums</code> ein <code>Set</code> statt einer <code>List</code> wäre, könnte die BindungsMap genau gleich aussehen - aber es könnte auch geschehen dass "Foxtrot" an zweiter Stelle im <code>Set</code> erscheint.
 +
 
 +
Wenn man existierende Elemente in einem Set mithilfe iner Map-Datenbindung realisieren möchte, müssen die Indexe wie im folgenden Beispiel an den derzeitigen Stand des Set's angepasst werden:</blockquote><syntaxhighlight lang="groovy">
 +
/*
 +
* The value of the indexes 0 and 1 in albums[0] and albums[1] are arbitrary
 +
* values that can be anything as long as they are unique within the Map.
 +
* They do not correspond to the order of elements in albums because albums
 +
* is a Set.
 +
*/
 +
def bindingMap = ['albums[0]': [id: 9, title: 'The Lamb Lies Down On Broadway']
 +
                  'albums[1]': [id: 4, title: 'Selling England By The Pound']]
 +
 
 +
def band = Band.get(someBandId)
 +
 
 +
/*
 +
* This will find the Album in albums that has an id of 9 and will set its title
 +
* to 'The Lamb Lies Down On Broadway' and will find the Album in albums that has
 +
* an id of 4 and set its title to 'Selling England By The Pound'.  In both
 +
* cases if the Album cannot be found in albums then the album will be retrieved
 +
* from the database by id, the Album will be added to albums and will be updated
 +
* with the values described above.  If a Album with the specified id cannot be
 +
* found in the database, then a binding error will be created and associated
 +
* with the band object.  More on binding errors later.
 +
*/
 +
band.properties = bindingMap
 +
</syntaxhighlight><br />
 +
===Mit Maps===
 +
<syntaxhighlight lang="groovy">
 +
// Klassen
 +
class Album {
 +
    String title
 +
    static hasMany = [players: Player] // One-To-Many Relation von Album-zu-Player (Für Datenbank-Relation wichtige Angabe!)
 +
    Map players
 +
}
 +
 
 +
class Player {
 +
    String name
 +
}
 +
 
 +
// Bindung
 +
def bindingMap = [title: 'The Lamb Lies Down On Broadway',
 +
                  'players[guitar]': [name: 'Steve Hackett'],
 +
                  'players[vocals]': [name: 'Peter Gabriel'],
 +
                  'players[keyboards]': [name: 'Tony Banks']]
 +
 
 +
def album = new Album(bindingMap)
 +
 
 +
assert album.title == 'The Lamb Lies Down On Broadway'
 +
assert album.players.size() == 3
 +
assert album.players.guitar.name == 'Steve Hackett'
 +
assert album.players.vocals.name == 'Peter Gabriel'
 +
assert album.players.keyboards.name == 'Tony Banks'
 +
</syntaxhighlight>
 +
 
 +
 
 +
'''Für weitere Informationen konsultiert man am besten die [https://docs.grails.org/3.3.9/guide/theWebLayer.html#dataBinding Offizielle Dokumentationsseite zu Data Binding in Grails]. (Sehr komplexes Thema)'''
 +
 
 +
 
 +
==[https://docs.grails.org/3.3.9/guide/theWebLayer.html#uploadingFiles Datei-Uploads]==
 +
Grails unterstützt Datei-Uploads über die [https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/multipart/MultipartHttpServletRequest.html MultipartHttpServletRequest]-Schnittstelle von Spring. Der erste Schritt für das Hochladen von Dateien ist die Erstellung eines multipart-Formulars wie dieses:
 +
<syntaxhighlight lang="jsp">
 +
Upload Form: <br />
 +
<g:uploadForm action="upload">
 +
  <input type="file" name="myFile" />
 +
  <input type="submit" />
 +
</g:uploadForm>
 +
</syntaxhighlight>Das <code>[http://docs.grails.org/3.1.1/ref/Tags/uploadForm.html uploadForm]</code>-Tag fügt bequem das <code>enctype="multipart/form-data"</code>-Attribut zum Standard <code><g:form></code>-Tag hinzu.
 +
 
 +
Es gibt dann eine Reihe von Möglichkeiten, den Datei-Upload zu handhaben. Eine besteht darin, direkt mit der Spring MultipartFile-Instanz zu arbeiten:
 +
 
 +
 
 +
Es gibt dann eine Reihe von Möglichkeiten, den Datei-Upload zu handhaben. Eine besteht darin, direkt mit der Spring [https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/multipart/MultipartFile.html <code>MultipartFile</code>]-Instanz zu arbeiten:
 +
 
 +
*Dies ist praktisch, um Übertragungen an andere Ziele durchzuführen und die Datei direkt zu manipulieren, da man mit der <code>MultipartFile</code>-Schnittstelle einen InputStream usw. erhalten kann.
 +
<syntaxhighlight lang="groovy">
 +
def upload() {
 +
    def f = request.getFile('myFile')
 +
    if (f.empty) {
 +
        flash.message = 'file cannot be empty'
 +
        render(view: 'uploadForm')
 +
        return
 +
    }
 +
 
 +
    f.transferTo(new File('/some/local/dir/myfile.txt'))
 +
    response.sendError(200, 'Done')
 +
}
 +
</syntaxhighlight><br />
 +
 
 +
===Datei-Upload mithilfe von Data-Binding===
 +
Datei-Uploads können auch mit Datenbindung durchgeführt werden. Betrachte z.B. diese Domänenklasse:<syntaxhighlight lang="groovy">
 +
// Domänenklasse
 +
class Image {
 +
    byte[] myFile
 +
 
 +
    static constraints = {
 +
        // Limit upload file size to 2MB
 +
        // Wichtig: Würde diese Eigenschaft nicht gesetzt werden, könnte die Datenbank-Spalte zu klein sein. (MySQL rechnet mit einer Standard Blob-Größe von 255-bytes für byte-Arrays)
 +
        myFile maxSize: 1024 * 1024 * 2
 +
    }
 +
}
 +
 
 +
// Datenbindung
 +
def img = new Image(params)
 +
</syntaxhighlight>
 +
 
 +
 
 +
===Upload-Größen-Limit von Grails===
 +
Grails Standard-Wert für die maximale Dateigröße liegt bei 128KB. Wenn dieses Limit überschritten wird taucht folgender fehler auf:
 +
<syntaxhighlight>
 +
org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException
 +
</syntaxhighlight>
 +
 
 +
Dieses Limit kann man in seiner <code>application.yml</code> folgendermaßen anpassen:
 +
<syntaxhighlight lang="yaml">
 +
grails:
 +
    controllers:
 +
        upload:
 +
            maxFileSize: 2000000 # Die maximal zulässige Größe für hochgeladene Dateien.
 +
            maxRequestSize: 2100000 # Die maximal zulässige Größe für Mehrteil-/Formulardatenanfragen.
 +
</syntaxhighlight>Diese Begrenzungen existieren um Denial-of-Service Attacken entgegen-zu-wirken und die Perfomanz der Applikation zu steigern. Siehe auch [https://www.owasp.org/index.php/Unrestricted_File_Upload OWASP recommendations - Unrestricted File Upload]
 +
<br />
 +
==URL-Mappings==
 +
In der [http://docs.grails.org/latest/guide/single.html Dokumentation] wird immer von der standardmäßigen URL-Mapping-Konfiguration ausgegangen, die wie folgt aussieht:<code>/controller/action/id</code>.<br>Diese Konvention ist jedoch nicht fest mit Grails verdrahtet und wird in der Tat durch eine URL-Mappings-Klasse gesteuert, die sich unter <code>grails-app/controllers/mypackage/UrlMappings.groovy</code> befindet. Diese Klasse hat eine einzelne Eigenschaft Namens <code>mappings</code> in der alle Regeln definiert werden.
 +
 
 +
 
 +
Beispiel-Mapping (URI <code>/product</code> auf die Aktion <code>list()</code> vom <code>ProductController</code> leiten):
 +
<syntaxhighlight lang="groovy">
 +
"/product"(controller: "product", action: "list")
 +
</syntaxhighlight>
 +
Alternative-Syntax (Closure/Block-Code):
 +
<syntaxhighlight lang="groovy">
 +
"/product" {
 +
    controller = "product"
 +
    action = "list"
 +
}
 +
</syntaxhighlight>
 +
 
 +
Beispiel-Mapping (URI <code>/product</code> auf die Standard-Aktion vom <code>ProductController</code> leiten):
 +
<syntaxhighlight lang="groovy">
 +
"/product"(controller: "product")
 +
</syntaxhighlight><br />
 +
===Nested URL-Mapping===
 +
Wenn man Zuordnungen hat, die alle unter einen bestimmten Pfad fallen, kann man diese Zuordnungen mit der <code>group</code>-Methode gruppieren:<syntaxhighlight lang="groovy">
 +
group "/product", {
 +
    "/apple"(controller:"product", id:"apple")
 +
    "/htc"(controller:"product", id:"htc")
 +
}
 +
</syntaxhighlight>
 +
<syntaxhighlight lang="groovy">
 +
// Nested Grouping
 +
group "/store", {
 +
    group "/product", {
 +
        "/$id"(controller:"product")
 +
    }
 +
}
 +
</syntaxhighlight><br />
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#redirectMappings Weiterleitung mithilfe von URL-Mappings]===
 +
Seit Grails 2.3 ist es möglich, URL-Mappings zu definieren, die eine Weiterleitung angeben. Wenn eine URL-Zuordnung eine Weiterleitung spezifiziert, wird jedes Mal, wenn diese Zuordnung mit einer eingehenden Anfrage übereinstimmt, eine Weiterleitung mit den von der Zuordnung gelieferten Informationen initiiert.
 +
 
 +
Wenn eine URL-Zuordnung eine Weiterleitung angibt, muss die Zuordnung entweder einen String liefern, der eine URI darstellt, zu der umgeleitet werden soll, oder eine Zuordnung, die das Ziel der Umleitung darstellt. Diese Map ist genau '''wie die Map strukturiert, die als Argument an die <code>redirect</code>-Methode in einem Controller übergeben werden kann.'''<syntaxhighlight lang="groovy">
 +
"/viewBooks"(redirect: '/books/list')
 +
"/viewAuthors"(redirect: [controller: 'author', action: 'list'])
 +
"/viewPublishers"(redirect: [controller: 'publisher', action: 'list', permanent: true])
 +
</syntaxhighlight>
 +
 
 +
Anforderungsparameter, die Teil der ursprünglichen Anforderung waren, werden standardmäßig nicht in die Weiterleitung einbezogen. Um sie aufzunehmen, ist es notwendig, den Parameter <code>keepParamsWhenRedirect: true</code> hinzuzufügen.<syntaxhighlight lang="groovy">
 +
"/viewBooks"(redirect: [uri: '/books/list', keepParamsWhenRedirect: true])
 +
"/viewAuthors"(redirect: [controller: 'author', action: 'list', keepParamsWhenRedirect: true])
 +
"/viewPublishers"(redirect: [controller: 'publisher', action: 'list', permanent: true, keepParamsWhenRedirect: true])
 +
</syntaxhighlight>
 +
 
 +
===[https://docs.grails.org/3.3.10/guide/single.html#embeddedVariables Integrierte Variablen im Mapping]===
 +
Im vorigen Abschnitt wurde gezeigt, wie einfache URLs mit '''konkreten "Token"''' abgebildet werden können.
 +
 
 +
*'''Bei der URL-Abbildung sind "Token" die Zeichenfolge zwischen jedem Schrägstrich, '/''''.
 +
*Ein konkreter Token ist ein Token, das gut definiert ist, wie z.B. <code>/product</code>.
 +
 
 +
In vielen Fällen weiß man jedoch erst zur Laufzeit, welchen Wert ein bestimmtes Token haben wird. In diesem Fall kann man zum Beispiel variable Platzhalter innerhalb der URL verwenden:<syntaxhighlight lang="groovy">
 +
static mappings = {
 +
  "/product/$id"(controller: "product")
 +
}
 +
</syntaxhighlight>
 +
In diesem Fall wird Grails durch Einbetten der <code>$id</code>-Variablen als zweites Token das zweite Token automatisch in einen Parameter (verfügbar über das <code>params</code>-Scope-Objekt) namens <code>id</code> abbilden.
 +
<syntaxhighlight lang="groovy">
 +
class ProductController {
 +
    def index() { render params.id }
 +
}
 +
</syntaxhighlight>
 +
 
 +
Natürlich kann man auch mehrere Variablen verwenden, wie bei diesem Beispiel eines Blogs:
 +
<syntaxhighlight lang="groovy">
 +
"/$blog/$year/$month/$day/$id"(controller: "blog", action: "show")
 +
</syntaxhighlight>
 +
 
 +
====[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_optional_variables Optionale Variablen im Mappings]====
 +
Indem man ein Fragezeichen am ende des Variabl-Namens anhängt schlägt das URL-Mapping auch zu, wenn der Nutzer diese nicht angegeben hat.
 +
Beispiel-Mapping:
 +
<syntaxhighlight lang="groovy">
 +
"/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show")
 +
</syntaxhighlight>
 +
 
 +
Das obige Beispiel-Mapping schlägt bei allen folgenden URLs zu:
 +
<syntaxhighlight lang="text">
 +
/graemerocher/2007/01/10/my_funky_blog_entry
 +
/graemerocher/2007/01/10
 +
/graemerocher/2007/01
 +
/graemerocher/2007
 +
/graemerocher
 +
</syntaxhighlight>
 +
 
 +
====[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_optional_file_extensions Optionale Datei-Erweiterungen in Mappings]====
 
Wenn man die Datei-Erweiterung auch ganz einfach in einer <code>params</code>-Scope Variable haben möchte, kann man dies durch das anhängen von der Optionalen Variable <code>(.$format)?</code>:
 
Wenn man die Datei-Erweiterung auch ganz einfach in einer <code>params</code>-Scope Variable haben möchte, kann man dies durch das anhängen von der Optionalen Variable <code>(.$format)?</code>:
<syntaxhighlight lang="groovy">
+
<syntaxhighlight lang="groovy">
// Mapping
+
// Mapping
"/$controller/$action?/$id?(.$format)?"()
+
"/$controller/$action?/$id?(.$format)?"()
 
+
 
// Controller
+
// Controller
def index() {
+
def index() {
     render "extension is ${response.format}"
+
     render "extension is ${response.format}"
}
+
}
</syntaxhighlight>
+
</syntaxhighlight>
 
+
 
====Selbst "eingepflanzte" Variablen====
+
====Selbst eingepflanzte Mapping-Variablen====
Man kann auch selber mithilfe von URL-Mapping Variablen in das <code>params</code>-Scope reinschreiben.
+
Man kann auch selber mithilfe von URL-Mapping Variablen in das <code>params</code>-Scope reinschreiben.
<syntaxhighlight lang="groovy">
+
<syntaxhighlight lang="groovy">
"/holiday/win" {
+
"/holiday/win" {
     id = "Marrakech"
+
     id = "Marrakech"
     year = 2007
+
     year = 2007
 +
}
 +
</syntaxhighlight>
 +
====Dynamisch aufgelöste Mapping-Variablen====
 +
Die oben-genannten, selbst setzbaren statischen Variablen sind sehr nützlich. <br>
 +
Aber manchmal soll sich der Wert der Variable je nach Anfrage anders verhalten können. Dies kann man erzielen, indem man einen eigenen Block zur Variable hinzufügt.
 +
<syntaxhighlight lang="groovy">
 +
"/holiday/win" {
 +
    id = { params.id }
 +
    isEligible = { session.user != null } // must be logged in
 +
}
 +
</syntaxhighlight>
 +
Im obrigen Fall wird der Code innerhalb der Blöcke erst dann evaluiert, wenn das URL-Matching zugeschlagen wurde, und kann dadurch "mit Logik versehen werden".
 +
 
 +
<br />
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#applyingConstraints URL-Mapping Constraints (Einschränkungen des Typens einer Variable)]===
 +
Nehmen wir folgendes Beispiel:
 +
<syntaxhighlight lang="groovy">
 +
static mappings = {
 +
  "/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show")
 +
}
 +
</syntaxhighlight>
 +
 
 +
Dieses Mapping funktioniert für:
 +
<syntaxhighlight lang="groovy">
 +
/graemerocher/2007/01/10/my_funky_blog_entry
 +
</syntaxhighlight>
 +
 
 +
Aber auch für Werte, die wir vllt. nicht vorgesehen haben:
 +
<syntaxhighlight lang="groovy">
 +
/graemerocher/not_a_year/not_a_month/not_a_day/my_funky_blog_entry
 +
</syntaxhighlight>
 +
 
 +
Dies wäre sehr problematisch und man müsste eine (komplexe) eigene Logik reincoden, damit bestimmte Variablen nur einen bestimmten Wert haben dürfen.<br>
 +
Zum Glück bieten uns URL-Mappings einen einfachen Weg für diese Einschränkung/Validation:
 +
<syntaxhighlight lang="groovy">
 +
"/$blog/$year?/$month?/$day?/$id?" {
 +
    controller = "blog"
 +
    action = "show"
 +
    constraints {
 +
          year(matches:/\\\d{4}/)
 +
          month(matches:/\\\d{2}/)
 +
          day(matches:/\\\d{2}/)
 +
    }
 +
}
 +
</syntaxhighlight><br />
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#mappingToResponseCodes Error-Code Mapping]===
 +
Mit Grails kann man HTTP-Antwortcodes auch Controllern, Aktionen oder Ansichten zuordnen. Hierzu verwendet man einfach einen Methodennamen, der zu dem Antwort-Code passt, an dem man interessiert ist:
 +
<syntaxhighlight lang="groovy">
 +
"403"(controller: "errors", action: "forbidden")
 +
"404"(controller: "errors", action: "notFound")
 +
"500"(controller: "errors", action: "serverError")
 +
</syntaxhighlight>
 +
 
 +
Man kann auch auftauchende Exceptions auf eine eigene Seite mappen:
 +
<syntaxhighlight lang="groovy">
 +
"500"(controller: "errors", action: "illegalArgument", exception: IllegalArgumentException)
 +
</syntaxhighlight><br />
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#mappingHTTP Nur bestimmte HTTP-Methoden Mappen]===
 +
URL-Mappings können auch so konfiguriert werden, dass sie basierend auf der HTTP-Methode (GET, POST, PUT oder DELETE) abgebildet werden. Dies ist sehr nützlich für RESTful-APIs und zur Einschränkung von Mappings auf der Basis der HTTP-Methode.<syntaxhighlight lang="groovy">
 +
static mappings = {
 +
  "/product/$id"(controller:"product", action: "update", method: "PUT")
 +
}
 +
</syntaxhighlight><br />
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#mappingWildcards Wildcards im Mapping]===
 +
Grails' URL-Mappings-Mechanismus unterstützt auch Wildcard-Mappings.
 +
 
 +
Dieses Mapping stimmt mit allen Pfaden wie <code>/image/logo.jpg</code> überein.<syntaxhighlight lang="groovy">
 +
static mappings = {
 +
    "/images/*.jpg"(controller: "image")
 +
    // Natürlich kann man den gleichen Effekt mit einer Variablen erreichen:
 +
    "/images/$name.jpg"(controller: "image")
 +
}
 +
</syntaxhighlight>Man kann jedoch auch doppelte Platzhalter verwenden, um mehr als eine Ebene darunter abzugleichen:<syntaxhighlight lang="groovy">
 +
static mappings = {
 +
    // will match /image/logo.jpg as well as /image/other/logo.jpg
 +
    "/images/**.jpg"(controller: "image")
 +
}
 +
</syntaxhighlight>Noch besser: Durch die Kombination dieser 2 Prinzipien kann man eine "doppelte Platzhalter-Variable" verwenden:<references /><syntaxhighlight lang="groovy">
 +
// Mapping
 +
static mappings = {
 +
    // will match /image/logo.jpg and /image/other/logo.jpg
 +
    "/images/$name**.jpg"(controller: "image")
 +
}
 +
 
 +
// Controller
 +
def name = params.name
 +
println name // prints "logo" or "other/logo"
 +
</syntaxhighlight>
 +
 
 +
 
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#restfulMappings RESTful-Mapping]===
 +
Seit Grails 2.3 ist es möglich, RESTful-URL-Mappings zu erstellen, die nach Konvention auf Controller abbilden. Die Syntax dafür lautet wie folgt:<syntaxhighlight lang="groovy">
 +
"/books"(resources:'book')
 +
</syntaxhighlight>
 +
Man definiert einen Basis-URI und den Namen des Controllers, der zugeordnet werden soll, mit dem <code>resources</code>-Parameter. Das obige Mapping führt zu den folgenden URLs:
 +
{| class="wikitable"
 +
!HTTP Methode
 +
!URI
 +
!Grails Aktion
 +
|-
 +
|GET
 +
|/books
 +
|index
 +
|-
 +
|GET
 +
|/books/create
 +
|create
 +
|-
 +
|POST
 +
|/books
 +
|save
 +
|-
 +
|GET
 +
|/books/${id}
 +
|show
 +
|-
 +
|GET
 +
|/books/${id}/edit
 +
|edit
 +
|-
 +
|PUT
 +
|/books/${id}
 +
|update
 +
|-
 +
|DELETE
 +
|/books/${id}
 +
|delete
 +
|}
 +
Wenn man sich nicht sicher ist, welche Zuordnung für einen Fall erzeugt wird, führt man einfach den Befehl <code>$ grails url-mappings-report</code> aus.
 +
 
 +
Wenn man eine der genannten URL-Mappings ein- oder ausschließen möchten, kann man dies mit dem <code>includes</code>- oder <code>excludes</code>-Parameter tun, der den Namen der Grails-Aktion zum Ein- oder Ausschließen akzeptiert:<syntaxhighlight lang="groovy">
 +
"/books"(resources:'book', excludes:['delete', 'update'])
 +
// or
 +
"/books"(resources:'book', includes:['index', 'show'])
 +
</syntaxhighlight>
 +
====Explizites REST-Mapping====
 +
(Ab Grails 3.1) Wenn man es vorzieht, sich bei der Definition seines Mappings nicht auf ein Ressourcen-Mapping zu verlassen, können Sie jedem URL-Mapping den Namen der HTTP-Methode (in Kleinbuchstaben) voranstellen, um die HTTP-Methode anzugeben, auf die es sich bezieht. Die folgende URL-Abbildung:<syntaxhighlight lang="groovy">
 +
"/books"(resources:'book')
 +
</syntaxhighlight>
 +
ist gleich wie
 +
<syntaxhighlight lang="groovy">
 +
get "/books"(controller:"book", action:"index")
 +
get "/books/create"(controller:"book", action:"create")
 +
post "/books"(controller:"book", action:"save")
 +
get "/books/$id"(controller:"book", action:"show")
 +
get "/books/$id/edit"(controller:"book", action:"edit")
 +
put "/books/$id"(controller:"book", action:"update")
 +
delete "/books/$id"(controller:"book", action:"delete")
 +
</syntaxhighlight>
 +
 
 +
====Single Resources====
 +
Eine <code>Single Ressource</code> ist eine Ressource, für die es nur eine (möglicherweise pro Benutzer) im System gibt. Man kann eine einzelne Ressource mit dem <code>single</code> Parameter anlegen (im Gegensatz zu <code>resources</code>):<syntaxhighlight lang="groovy">
 +
"/book"(single:'book')
 +
 
 +
</syntaxhighlight>Daraus ergeben sich die folgenden URL-Mappings: (Der Hauptunterschied besteht darin, dass die Id nicht in der URL-Zuordnung enthalten ist.)
 +
{| class="wikitable"
 +
!HTTP Method
 +
!URI
 +
!Grails Aktion
 +
|-
 +
|GET
 +
|/book/create
 +
|create
 +
|-
 +
|POST
 +
|/book
 +
|save
 +
|-
 +
|GET
 +
|/book
 +
|show
 +
|-
 +
|GET
 +
|/book/edit
 +
|edit
 +
|-
 +
|PUT
 +
|/book
 +
|update
 +
|-
 +
|DELETE
 +
|/book
 +
|delete
 +
|}
 +
 
 +
====Nested URL-Resource-Mapping====
 +
Man kann Ressourcen-Zuordnungen verschachteln, um untergeordnete Ressourcen zu erzeugen. Zum Beispiel resultiert die folgende Definition dazu,..:<syntaxhighlight lang="groovy">
 +
"/books"(resources:'book') {
 +
  "/authors"(resources:"author")
 +
}
 +
</syntaxhighlight>...dass die folgende URL verfügbar ist:
 +
{| class="wikitable"
 +
!HTTP Method
 +
!URL
 +
!Grails Action
 +
|-
 +
|GET
 +
|/books/${bookId}/authors
 +
|index
 +
|-
 +
|GET
 +
|/books/${bookId}/authors/create
 +
|create
 +
|-
 +
|POST
 +
|/books/${bookId}/authors
 +
|save
 +
|-
 +
|GET
 +
|/books/${bookId}/authors/${id}
 +
|show
 +
|-
 +
|GET
 +
|/books/${bookId}/authors/edit/${id}
 +
|edit
 +
|-
 +
|PUT
 +
|/books/${bookId}/authors/${id}
 +
|update
 +
|-
 +
|DELETE
 +
|/books/${bookId}/authors/${id}
 +
|delete
 +
|}
 +
 
 +
====Linking to REST-Mappings from GSP====
 +
Man kann auf jedes URL-Mapping verlinken, das mit dem von Grails bereitgestellten <code>g:link</code>-Tag erstellt wurde, indem man einfach auf den Controller und die Aktion verweist, auf die verlinkt werden soll:<syntaxhighlight lang="jsp">
 +
<g:link controller="book" action="index">My Link</g:link>
 +
</syntaxhighlight>Als Annehmlichkeit kann man auch eine Domäneninstanz an das Ressourcenattribut des Link-Tags übergeben:<syntaxhighlight lang="jsp">
 +
<!-- Hier wird automatisch der richtige Link erzeugt (in diesem Fall "/books/1" für eine ID von "1"). -->
 +
<g:link resource="${book}">My Link</g:link>
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
Im Falle der verschachtelten Ressourcen ist ein wenig anders, da sie in der Regel zwei Identifikatoren erfordern (die ID der Ressource und diejenige, in der sie verschachtelt ist). Zum Beispiel das Mapping:<syntaxhighlight lang="groovy">
 +
"/books"(resources:'book') {
 +
  "/authors"(resources:"author")
 +
}
 +
</syntaxhighlight>Wenn man einen Link zur Show-Aktion des Author-Controllers herstellen wollen würde, würde man es so schreiben müssen:<syntaxhighlight lang="jsp">
 +
<!-- Results in /books/1/authors/2 -->
 +
<g:link controller="author" action="show" method="GET" params="[bookId:1]" id="2">The Author</g:link>
 +
</syntaxhighlight>Um dies jedoch prägnanter zu machen, gibt es ein <code>resource</code>-Attribut zum Link-Tag, das stattdessen verwendet werden kann:
 +
 
 +
*Das <code>resource</code>-Attribut akzeptiert einen durch einen Schrägstrich getrennten Pfad zur Ressource (in diesem Fall "book/author"). Die Attribute des Tags können zur Angabe des notwendigen <code>bookId</code>-Parameters verwendet werden.
 +
<syntaxhighlight lang="jsp">
 +
<!-- Results in /books/1/authors/2 -->
 +
<g:link resource="book/author" action="show" bookId="1" id="2">My Link</g:link>
 +
</syntaxhighlight><br />
 +
 
 +
==[https://docs.grails.org/3.3.9/guide/theWebLayer.html#cors CORS]==
 +
Spring Boot bietet CORS-Unterstützung out-of-the-box, aber es ist schwierig, es in einer Grails-Anwendung zu konfigurieren, da URLMapping anstelle von Annotationing zur Definition von URLs verwendet werden.
 +
 
 +
Beginnend mit Grails 3.2.1 wurde eine Möglichkeit zur Konfiguration von CORS hinzugefügt, die in einer Grails-Anwendung sinnvoll ist.
 +
 
 +
Einmal aktiviert, ist die Standardeinstellung "weit offen".<syntaxhighlight lang="yaml">
 +
# application.yml
 +
grails:
 +
    cors:
 +
        enabled: true
 +
</syntaxhighlight>Das ergibt eine Zuordnung zu allen urls (<code>/**</code>) mit:
 +
{| class="wikitable"
 +
|allowedOrigins
 +
|<code>['*']</code>
 +
|-
 +
|allowedMethods
 +
|<code>['*']</code>
 +
|-
 +
|allowedHeaders
 +
|<code>['*']</code>
 +
|-
 +
|exposedHeaders
 +
|<code>null</code>
 +
|-
 +
|maxAge
 +
|<code>1800</code>
 +
|-
 +
|allowCredentials
 +
|true
 +
|}
 +
Einige dieser Einstellungen kommen direkt von Spring Boot und können sich in zukünftigen Versionen ändern. Siehe [https://docs.spring.io/spring/docs/current/javadoc-api//org/springframework/web/cors/CorsConfiguration.html#applyPermitDefaultValues Spring CORS Configuration Documentation]
 +
Alle diese Einstellungen können leicht außer Kraft gesetzt werden.<syntaxhighlight lang="yaml">
 +
# application.yml
 +
grails:
 +
    cors:
 +
        enabled: true
 +
        allowedOrigins:
 +
            - http://localhost:5000
 +
</syntaxhighlight>
 +
 
 +
 
 +
Man kann auch Einstellungen für mehrere URLs definieren:
 +
 
 +
*Beachte: Die Angabe von mindestens einem Mapping deaktiviert die Erstellung des globalen Mappings (<code>/**</code>). Wenn man diese Einstellung beibehalten möchten, sollte man diese zusammen mit den anderen Mappings explizit angeben
 +
<syntaxhighlight lang="yaml">
 +
# application.yml
 +
grails:
 +
    cors:
 +
        enabled: true
 +
        allowedHeaders:
 +
            - Content-Type
 +
        mappings:
 +
            /api/**:
 +
                allowedOrigins:
 +
                    - http://localhost:5000
 +
                # Other configurations not specified default to the global config
 +
</syntaxhighlight>Die obigen Einstellungen führen zu einer einzigen Mapping von <code>/api/**</code> mit den folgenden Einstellungen:
 +
{| class="wikitable"
 +
|allowedOrigins
 +
|<code>['<nowiki>http://localhost:5000'</nowiki>]</code>
 +
|-
 +
|allowedMethods
 +
|<code>['*']</code>
 +
|-
 +
|allowedHeaders
 +
|<code>['Content-Type']</code>
 +
|-
 +
|exposedHeaders
 +
|<code>null</code>
 +
|-
 +
|maxAge
 +
|<code>1800</code>
 +
|-
 +
|allowCredentials
 +
|true
 +
|}
 +
 
 +
Wenn man keine der Standardeinstellungen außer Kraft setzen will, sondern nur URLs angeben möchten, kann man dies wie in diesem Beispiel tun:<syntaxhighlight lang="yaml">
 +
# application.yml
 +
grails:
 +
    cors:
 +
        enabled: true
 +
        mappings:
 +
            /api/**: inherit
 +
</syntaxhighlight><br />
 +
 
 +
==[https://docs.grails.org/3.3.9/guide/theWebLayer.html#interceptors Interceptor]==
 +
 
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_interceptors_vs_filters Abfänger vs Filters]===
 +
Das neue Abfang-Konzept in Grails 3.0 ist in vielerlei Hinsicht dem Filter-Konzept überlegen, vor allem können die Inteceptoren die <code>[https://docs.grails.org/latest/guide/staticTypeCheckingAndCompilation.html CompileStatic]</code>-Annotation von Groovy verwenden, um die Leistung zu optimieren (was oft kritisch ist, da die Inteceptoren bei jeder Anfrage ausgeführt werden können).
 +
 
 +
Filters werden aus Gründen der Abwärtskompatibilität weiterhin unterstützt, gelten aber als veraltet.
 +
<br />
 +
 
 +
===Request-Matching von Interceptoren===
 +
Die Konvention von Grails sieht standardmäßig vor, dass ein Interceptor <code>X</code> alle Anfragen vom Controller mit gleichen Namen <code>X</code> abfängt.
 +
 
 +
Mithilfe der [https://docs.grails.org/3.3.9/api/grails/artefact/Interceptor.html Interceptor-API] können jedoch auch eigene Matching-Regeln festgelegt werden. Hierzu gibt es die Methoden <code>match</code> und <code>matchAll</code>, die beide eine Instanz von [https://docs.grails.org/3.3.9/api/grails/interceptors/Matcher.html <code>Matcher</code>] zurückgeben, mit dem man den Interceptor konfigurieren kann.
 +
 
 +
 
 +
 
 +
Zum Beispiel wird der folgende Interceptor sich mit allen Anfragen beschäftigen, die nicht an den <code>login</code>-Controller gerichtet sind:<syntaxhighlight lang="groovy">
 +
class AuthInterceptor {
 +
  AuthInterceptor() {
 +
    matchAll()
 +
    .excludes(controller:"login")
 +
  }
 +
 
 +
  boolean before() {
 +
    // perform authentication
 +
  }
 +
}
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
Man kann den Abgleich auch mit einem benannten Argument durchführen:<syntaxhighlight lang="groovy">
 +
class LoggingInterceptor {
 +
  LoggingInterceptor() {
 +
    match(controller:"book", action:"show") // using strings
 +
    match(controller: ~/(author|publisher)/) // using regex
 +
  }
 +
 
 +
  boolean before() {
 +
    ...
 +
  }
 +
}
 +
</syntaxhighlight>Man kann eine beliebige Anzahl von Matchern verwenden. Diese werden in der Reihenfolge ausgeführt, in der sie definiert wurden.
 +
 
 +
Zum Beispiel wird der obige Interceptor für alle der folgenden Punkte passen:
 +
 
 +
*wenn die <code>show</code>-Aktion vom <code>BookController</code> aufgerufen wird
 +
*wenn (jegliche Aktion vom) <code>AuthorController</code> oder <code>PublisherController</code> aufgerufen wird
 +
 
 +
 
 +
Alle genannten Argumente mit Ausnahme von <code>uri</code> akzeptieren entweder einen String- oder einen Regex-Ausdruck. Das <code>uri</code>-Argument unterstützt einen String-Pfad, der mit dem [https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/AntPathMatcher.html <code>AntPathMatcher</code>] von Spring kompatibel ist. Die möglichen benannten Argumente sind:
 +
 
 +
*<code>namespace</code> - Der Namensraum des Controller
 +
*<code>controller</code> - Der Name des Controllers
 +
*<code>action</code> - Der Name der Aktion
 +
*<code>method</code> - Die HTTP-Methode
 +
*<code>uri</code> - Die URI der Anfrage. Wenn dieses Argument genutzt wird, werden alle anderen Argumente ignoriert und nur dieses Argument genutzt!
 +
 
 +
<br />
 +
 
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#interceptorOrdering Reihenfolge von Interceptoren bestimmen]===
 +
Interceptoren können durch Definition der <code>order</code>-Eigenschaft geordnet werden, die eine numerische Priorität definiert.
 +
 
 +
Der Standardwert der <code>order</code>-Eigenschaft ist 0. Die Ausführungsreihenfolge des Interceptors wird bestimmt, indem die Order-Eigenschaft in aufsteigender Richtung sortiert '''und der niedrigste numerisch geordnete Interceptor zuerst ausgeführt wird.'''
 +
 
 +
 
 +
Die Werte <code>HIGHEST_PRECEDENCE</code> und <code>LOWEST_PRECEDENCE</code> können verwendet werden, um Filter zu definieren, die zuerst bzw. zuletzt ausgeführt werden sollen.
 +
 
 +
Zu beachten gilt: Wenn man einen Interceptor schreibt der von anderen benutzt werden soll, ist es besser, die <code>HIGHEST_PRECEDENCE</code> und <code>LOWEST_PRECEDENCE</code> zu inkrementieren oder zu dekrementieren, damit andere Interceptoren vor oder nach seinem Interceptor eingefügt werden können:<syntaxhighlight lang="groovy">
 +
int order = HIGHEST_PRECEDENCE + 50
 +
// or
 +
int order = LOWEST_PRECEDENCE - 50
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
Um die berechnete Reihenfolge der Interceptoren herauszufinden, kann <code>logback.groovy</code> ein Debug-Logger hinzufügt werden:<syntaxhighlight lang="groovy">
 +
logger 'grails.artefact.Interceptor', DEBUG, ['STDOUT'], false
 +
</syntaxhighlight>
 +
 
 +
 
 +
Die <code>order</code>-Eigenschaft eines Interceptors kann innerhalb <code>grails-app/conf/application.yml</code> auch überschrieben werden:
 +
 
 +
Man kann die Standardreihenfolge eines Interceptors überschreiben, indem man die Konfiguration der <code>bean</code>-Überschreibung in <code>grails-app/conf/application.yml</code> verwendet:<syntaxhighlight lang="yaml">
 +
beans:
 +
  authInterceptor:
 +
    order: 50
 +
</syntaxhighlight><br />
 +
 
 +
===Aufbau von Interceptoren===
 +
Mit dem Befehl <code>$ grails [https://docs.grails.org/3.3.9/ref/Command%20Line/create-interceptor.html create-interceptor] [Name]</code> kann das Grundgerüst für einen Interceptor in <code>grails-app/controllers</code> erstellt werden.
 +
 
 +
Jeder Interceptor erbt nach Konvention vom [https://docs.grails.org/3.3.9/api/grails/artefact/Interceptor.html Interceptor-Trait].
 +
 
 +
 
 +
 
 +
Beispiel: (<code>$ grails create-interceptor Test</code>, Kommentare von mir eingefügt)<syntaxhighlight lang="groovy">
 +
class TestInterceptor {
 +
 
 +
    /**
 +
    * Executed before a matched action
 +
    *
 +
    * @return Whether the action should continue and execute
 +
    */
 +
    boolean before() { true }
 +
 
 +
    /**
 +
    * Executed after the action executes but prior to view rendering
 +
    * The after method can also modify the view or model using the view and model properties respectively.
 +
    *
 +
    * @return True if view rendering should continue, false otherwise
 +
    */
 +
    boolean after() {
 +
        /* Example of altering the 'model' and 'view':
 +
        model.foo = "bar" // add a new model attribute called 'foo'
 +
        view = 'alternate' // render a different view called 'alternate'
 +
        */
 +
        true
 +
    }
 +
   
 +
    /**
 +
    * Executed after view rendering completes
 +
    * If an exception occurs, the exception is available using the 'throwable' property of the Interceptor trait.
 +
    */
 +
    void afterView() {
 +
        //no-op
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
 
 +
==[https://docs.grails.org/3.3.9/guide/theWebLayer.html#contentNegotiation Inhaltsverhandlung (Content Negotiation)]==
 +
Grails hat eine Unterstützung für [[wikipedia:Content_negotiation|Inhaltsverhandlungen]] eingebaut, bei denen entweder
 +
 
 +
*der HTTP-<code>Accept</code>-Header,
 +
*ein expliziter Anforderungsparameter namens <code>format</code> oder
 +
*die Erweiterung einer zugeordneten URI verwendet wird.
 +
 
 +
<br />
 +
 
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_configuring_mime_types Mime-Types konfigurieren]===
 +
Bevor man sich mit Inhaltsverhandlungen befassen kann, muss man Grails mitteilen, welche Inhaltstypen man unterstützen möchten. Standardmäßig wird Grails mit einer Reihe verschiedener Inhaltstypen innerhalb von <code>grails-app/conf/application.yml</code> unter Verwendung der Einstellung <code>grails.mime.types</code> konfiguriert:<syntaxhighlight lang="yaml">
 +
grails:
 +
    mime:
 +
        types:
 +
            all: '*/*'
 +
            atom: application/atom+xml
 +
            css: text/css
 +
            csv: text/csv
 +
            form: application/x-www-form-urlencoded
 +
            html:
 +
              - text/html
 +
              - application/xhtml+xml
 +
            js: text/javascript
 +
            json:
 +
              - application/json
 +
              - text/json
 +
            multipartForm: multipart/form-data
 +
            rss: application/rss+xml
 +
            text: text/plain
 +
            hal:
 +
              - application/hal+json
 +
              - application/hal+xml
 +
            xml:
 +
              - text/xml
 +
              - application/xml
 +
</syntaxhighlight>Das obige Konfigurationsbit erlaubt es Grails, das Format einer Anfrage, die entweder die Medientypen <code>text/xml</code> oder <code>application/xml</code> enthält, als einfach <code>xml</code> zu erkennen. Man kann auch eigenen Typen hinzufügen, indem man einfach neue Einträge in die <code>grails.mime.types</code>-Map einfügt. Der erste Eintrag ist das Standardformat.
 +
<br />
 +
 
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_content_negotiation_using_the_format_request_parameter Inhaltsverhandlung unter Verwendung des <code>format</code> Anfrageparameters]===
 +
Angenommen, eine Controller-Aktion kann eine Ressource in einer Vielzahl von Formaten zurückgeben: HTML, XML und JSON. Welches Format wird der Client erhalten?
 +
 
 +
*Der einfachste und zuverlässigste Weg für den Client, dies zu kontrollieren, ist über einen URL-Parameter namens <code>format</code>:
 +
<syntaxhighlight>
 +
http://my.domain.org/books?format=xml
 +
</syntaxhighlight>
 +
 
 +
 
 +
 
 +
Man '''könnte''' diesen Parameter auch in der URL-Mappings-Definition hart-definieren...<syntaxhighlight lang="groovy">
 +
"/book/list"(controller:"book", action:"list") {
 +
    format = "xml"
 +
}
 +
</syntaxhighlight>.., aber man kann auch die Controller-spezifische Methode <code>withFormat()</code> verwenden:<syntaxhighlight lang="groovy">
 +
import grails.converters.JSON
 +
import grails.converters.XML
 +
 
 +
class BookController {
 +
 
 +
    def list() {
 +
        def books = Book.list()
 +
 
 +
        withFormat {
 +
            html bookList: books
 +
            json { render books as JSON }
 +
            xml { render books as XML }
 +
            '*' { render books as JSON }
 +
        }
 +
    }
 +
}
 +
</syntaxhighlight>In diesem Beispiel führt Grails nur den Block innerhalb <code>withFormat()</code> aus, der dem angeforderten Inhaltstyp entspricht. Wenn das bevorzugte Format also html ist, wird Grails nur den <code>html()</code>-Aufruf ausführen.
 +
 
 +
Jeder 'Block' kann entweder ein Map-Modell für die entsprechende Ansicht (wie wir es für 'html' im obigen Beispiel tun) oder eine Closure sein. Die Closure kann jeden Standard-Aktionscode enthalten, z.B. kann er ein Modell zurückgeben oder Inhalte direkt rendern.
 +
 
 +
Wenn kein Format explizit übereinstimmt, kann ein <code>*</code> (Wildcard)-Block verwendet werden, um alle anderen Formate zu behandeln.
 +
 
 +
 
 +
Es gibt ein spezielles Format, <code>all</code>, das anders behandelt wird als die expliziten Formate. Wenn <code>all</code> angegeben wird (normalerweise geschieht dies über den Accept-Header - siehe unten), dann wird der erste Block von <code>withFormat()</code> ausgeführt, wenn kein <code>*</code> (Platzhalter)-Block vorhanden ist.
 +
<br />
 +
 
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_using_the_accept_header Inhaltsverhandlung unter Verwendung des <code>Accept</code>-Headers]===
 +
Jede eingehende HTTP-Anfrage hat einen speziellen <code>[https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html Accept]</code>-Header, der definiert, welche Medientypen (oder Mime-Typen) ein Client "akzeptieren" kann. In älteren Browsern ist dies typischerweise der Fall:<syntaxhighlight>
 +
*/*
 +
</syntaxhighlight>was einfach alles bedeutet. Neuere Browser senden jedoch interessantere Werte wie diesen, der von Firefox gesendet wird:<syntaxhighlight>
 +
text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, \
 +
    text/plain;q=0.8, image/png, */*;q=0.5
 +
</syntaxhighlight>'''Dieser spezielle Accept-Header ist nicht hilfreich, da er anzeigt, dass XML das bevorzugte Antwortformat ist, während der Benutzer eigentlich HTML erwartet.''' Aus diesem Grund ignoriert Grails den Accept-Header bei Browsern standardmäßig.
 +
 
 +
Nicht-Browser-Clients sind jedoch in der Regel spezifischer in ihren Anforderungen und können Accept-Header wie z.B:<syntaxhighlight>
 +
application/json
 +
</syntaxhighlight>Wie erwähnt, ist die Standardkonfiguration in Grails, den Accept-Header für Browser zu ignorieren. Dies wird durch die Konfigurationseinstellung <code>grails.mime.disable.accept.header.userAgents</code> erreicht, die so konfiguriert ist, dass sie die wichtigsten Rendering-Engines erkennt und deren ACCEPT-Header ignoriert. Dadurch kann die Inhaltsaushandlung von Grails auch für Nicht-Browser-Clients weiterhin funktionieren:<syntaxhighlight lang="yaml">
 +
grails:
 +
    mime:
 +
        disable:
 +
            accept:
 +
                header:
 +
                    userAgents:
 +
                        - Gecko
 +
                        - WebKit
 +
                        - Presto
 +
                        - Trident
 +
</syntaxhighlight>Anmerkung:<blockquote>Wenn der "accept"-Header verwendet wird, aber keine registrierten Inhaltstypen enthält, geht Grails davon aus, dass ein defekter Browser die Anfrage stellt und stellt das HTML-Format ein - beachten Sie, dass sich dies von der Funktionsweise der anderen Inhaltsaushandlungsmodi unterscheidet, da diese das "all"-Format aktivieren würden!
 +
 
 +
''Ein Accept-Header mit dem Wert <code>*/\*</code> resultiert in den Wert <code>all</code> für die <code>format</code>-Eigenschaft.''</blockquote><br />
 +
 
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_content_negotiation_with_uri_extensions Inhaltsverhandlung unter Verwendung einer Erweiterung in der URI]===
 +
Dank der Standard URL-Mapping-Definition..<syntaxhighlight lang="groovy">
 +
"/$controller/$action?/$id?(.$format)?"{
 +
</syntaxhighlight>..kann das <code>format</code> auch durch das anhängen der gewünschten Erweiterung am Ende der URI erfolgen:<syntaxhighlight>
 +
/book/list.xml
 +
 
 +
</syntaxhighlight><br />
 +
 
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_request_format_vs_response_format Anfrage-Format vs Rückgabe-Format]===
 +
Ab Grails 2.0 gibt es einen getrennten Begriff des <code>request</code>-Formats und des <code>response</code>-Formats.
 +
 
 +
*Das <code>request</code>-Format wird durch den <code>CONTENT_TYPE</code>-Header diktiert und wird normalerweise verwendet, um festzustellen, ob die eingehende Anforderung in XML oder JSON geparst werden kann.
 +
 
 +
*Das <code>response</code>-Format verwendet die Dateierweiterung, den Formatparameter oder den ACCEPT-Header, um zu versuchen, dem Client eine geeignete Antwort zu liefern. (Wie bereits mehrmals beschrieben)
 +
 
 +
 
 +
 
 +
Das bei Controllern verfügbare <code>withFormat</code> befasst sich speziell mit dem <code>response</code>-Format. Wenn man eine Logik hinzufügen möchte, die sich mit dem <code>request</code>-Format befasst, kann man dies mit einer separaten <code>withFormat</code>-Methode tun, die im <code>request</code>-Objekt verfügbar ist:<syntaxhighlight lang="groovy">
 +
request.withFormat {
 +
    xml {
 +
        // read XML
 +
    }
 +
    json {
 +
        // read JSON
 +
    }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#_testing_content_negotiation Inhaltsverhandlung Testen]===
 +
Um die Inhaltsaushandlung in einem Unit- oder Integrationstest zu testen (siehe Abschnitt Testen), kann man entweder
  
====Dynamisch aufgelöste Variablen====
+
*die Kopfzeilen der eingehenden Anfragen manipulieren:
Die oben-genannten, selbst setzbaren statischen Variablen sind sehr nützlich. <br>
 
Aber manchmal soll sich der Wert der Variable je nach Anfrage anders verhalten können.
 
Dies kann man erzielen, indem man einen eigenen Block zur Variable hinzufügt.
 
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
"/holiday/win" {
+
void testJavascriptOutput() {
    id = { params.id }
+
    def controller = new TestController()
    isEligible = { session.user != null } // must be logged in
+
    controller.request.addHeader "Accept",
}
+
              "text/javascript, text/html, application/xml, text/xml, */*"
</syntaxhighlight>
 
Im obrigen Fall wird der Code innerhalb der Blöcke erst dann evaluiert, wenn das URL-Matching zugeschlagen wurde, und kann dadurch "mit Logik versehen werden".
 
  
====Constraints (Einschränkungen des Typens einer Variable)====
+
    controller.testAction()
Nehmen wir folgendes Beispiel:
+
    assertEquals "alert('hello')", controller.response.contentAsString
<syntaxhighlight lang="groovy">
 
static mappings = {
 
  "/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show")
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Dieses Mapping funktioniert für:
+
*oder den <code>format</code>-Parameter der Anfrage setzen, um einen ähnlichen Effekt auszulösen:
<syntaxhighlight lang="groovy">
 
/graemerocher/2007/01/10/my_funky_blog_entry
 
</syntaxhighlight>
 
 
 
Aber auch für Werte, die wir vllt. nicht vorgesehen haben:
 
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
/graemerocher/not_a_year/not_a_month/not_a_day/my_funky_blog_entry
+
void testJavascriptOutput() {
</syntaxhighlight>
+
    def controller = new TestContr