Groovy als Web-Backend: Unterschied zwischen den Versionen

Aus Jonas Notizen Webseite
Zur Navigation springen Zur Suche springen
(Kleine Texte verbessert)
 
(46 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
= Einführung =
+
[[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''.
== Über Grails ==
+
<br />
Für die Java-Technologie gibt es eine große Anzahl Web-Frameworks. Diese sind aber meist komplizierter als sie sein müssten,
+
=Einführung=
und folgen meist nicht dem '''[https://de.wikipedia.org/wiki/Don’t_repeat_yourself DRY-Prinzip]''' ("'''D'''on't '''R'''epeat '''y'''ourself").<br>
+
==Quellen==
Dynamische Frameworks wie [https://www.djangoproject.com/ Django (Python)] und [https://rubyonrails.org/ Rails (Ruby)]
 
haben neue 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.'''
+
*[https://grails.org/ Offizielle Grails Webseite]
''Das Besondere daran ist jedoch, dass es auf bereits etablierten Java-Technologien wie Spring und Hibernate aufbaut.''
+
*[http://docs.grails.org/3.3.9/guide/single.html Offizielle Dokumentationsseite zu Grails 3.3.9] (Single-Page)
 +
*[http://guides.grails.org/ Homepage der offiziellen Grails-Guides]
 +
*[http://views.grails.org/latest/ Startseite des offiziellen Unterprojekts "Grails Views"]
 +
*[[wikipedia:Grails_(framework)|Englischer Wikipedia-Eintrag zum Thema "Grails (Framework)"]]
  
Zu den hervorbenden Features zählen:
+
<br />
* [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].
+
==Über Grails==
* View-Templating zum [https://gsp.grails.org/ rendern von HTML] sowie auch [http://views.grails.org/ JSON]
+
[[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").  
* Eine Controller-Logik, aufgebaut auf [http://www.spring.io/ Spring Boot]
 
* 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)''
 
* Eine interaktive Kommandozeilen-Umgebung (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
 
  
All dies wird durch die Leistungsfähigkeit der [http://groovy-lang.org/ Groovy]-Sprache und den umfassenden Einsatz
+
<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.  
[https://de.wikipedia.org/wiki/Domänenspezifische_Sprache domänenspezifischer Sprachen] (DSLs) wesentlich vereinfacht.
 
  
== Grails installieren mithilfe des SDK-Mans ==
+
'''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.''
''(Für Grails 3 wird eine minimale JDK-Version von 1.8 gefordert [https://docs.grails.org/3.3.10/guide/single.html#requirements].)''
 
  
Mithilfe vom [https://sdkman.io/ SDKMAN] kann man Grails ganz einfach auf sein System mit folgendem Befehl installieren:<br>
+
Zu den hervor-zu-hebenden Features zählen:
<code>$ sdk install grails 3.3.9</code>
 
  
== App erstellen ==
+
*[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].
<code>$ grails create-app APP-NAME</code> erstellt im derzeitigen Verzeichnis die Grundlage für die neue App.
+
*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
 +
*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] ''(Starthilfen)''
 +
*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 neu-geladen werden kann
  
=== Grails Befehle ausführen ===
+
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.
Wenn man jetzt in den erstellten Projekt-Ordner wechselt ''(<code>$ cd APP-NAME</code>)'' gibt es 2 Möglichkeiten, Grails-Befehle auszuführen:
 
* <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 / benötigte Projekt/Grails-Daten geladen (= Weniger Ladezeit)
 
** Vorteil: Auto-Vervollständigung mithilfe der Tabulator-Taste
 
  
== [http://docs.grails.org/latest/guide/single.html#conventionOverConfiguration "Konventionen über Konfiguration"] ==
+
<br />
"Konventionen über Konfiguration" bedeutet, '''dass der Ort/Name einer Datei zur Identifikation seiner Rolle/Eigenschaften genutzt wird'''.<br>
+
 
'''Je nach Ort einer Klasse erhält diese spezifische Variablen/Methoden automatisch durch DSLs hinzugefügt/injected. (Beispiel: [https://docs.grails.org/latest/ref/Constraints/Usage.html constraints], [http://docs.grails.org/3.1.1/ref/Domain%20Classes/hasMany.html hasMany]).  
+
==Installation von Grails 3.3==
 +
 
 +
===Installations-Anforderungen===
 +
<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>
 +
 
 +
===Automatische Grails-Installation mithilfe des SDKMAN's-Projekts===
 +
<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>
 +
 
 +
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
 +
 
 +
Optional noch Java und Groovy SDK installieren, falls nicht schon getan:
 +
$ 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.
 +
 
 +
''Das Standardmäßig genutzte [https://github.com/grails-profiles Profil] zur Erstellung des Grundgerüsts ist <code>web</code>.''
 +
 
 +
===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:
 +
 
 +
#<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
 +
 
 +
 
 +
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 />
 +
==Grails-Integrationen für diverse IDE's==
 +
[[Datei:IntelliJ IDEA Logo.svg|mini|140x140px|IntelliJ IDEA Logo]]
 +
 
 +
===IntelliJ-IDEA===
 +
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"]).
 +
 
 +
Ich kann es jedem nur empfehlen - ich könnte nicht-mehr ohne die Funktionen von IntelliJ IDEA Ultimate leben.
 +
 
 +
===TextMate, Sublime, VIM, ...===
 +
Siehe [http://docs.grails.org/3.3.9/guide/single.html#_textmate_sublime_vim_etc offizielle Dokumentation]
 +
<br />
 +
==[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/[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:
* <code>grails-app</code> - "Top-Level" Ordner für allerhand App-Resourcen
+
{{#tree:
** <code>'''assets'''</code> - Statische, unveränderbare Resourcen wie CSS, Bilder und Javascript:
+
*{"expanded":true, "folder":true}<code>grails-app</code> - "Top-Level" Ordner für allerhand Groovy-Ressourcen
*** <code>images</code>
+
**{"expanded":true, "folder":true}<code>'''assets'''</code> - Statische, unveränderbare Ressourcen wie CSS, Bilder und Javascript (Siehe [https://grails.org/plugin/asset-pipeline asset-pipeline Plugin])
*** <code>javascripts</code>
+
***{"folder":true}<code>images</code>
*** <code>stylesheets</code>
+
***{"folder":true}<code>javascripts</code>
** <code>'''conf'''</code> - [https://docs.grails.org/latest/guide/conf.html Konfigurations-Dateien]
+
***{"folder":true}<code>stylesheets</code>
*** <code>'''application.yml'''</code> - Runtime Konfigurationen
+
**{"expanded":true, "folder":true}<code>'''conf'''</code> - [https://docs.grails.org/3.3.9/guide/conf.html Konfigurations-Dateien]
*** <code>logback.groovy</code> - [https://logback.qos.ch/manual/groovy.html logback-Konfigurationen]
+
***<code>'''application.yml'''</code> - Laufzeit Konfiguration
** <code>'''controllers'''</code> - [http://docs.grails.org/latest/guide/theWebLayer.html#controllers Web-Controller-Klassen] - '''Das C''' im '''M'''odel'''V'''iew'''C'''ontrol
+
***<code>logback.groovy</code> - [https://logback.qos.ch/manual/groovy.html Groovy LOGBack-Konfiguration]
** <code>'''domain'''</code> - [http://docs.grails.org/latest/guide/GORM.html Domain-Klassen] - '''Das M''' im '''M'''odel'''V'''iew'''C'''ontrol
+
**{"folder":true} <code>'''controllers'''</code> - [http://docs.grails.org/3.3.9/guide/theWebLayer.html#controllers Web-Controller-Klassen] - '''Das C''' im '''M'''odel'''V'''iew'''C'''ontrol
** <code>'''i18n'''</code> - Nachrichten-Bündel für [http://docs.grails.org/latest/guide/i18n.html i18n (Internationalisierung)]
+
***<code>*Controller.groovy</code>
** <code>init</code> - Eingangspunkt der App
+
**{"folder":true}<code>'''domain'''</code> - [http://docs.grails.org/3.3.9/guide/GORM.html Domain-Klassen] - '''Das M''' im '''M'''odel'''V'''iew'''C'''ontrol
*** <code>Application.groovy</code> - Eingangspunkt zum grundsätzlichen starten von Grails (Enthält die <code>main</code>-Methode)
+
***<code>*.groovy</code>
*** <code>'''Bootstrap.groovy'''</code> - Beinhält Methoden, die nach dem anfänglichen initialisieren von Grails (Siehe oben) aufgerufen werden. In dieser Klasse steht einem schon die so ziemlich gesamte Grails-Umgebung zur Verfügung (um zB. GORM-Objekte zu erstellen/validieren/löschen/... , E-Mails zu schicken, Konfigurationsdateien zu lesen, ...)
+
**{"folder":true}<code>'''i18n'''</code> - Nachrichten-Bündel für [http://docs.grails.org/3.3.9/guide/i18n.html i18n (Internationalisierung)]
** <code>jobs</code> - Klassen, die in einem selbst-bestimmten Zyklus aufgerufen werden ([https://grails.org/plugin/quartz?skipRedirect=true Siehe Quartz-Plugin])
+
***<code>*.properties</code>
** <code>services</code> - Die [http://docs.grails.org/latest/guide/services.html Service-Layer] für die eigentliche Applikations-Logik (Controller sollten nur die Request/Redirects behandeln)
+
**{"expanded":true, "folder":true}<code>init</code> - Eingangspunkt der App
** <code>taglib</code> - [https://gsp.grails.org/latest/guide/taglibs.html Tag-Libraries] zum erstellen eigener GSP-Tags
+
***<code>Application.groovy</code> - Eingangspunkt zum grundsätzlichen starten von Grails (Enthält die <code>main</code>-Methode)
** <code>utils</code> - Hilfsklassen die auf die Grails-Umgebung Zugriff haben?
+
***<code>'''Bootstrap.groovy'''</code> - Beinhält Methoden, die nach dem anfänglichen initialisieren von Grails (Siehe oben) aufgerufen werden. In dieser Klasse steht einem schon die so ziemlich gesamte Grails-Umgebung zur Verfügung (um zB. GORM-Objekte zu erstellen/validieren/löschen/... , E-Mails zu schicken, Konfigurationsdateien zu lesen, ...)
** <code>'''views'''</code> - [http://docs.grails.org/latest/guide/theWebLayer.html#gsp Groovy Server Pages] - '''Das V''' im '''M'''odel'''V'''iew'''C'''ontrol
+
**{"folder":true}<code>jobs</code> - Klassen, die in einem selbst-bestimmten Zyklus aufgerufen werden (Siehe [https://grails.org/plugin/quartz?skipRedirect=true Quartz-Plugin])
* <code>scripts</code> - Zum erstellen [http://docs.grails.org/latest/guide/commandLine.html eigener Grails Befehle, zB. Code-Generierungs Skripts wie <code>generate-all</code>]
+
***<code>*.groovy</code>
* <code>src/main/groovy</code> - (Anderweitig) Hilfsklassen
+
**{"folder":true}<code>services</code> - Die [http://docs.grails.org/latest/guide/services.html Service-Layer] für die eigentliche Applikations-Logik (Controller sollten nur die Request/Redirects behandeln)
* <code>src/test/groovy</code> - [http://docs.grails.org/latest/guide/testing.html Unit und Integrations-Tests]
+
***<code>*Service.groovy</code>
* <code>'''build.gradle'''</code> - (Gradle-)Build Konfigurationen (Für Plugins, Dependencies, ...)
+
**{"folder":true}<code>taglib</code> - [https://gsp.grails.org/latest/guide/taglibs.html Tag-Libraries] zum erstellen eigener GSP-Tags
 +
***<code>*TagLib.groovy</code>
 +
**{"folder":true}<code>utils</code> - Hilfsklassen die auf die Grails-Umgebung Zugriff haben?
 +
***<code>*.groovy</code>
 +
**{"expanded":true, "folder":true}<code>'''views'''</code> - [http://docs.grails.org/latest/guide/theWebLayer.html#gsp Groovy Server Pages] - '''Das V''' im '''M'''odel'''V'''iew'''C'''ontrol
 +
***{"folder":true}<code>[controller]</code>
 +
****<code>*.gsp</code>
 +
*{"folder":true}<code>scripts</code> - Zum erstellen [http://docs.grails.org/latest/guide/commandLine.html eigener Grails Befehle, zB. Code-Generierungs Skripts wie <code>generate-all</code>]
 +
***<code>*.groovy</code>
 +
*{"expanded":true, "folder":true}<code>src</code>
 +
**{"expanded":true, "folder":true}<code>main</code>
 +
***{"folder":true}<code>groovy</code> - (Anderweitig) Hilfsklassen
 +
****<code>*.groovy</code>
 +
**{"expanded":true, "folder":true}<code>test</code>
 +
***{"folder":true}<code>groovy</code> - [http://docs.grails.org/latest/guide/testing.html Unit und Integrations-Tests]
 +
****<code>*.groovy</code>
 +
*''<code>'''build.gradle'''</code> - (Gradle-)Build Konfigurationen (Für Plugins, Dependencies, ...)''
 +
}}
 +
 
 +
==App starten==
 +
Grails-Apps können durch den eingebauten Tomcat-Server mit dem Befehl <code>$ grails [http://docs.grails.org/latest/ref/Command%20Line/run-app.html run-app]</code> "von Grund auf" gestartet werden.
  
== App starten ==
+
*Standard-Port dieses Web-Servers ist <code>8080</code>, kann aber durch den Parameter <code>-port=[PORT]</code> abgeändert werden.
Grails-Apps können durch den eingebauten Tomcat-Server mit dem Befehl
+
*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/>''
<code>$ grails [http://docs.grails.org/latest/ref/Command%20Line/run-app.html run-app]</code> ("von Grund auf") gestartet 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/)''
 
  
== App testen ==
+
==App testen==
Alle <code>$ grails create-*</code> Befehle erstellen eine dazugehörige
+
Alle <code>$ grails create-*</code> Befehle generieren eine [http://docs.grails.org/latest/guide/testing.html Unit oder Integrations-Testing] Klasse im <code>src/test/groovy</code> Ordner.
[http://docs.grails.org/latest/guide/testing.html Unit-Testing] Klasse unter <code>src/test/groovy</code>.
 
Wenn man nun die Logik hinter den Tests hinzugefügt hat, kann man diese mit
 
<code>$ grails [http://docs.grails.org/latest/ref/Command%20Line/test-app.html test-app]</code> starten.
 
  
== [https://docs.grails.org/3.3.9/guide/gettingStarted.html#deployingAnApplication App bereitstellen] ==
+
Wenn man nun die Logik hinter den Tests hinzugefügt hat, kann man diese mit <code>$ grails [http://docs.grails.org/latest/ref/Command%20Line/test-app.html test-app]</code> starten.
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>.java</code>-Archiv)''<br>
 
  
<code>$ grails war</code> erstellt diese <code>.war</code>-Datei im Verzeichnis <code>build/libs</code> des Projekts.
+
==[https://docs.grails.org/3.3.9/guide/gettingStarted.html#deployingAnApplication App bereitstellen]==
* Grails inkludiert eine eigene eingebette Version von Tomcat innerhalb der WAR-Datei, welches zu Problemen führen kann wenn der eigentliche Server eine andere Version von Tomcat hat:
+
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>
Wenn man dies nicht will kann man den Scope innerhalb von <code>build.gradle</code> wie folgt ändern:  
+
 
 +
===Grails und Tomcat===
 +
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:  
 
<syntaxhighlight lang="gradle">
 
<syntaxhighlight lang="gradle">
 
provided "org.springframework.boot:spring-boot-starter-tomcat"
 
provided "org.springframework.boot:spring-boot-starter-tomcat"
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Grails baut standardmäßig auf Tomcat-8 APIs auf. Diese Version kann man auch 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'
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== WAR-Datei starten ===
+
===WAR-Datei erstellen und starten===
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 Grails-Befehl <code>$ grails war</code> baut Grails die "<code>.war</code>-Version" der App und speichert das Resultat unter <code>build/libs</code>.
Beispiel: <code>java -Dgrails.env=prod -jar build/libs/mywar-0.1.war</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. ''Beispiel: <code>java -Dgrails.env=prod -jar build/libs/mywar-0.1.war</code>''
 +
 
 +
===Unterstützte Java EE-Container===
 +
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
 +
*GlassFish 3
 +
*Resin 4
 +
*JBOSS 6
 +
*Jetty 8
 +
*Oracle Weblogic 12c
 +
*IBM WebSphere 8.0 ([http://docs.grails.org/3.3.9/guide/single.html#supportedJavaEEContainers *])
 +
 
 +
<br />
 +
==Automatisches generieren von Artefakten für eine Domänen-Klasse (Scaffolding)==
 +
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:
  
=== Unterstützte Java EE-Container ===
 
Grails läuft auf jeden Container der [https://de.wikipedia.org/wiki/Servlet Java Servlet] 3.0 (oder darüber) unterstützt. Darunter:
 
* Tomcat 7
 
* GlassFish 3
 
* Resin 4
 
* JBOSS 6
 
* Jetty 8
 
* Oracle Weblogic 12c
 
* IBM WebSphere 8.0
 
  
== Generierung eines Applikations-Bereichs (Scaffolding) ==
+
=[https://docs.grails.org/3.3.9/guide/single.html#conf Konfiguration]=
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änenklasse generieren, darunter:
+
Es mag merkwürdig erscheinen, dass man dieses Thema in einem Framework angeht, welches sich dem "Konvention-über-Konfiguration"-Leitfaden widmet.
* 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>
+
Mit den Standardeinstellungen von Grails kann man tatsächlich eine Anwendung entwickeln, ohne irgendeine Konfiguration vorzunehmen (wie der schnell-Einstieg zeigte), aber es ist wichtig zu lernen, wo und wie man die Konventionen bei Bedarf außer Kraft setzen kann.
ganz einfach erstellt werden.
 
  
= [https://docs.grails.org/3.3.10/guide/single.html#conf Konfiguration] =
 
Obwohl Grails dem Schema "Convention-over-Configuration" folgt, kommt man natürlich nicht ohne Konfigurations-Dateien aus.<br>
 
  
'''Für einen schnelles Hello-World''' (o.Ä.) '''reichen die Standard-Konfigurationen komplett aus'''. 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.10/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-Konfiguration (<code>build.gradle</code>)
 
* Laufzeit-Konfiguration (<code>grails-app/conf/application.yml</code>)
 
  
=== Standard-Variablen ===
+
*Build (<code>build.gradle</code>)
Für die Konfigurations-Dateien stehen folgende Variablen zur Verfügung:
+
*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===
 +
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"
 
|-
 
|-
! Variable !! Beschreibung
+
!Variable!!Beschreibung
 
|-
 
|-
| <code>userHome</code> || Ordner-Pfad beim starten des Servers
+
|<code>userHome</code>||Ordner-Pfad beim starten des Servers
 
|-
 
|-
| <code>grailsHome</code> || Ordner-Pfad indem Grails installiert ist. ''(WENN <code>GRAILS_HOME</code> gesetzt wurde, wird diese genutzt)''
+
|<code>grailsHome</code>||Ordner-Pfad indem Grails installiert ist. ''(WENN <code>GRAILS_HOME</code> gesetzt wurde, wird diese genutzt)''
 
|-
 
|-
| <code>appName</code> || Der Applikations-Name, gelesen von <code>build.gradle</code>
+
|<code>appName</code>||Der Applikations-Name, gelesen von <code>build.gradle</code>
 
|-
 
|-
| <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 />
Beispiel-Benutzung:
+
===Grails 2.0 Konfigurations-Syntax verwenden===
<syntaxhighlight lang="groovy">
+
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.
my.tmp.dir = "${userHome}/.grails/tmp"
+
 
</syntaxhighlight>
+
*ConfigSlurper ist eine Utility-Klasse zum Lesen von Konfigurationsdateien, die in Form von Groovy-Skripten definiert sind.  
 +
**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.
  
=== Konfigurationswerte abrufen ===
+
Zwei Groovy-Konfigurationsdateien sind hierbei verfügbar:
==== innerhalb von Controllern/Tag-Libraries ====
+
{{#tree:
Um innerhalb von Controllern/Tag-Libraries auf die Laufzeit-Konfiguration zuzugreifen, gibt es eine spezielle öffentliche Variable namens <code>grailsApplication</code>  
+
*{"expanded":true} grails-app
vom Typ [http://docs.grails.org/4.0.0.M1/api/grails/core/GrailsApplication.html GrailsApplication].
+
**{"expanded":true} conf
 +
***application.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]===
 +
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>-Variable vom Typen [http://docs.grails.org/4.0.0.M1/api/grails/config/Config.html Config]  
+
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:
bietet die benötigten Funktionen um Werte aus der Konfigurationsdatei zu erhalten.<br>
 
Insbesondere Interessant ist die <code>getProperty</code>-Methode, bei der man auch einen Fallback-Wert angeben kann falls kein Wert in der Konfig angegeben ist:
 
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
 
class MyController {
 
class MyController {
Zeile 167: Zeile 228:
 
         def greeting = grailsApplication.config.getProperty('foo.bar.greeting', "Hello")
 
         def greeting = grailsApplication.config.getProperty('foo.bar.greeting', "Hello")
  
         def message = (recipient.receivedHelloCount >= max) ?
+
         def message = (recipient.receivedHelloCount >= max) ? "Sorry, you've been greeted the max number of times" :  "${greeting}, ${recipient}"
          "Sorry, you've been greeted the max number of times" :  "${greeting}, ${recipient}"
 
        }
 
  
 
         render message
 
         render message
 
     }
 
     }
 
}
 
}
</syntaxhighlight>
+
</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 Fallback-Wert bereitgestellt wird.
 +
 
 +
 
 +
Zu beachten ist, dass die Config-Instanz eine zusammengeführte Konfiguration ist, die auf dem [https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/PropertySource.html PropertySource]-Konzept von Spring basiert und die Konfiguration aus
 +
 
 +
*der Umgebung,
 +
*den Systemeigenschaften und
 +
*der lokalen Anwendungskonfiguration (bspw. <code>application.yml</code>)
 +
 
 +
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">
 +
import grails.core.*
 +
 
 +
class MyService {
 +
    GrailsApplication grailsApplication
  
==== Konfigurationswerte mit Variablen verknüpfen ====
+
    String greeting() {
Zugehörige Konfigurations-Werte kann man zudem mit Springs [https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Value.html @Value]-Annotation mit einer Variable verknüpfen.
+
        def recipient = grailsApplication.config.getProperty('foo.bar.hello')
 +
        return "Hello ${recipient}"
 +
    }
 +
}
 +
</syntaxhighlight><br />
 +
===Konfigurationswerte mit Variablen verknüpfen===
 +
Die [https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Value.html Value]-Annotation von Spring kann dazu verwendet werden, einer Variable zur Laufzeit mit dem jeweiligen Konfigurationswert einzuspeisen
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
 
import org.springframework.beans.factory.annotation.*
 
import org.springframework.beans.factory.annotation.*
Zeile 190: Zeile 272:
 
     }
 
     }
 
}
 
}
 +
</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 />
 +
===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.
 +
 +
Hier ist ein Beispiel meiner zusammengebastelten LogBack-Groovy-Konfiguration:<syntaxhighlight lang="groovy">
 +
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())
 +
}
 +
 +
// 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'
 +
 +
// See http://logback.qos.ch/manual/groovy.html for details on configuration..
 +
def HOME_DIR = "."
 +
 +
// DEFINE Appender
 +
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>
 
</syntaxhighlight>
  
=== Logging ===
+
====Logger-Namen====
Standardmäßig wird die Protokollierung in Grails 3.0 mithilfe des [https://logback.qos.ch/manual/groovy.html Logback]-Frameworks verarbeitet und kann mit der Datei
+
Grails-Artifakte (Controller, Services, ...) wird automatisch eine <code>log</code> Methode injeziert.
unter <code>grails-app/conf/logback.groovy</code> konfiguriert werden.
+
 
 +
*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"
 +
|+
 +
|
 +
!'''Logger Name'''
 +
'''(Grails 3.3.x oder höher)'''
 +
!'''Logger Name'''
 +
'''(Grails 3.2.x oder niedrieger)'''
 +
|-
 +
!<code>BookController.groovy</code> in <code>grails-app/controllers/com/company</code> OHNE <code>[http://docs.groovy-lang.org/latest/html/gapi/groovy/util/logging/Slf4j.html @Slf4j]-Annotation</code>
 +
|com.company.BookController
 +
|grails.app.controllers.com.company.BookController
 +
|-
 +
!<code>BookController.groovy</code> in <code>grails-app/controllers/com/company</code> MIT <code>[http://docs.groovy-lang.org/latest/html/gapi/groovy/util/logging/Slf4j.html @Slf4j]-Annotation</code>
 +
|com.company.BookController
 +
|com.company.BookController
 +
|}
 +
[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 externe Konfigurationsdateien] ====
 
 
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:
* Innerhalb der Runtime-Konfiguration:
+
 
 +
*Innerhalb der Runtime-Konfiguration:
 
<syntaxhighlight lang="yaml">
 
<syntaxhighlight lang="yaml">
 
logging:
 
logging:
 
     config: /Users/me/config/logback.groovy
 
     config: /Users/me/config/logback.groovy
 
</syntaxhighlight>
 
</syntaxhighlight>
* Mithilfe einer Umgebungsvariable:  
+
 
 +
*Mithilfe einer Umgebungsvariable:
 
<syntaxhighlight>
 
<syntaxhighlight>
 
$ export LOGGING_CONFIG=/Users/me/config/logback.groovy
 
$ export LOGGING_CONFIG=/Users/me/config/logback.groovy
 
$ ./gradlew bootRun
 
$ ./gradlew bootRun
 
</syntaxhighlight>
 
</syntaxhighlight>
* Mithilfe einer System-Eigenschaft:  
+
 
 +
*Mithilfe einer System-Eigenschaft:
 
<syntaxhighlight>
 
<syntaxhighlight>
 
$ ./gradlew -Dlogging.config=/Users/me/config/logback.groovy bootRun
 
$ ./gradlew -Dlogging.config=/Users/me/config/logback.groovy bootRun
</syntaxhighlight>
+
</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 Stacktrace verstecken] ====
+
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:
Wenn Grails eine Stack-Trace protokolliert, enthält die Protokollnachricht möglicherweise die Namen und Werte aller Anforderungsparameter  
 
für die aktuelle Anfrage. Parameter, die in dem Protokoll nicht aufgeführt werden sollen, können innerhalb der Konfigurationsvariable  
 
<code>grails.exceptionresolver.params.exclude</code> hinterlegt werden:
 
 
<syntaxhighlight lang="yaml">
 
<syntaxhighlight lang="yaml">
 
grails:
 
grails:
Zeile 226: 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.
  
=== [https://docs.grails.org/3.3.10/guide/single.html#environments Umgebungs-Abhängige Konfigurationswerte] ===
+
'''Der Standardwert ist'''
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.<br>
+
 
In Grails kann man, je nachdem in welcher "Umgebung/Phase" die App gestartet wurde, andere Werte festlegen:
+
*'''<code>true</code>, wenn die Anwendung im Modus DEVELOPMENT läuft, und'''
<syntaxhighlight lang="groovy">
+
*'''<code>false</code> für alle anderen Umgebungen.<br />'''
 +
 
 +
 
 +
===[https://docs.grails.org/3.3.10/guide/single.html#environments Umgebungs-Abhängige Konfigurationswerte]===
 +
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> 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:
 +
<syntaxhighlight lang="yaml">
 +
environments:
 +
    development:
 +
        dataSource:
 +
            dbCreate: create-drop
 +
            url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
 +
    test:
 +
        dataSource:
 +
            dbCreate: update
 +
            url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
 +
    production:
 +
        dataSource:
 +
            dbCreate: update
 +
            url: jdbc:h2:prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
 +
        properties:
 +
          jmxEnabled: true
 +
          initialSize: 5
 +
        ...
 +
</syntaxhighlight>
 +
Der obige Code kann im Groovy-Syntax (<code>application.groovy</code>) wie folgt ausgedrückt werden: (Bemerke, wie die allgemeine Konfiguration auf der obersten Ebene bereitgestellt wird und dann ein Umgebungsblock-pro-Umgebung Einstellungen für die Eigenschaften <code>dbCreate</code> und <code>url</code> der DataSource angeben.)<syntaxhighlight lang="groovy">
 +
dataSource {
 +
    pooled = false
 +
    driverClassName = "org.h2.Driver"
 +
    username = "sa"
 +
    password = ""
 +
}
 
environments {
 
environments {
     production {
+
     development {
 
         dataSource {
 
         dataSource {
             url = "jdbc:mysql://liveip.com/liveDb"
+
            dbCreate = "create-drop"
            //...
+
             url = "jdbc:h2:mem:devDb"
 
         }
 
         }
 
     }
 
     }
 
     test {
 
     test {
 
         dataSource {
 
         dataSource {
             url = "jdbc:mysql://localhost.com/testDb"
+
            dbCreate = "update"
             //...
+
             url = "jdbc:h2:mem:testDb"
 +
        }
 +
    }
 +
    production {
 +
        dataSource {
 +
            dbCreate = "update"
 +
             url = "jdbc:h2:prodDb"
 
         }
 
         }
 
     }
 
     }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Hiermit hat man seine eigene Abschottung zum Testen und muss sich zB. nicht fürchten die Produktion-Datenbank zu vernichten.
 
  
== [http://docs.grails.org/latest/guide/single.html#dataSource DataSource] ==
+
====Befehl in gewünschter Umgebung ausführen====
Weil Grails auf Java aufbaut sollte man ein bisschen Verständnis von JDBC ([https://de.wikipedia.org/wiki/Java_Database_Connectivity Java Database Connectivity],
+
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>
die Datenbankschnittstelle der Java-Plattform) besitzen.<br>
+
 
 +
====Umgebung programmatisch auslesen====
 +
Innerhalb des Codes, z.B. in einem Gant-Skript oder einer Bootstrap-Klasse, kann man die Umgebung mit Hilfe der [http://docs.grails.org/3.3.9/api/grails/util/Environment.html Environment]-Klasse erkennen:<syntaxhighlight lang="groovy">
 +
import grails.util.Environment
  
=== Treiber einfügen ===
+
...
Wenn man eine andere Datenbank als H2 nimmt benötigt man noch einen JDBC-Treiber. Für MySQL wäre dies [http://www.mysql.com/downloads/connector/j/ Connector/J].<br>
+
 
Solche Treiber kommen normalerweise in Form eines Java-Archivs. Am besten fügt man diese Treiber mithilfe eines Maven-Repos in seine dependencies ein: (Gefunden in <code>build.gradle</code>)
+
switch (Environment.current) {
<syntaxhighlight lang="maven">
+
    case Environment.DEVELOPMENT:
 +
        configureForDevelopment()
 +
        break
 +
    case Environment.PRODUCTION:
 +
        configureForProduction()
 +
        break
 +
}
 +
</syntaxhighlight><br />
 +
==[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.
 +
 
 +
===Treiber einfügen===
 +
Wenn man eine andere Datenbank als H2 nimmt benötigt man noch einen passenden JDBC-Treiber. Für MySQL wäre dies [http://www.mysql.com/downloads/connector/j/ Connector/J].<br>Solche Treiber kommen normalerweise in Form eines Java-Archivs. Am besten fügt man diese Treiber jedoch anhand ihres Maven-Repos in seine dependencies ein: (Gefunden in <code>build.gradle</code>)
 +
<syntaxhighlight>
 
dependencies {
 
dependencies {
    ...
 
 
     runtime 'mysql:mysql-connector-java:5.1.29'
 
     runtime 'mysql:mysql-connector-java:5.1.29'
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Wenn man die JAR erfolgreich eingebunden hat kann man sich jetzt mit der Weise bekannt machen wie Grails Datenbanken-Aktionen verwaltet.
+
Wenn man die JAR erfolgreich eingebunden hat (und diese auch erfolgreich aufgelöst wird) kann man sich jetzt mit der Weise bekannt machen wie Grails die Datenbank-Konfigurationen handhabt.
 +
 
 +
===<code>dataSource</code> einrichten===
 +
''In Grails 2.0 wurde die Datei <code>grails-app/conf/DataSource.groovy</code> zum einstellen der Datenbank-Konfigurationen verwendet.''
 +
 
 +
Seit Grails 3.0 muss eine Runtime-Konfigurationsdatei wie die <code>application.yml</code> verwendet werden.<br>
 +
 
 +
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>'''username'''</code>, <code>'''password'''</code> - Login-Daten, um die JDBC-Verbindung aufzubauen
 +
*<code>jndiName</code> - Der Name der JNDI-Ressource für die DataSource
 +
*<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-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>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>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>logSql</code> - Leitet SQL-Logs zum stdout um
 +
*<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>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>properties</code> - Extra Einstellungen für den DatenSource-Bean. (Siehe [http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#Common_Attributes Tomcat-Pool Dokumentation] sowie die dazugehörige [https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/tomcat/jdbc/pool/PoolConfiguration.html Javadoc])
 +
*<code>''jmxExport''</code> - Wenn <code>false</code>, wird die Registrierung von JMX-MBeans für alle DataSources deaktiviert. Standardmäßig werden JMX-MBeans für DataSources mit <code>jmxEnabled = true</code> in den Eigenschaften hinzugefügt.
 +
 
 +
<br />
  
=== Datasource Einrichten ===
+
===<u>Wichtige Notiz zu <code>dbCreate</code> und Datenbank-Migration</u>===
In <code>grails-app/conf/DataSource.groovy</code> wird eingestellt, wie Grails die Domain-Klassen-Objekte speichert.<br>
+
<blockquote>Die Eigenschaft <code>dbCreate</code> der DataSource-Definition ist wichtig, da sie vorgibt, was Grails zur Laufzeit hinsichtlich der automatischen Generierung der Datenbanktabellen aus GORM-Klassen tun soll.
  
Für <code>dataSource</code> stehen folgende Konfigurations-Möglichkeiten zur Verfügung:
+
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.
* <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>'''url'''</code> - Die JDBC-URL der Datenbank
 
* <code>'''dbCreate'''</code> - Gibt an, ob die Datenbank automatisch aus dem Domänen-Modell generiert werden soll: "create-drop", "create", "update" oder "validate".
 
** <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 jedem 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>validate</code> - Warnt vor Änderungen, aber verändert das Datenbank-Schema bei Veränderung '''nicht'''
 
* <code>pooled</code> - Ob man einen Pool von Verbindungen benutzen sollte (Standard: true)
 
* <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>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>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>''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>properties</code> - Extra Einstellungen für den DatenSource-Bean. (Siehe [http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#Common_Attributes Tomcat-Pool Dokumentation] sowie die dazugehörige [https://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/tomcat/jdbc/pool/PoolConfiguration.html Javadoc])
 
* <code>''jmxExport''</code> - Wenn <code>false</code>, wird die Registrierung von JMX-MBeans für alle DataSources deaktiviert. Standardmäßig werden JMX-MBeans für DataSources mit <code>jmxEnabled = true</code> in den Eigenschaften hinzugefügt.
 
  
=== NOTIZ ===
+
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.
Anscheinend wird in neueren Versionen von Grails die <code>dataSource</code> in der Datei <code>grails-app/conf/application.yml</code> im YAML-Format eingestellt. Die Konfigurationsmöglichkeiten bleiben aber anscheinend die gleichen, Beispiel:
+
 
<syntaxhighlight lang="yml">
+
 
dataSource:
+
Grails unterstützt Datenbankmigrationen mit Liquibase oder Flyway (Siehe [https://www.liquibase.org/liquibase-vs-flyway offizieller Vergleich]) über Plugins:
    pooled: true
 
    jmxExport: true
 
    driverClassName: com.mysql.jdbc.Driver
 
    url: "jdbc:mysql://localhost:3306/db_mahlzeit"
 
    dbCreate: "create-drop"
 
    dialect: org.hibernate.dialect.MySQL5InnoDBDialect
 
    username: usr_mahlzeit
 
    password: nesev0_JP
 
</syntaxhighlight>
 
  
=== Typische MySQL Konfiguration ===
+
*[http://plugins.grails.org/plugin/grails/database-migration '''database-migration Plugin (Liquibase)''']
Eine Typische MySQL-Konfiguration wäre:
+
*[http://plugins.grails.org/plugin/saw303/org.grails.plugins%3Agrails-flyway flyway Plugin (Flyway)]
 +
</blockquote><br />
 +
===Typische MySQL Konfiguration===
 +
Eine typische DataSource eines Grails 2.0 Projekts könnte so aussehen:
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
 
dataSource {
 
dataSource {
Zeile 333: Zeile 589:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Komplexere MySQL Konfiguration ===
+
===Komplexere MySQL Konfiguration===
Oder ein Beispiel einer eher komplexeren Konfiguration:
+
Ein Beispiel einer komplexeren DataSource-Konfiguration:
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
 
dataSource {
 
dataSource {
Zeile 411: Zeile 667:
  
  
= [https://docs.grails.org/3.3.9/guide/commandLine.html Eigene Scripts] =
+
==[http://docs.grails.org/3.3.9/guide/single.html#applicationClass Die <code>Application</code>-Klasse]==
Mit dem Befehl <code>$ grails create-script NAME</code> kann man sein eigenes Skript erstellen,
+
Jede neue Grails-Anwendung verfügt über eine Klasse namens <code>Application.groovy</code> innerhalb des Verzeichniss <code>grails-app/init</code>.
welches standardmäßig in <code>src/main/scripts/</code> gespeichert wird.
 
  
== description() ==
+
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 description()-Methode wird für die Ausgabe vom <code>grails help</code> Befehl verwendet, um den Nutzern zu helfen.<br>
+
 
Beispiel vom <code>generate-all</code> Befehl:
+
===Die Application-Klasse ausführen===
<syntaxhighlight lang="groovy">
+
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 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 Kontainerlosen Ansatz zu implementieren.):<syntaxhighlight lang="bash">
 +
$ grails package
 +
$ java -jar build/libs/myapp-0.1.war
 +
</syntaxhighlight>
 +
 
 +
=[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.''
 +
Wenn man den folgenden Befehl eingibt, durchsucht Grails das [https://bintray.com/grails/profiles Profil-Repository] auf der Grundlage des Profils der aktuellen Anwendung. Wenn das Profil für eine Web-Anwendung ist, werden Befehle aus dem Web-Profil und dem Basis-Profil, von dem es erbt, gelesen.<syntaxhighlight>
 +
$ grails <<command name>>
 +
</syntaxhighlight>
 +
 
 +
Es wird zuerst die Anwendung und dann das Profil nach Befehlen durchsucht. Beispiel am Befehl <code>run-app</code>:
 +
 
 +
*<code>PROJECT_HOME/src/main/scripts/run-app.groovy</code>
 +
*<code>[profile]/commands/run-app.groovy</code>
 +
*<code>[profile]/commands/run-app.yml</code><br />
 +
 
 +
==[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>
 +
==description()==
 +
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/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">
 
description( "Generates a controller that performs CRUD operations and the associated views" ) {
 
description( "Generates a controller that performs CRUD operations and the associated views" ) {
 
   usage "grails generate-all <<DOMAIN CLASS>>"
 
   usage "grails generate-all <<DOMAIN CLASS>>"
Zeile 425: Zeile 709:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
==[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.
 +
 +
Beispiel:<syntaxhighlight lang="groovy">
 +
def domain = model(com.foo.Bar)
 +
 +
domain.className == "FooBar"
 +
domain.fullName == "com.foo.FooBar"
 +
domain.packageName == "com.foo"
 +
domain.packagePath == "com/foo"
 +
domain.propertyName == "fooBar"
 +
domain.lowerCaseName == "foo-bar"
 +
</syntaxhighlight>
 +
Darüber hinaus steht die <code>asMap</code>-Methode zur Verfügung, mit der alle Eigenschaften in eine Map verwandelt werden können, die an die <code>render</code>-Methode übergeben wird.
 +
==[https://docs.grails.org/3.3.9/guide/commandLine.html#reusingGrailsScripts Scripts innerhalb eines Scripts aufrufen]==
 +
Grails hat schon von Anfang an eine große Anzahl an Skripts, welche man ganz einfach aufrufen kann.<br>
 +
Der folgende Code ruft den Befehl <code>[https://docs.grails.org/latest/ref/Command%20Line/test-app.html testApp]</code> mit dem Parametern <code>--debug-jvm</code> auf.
 +
<syntaxhighlight lang="groovy">
 +
testApp('--debug-jvm')
 +
</syntaxhighlight>
 +
 +
===[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">
 +
gradle.compileGroovy()
 +
</syntaxhighlight>
 +
 +
===[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:
 +
 +
([https://ant.apache.org/ Ant] ist über [https://grails.org/plugin/grails-ant ein Plugin], dass bei so gut wie jeder Applikation dabei sein sollte, bereits integriert)<syntaxhighlight lang="groovy">
 +
ant.mkdir(dir:"path")
 +
</syntaxhighlight><br />
 +
==Template-Generation + Beispiel-Befehl==
 +
Plugins und Anwendungen, die Aufgaben zur Vorlagenerstellung definieren müssen, können dies mit Hilfe von Skripten tun.
 +
 +
Hierzu sollten die Methoden benutzt werden, die vom geerbeten [https://docs.grails.org/3.3.9/api/org/grails/cli/profile/commands/templates/TemplateRenderer.html TemplateRenderer] bereitgestellt werden.
  
== Beispiel-Befehl ==
 
 
Ein Beispiel anhand des <code>create-script</code>-Befehls:
 
Ein Beispiel anhand des <code>create-script</code>-Befehls:
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
Zeile 445: Zeile 764:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== model() ==
 
Wenn man <code>model()</code> mit einem Class/String/File/Resource-Parameter aufruft erhält man eine neue Instanz von 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.<br>
 
  
== Scripts innerhalb eines Scripts aufrufen ==
+
Wenn ein Skript in einem Plugin oder Profil definiert ist, wird die <code>template(String)</code>-Methode in der Anwendung nach der Vorlage suchen, bevor die von Ihrem Plugin oder Profil bereitgestellte Vorlage verwendet wird. Auf diese Weise können die Benutzer des jeweiligen Plugins oder Profils anpassen, was generiert wird.
Grails hat schon von Anfang an eine große Anzahl an Skripts, welche man ganz einfach aufrufen kann.<br>
+
 
Der folgende Code ruft den Befehl <code>[https://docs.grails.org/latest/ref/Command%20Line/test-app.html testApp]</code> mit dem Parametern <code>--debug-jvm</code> auf.
+
Es ist üblich, den Benutzern auf einfache Weise zu ermöglichen, die Vorlagen aus seinem Plugin oder Profil zu kopieren. Hier ist ein Beispiel dafür, wie das Angular-Scaffolding-Script Vorlagen kopiert.<syntaxhighlight lang="groovy">
 +
templates("angular/**/*").each { Resource r ->
 +
    String path = r.URL.toString().replaceAll(/^.*?META-INF/, "src/main")
 +
    if (path.endsWith('/')) {
 +
        mkdir(path)
 +
    } else {
 +
        File to = new File(path)
 +
        SpringIOUtils.copy(r, to)
 +
        println("Copied ${r.filename} to location ${to.canonicalPath}")
 +
    }
 +
}
 +
</syntaxhighlight><br />
 +
=[https://docs.grails.org/3.3.9/guide/commandLine.html#creatingCustomCommands Grails Befehle]=
 +
 
 +
==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.
 +
 
 +
===Ä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.
 +
 
 +
Befehle, die in Grails 3.1.x oder niedriger erstellt wurden, implementieren standardmäßig die Eigenschaft [https://docs.grails.org/latest/api/grails/dev/commands/ApplicationCommand.html ApplicationCommand], die erfordert, dass der Befehl die folgende Methode implementiert:<syntaxhighlight lang="groovy">
 +
boolean handle(ExecutionContext executionContext)
 +
</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()
 +
</syntaxhighlight>
 +
==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.
 +
 
 +
==Befehl ausführen==
 +
Selbst-geschriebene Befehle können
 +
 
 +
*mithilfe von <code>$ grails run-command NAME</code> oder
 +
*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>
 +
 
 +
aufgerufen werden.
 +
 
 +
 
 +
 
 +
<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'
 +
 
 +
  runtime 'org.grails.plugins:asset-pipeline'
 +
  runtime 'org.grails.plugins:scaffolding'
 +
 
 +
  testCompile 'org.grails:grails-plugin-testing'
 +
  testCompile 'org.grails.plugins:geb'
 +
 
 +
  // Note: It is recommended to update to a more robust driver (Chrome, Firefox etc.)
 +
  testRuntime 'org.seleniumhq.selenium:selenium-htmlunit-driver:2.44.0'
 +
 
 +
  console 'org.grails:grails-console'
 +
}
 +
</syntaxhighlight>
 +
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 {
 +
    imports {
 +
        mavenBom 'org.grails:grails-bom:' + grailsVersion
 +
    }
 +
    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:
 +
 
 +
*<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.
 +
 
 +
*<code>[https://grails.org/plugin/asset-pipeline asset-pipeline]</code> - Das Asset-Pipeline-Plugin ermöglicht die Zusammenstellung von statischen Assets (JavaScript, CSS usw.)
 +
 
 +
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:
 +
 
 +
*<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.
 +
 
 +
*<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.
 +
 
 +
*<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.
 +
 
 +
*<code>[https://github.com/grails/grails-gradle-plugin org.grails.grails-plugin]</code> - Ein Plugin für Gradle zur Erstellung von Grails-Plugins.
 +
 
 +
*<code>org.grails.grails-plugin-publish</code> - Ein Plugin für die Veröffentlichung von Grails-Plugins im zentralen Repository.
 +
 
 +
*<code>org.grails.grails-profile</code> - Ein Plugin zur Verwendung beim Erstellen von Grails-Profilen.
 +
 
 +
*<code>org.grails.grails-profile-publish</code> - Ein Plugin zur Veröffentlichung von Grails-Profilen im zentralen Repository.
 +
 
 +
*<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.
 +
 
 +
 
 +
 
 +
 
 +
=[http://gorm.grails.org/6.1.x/hibernate/manual/#introduction Objekt-Relationales Mapping (ORM)]=
 +
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.
 +
 
 +
 
 +
'''GORM ist die [https://de.wikipedia.org/wiki/Objektrelationale_Abbildung objektrelationale Mapping (ORM)]-Implementierung von Grails.'''
 +
 
 +
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'''.
 +
 
 +
 
 +
'''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).'''
 +
 
 +
''Es gibt auch noch andere Implementation wie "[http://gorm.grails.org/latest/mongodb/manual GORM for MongoDB]".''
 +
 
 +
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.
 +
 
 +
<br />
 +
 
 +
==Versionsverlauf==
 +
Siehe [http://gorm.grails.org/6.1.x/hibernate/manual/#releaseHistory Offizielle Dokumentation]
 +
 
 +
==Upgrade-Hinweise==
 +
Siehe [http://gorm.grails.org/6.1.x/hibernate/manual/#upgradeNotes Offizielle Dokumentation]
 +
<br />
 +
 
 +
==[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:
 +
 
 +
*Folgende Abhängigkeiten in <code>build.gradle</code> einfügen:
 +
<syntaxhighlight lang="groovy">
 +
// build.gradle
 +
dependencies {
 +
    compile "org.grails.plugins:hibernate5:6.1.12"
 +
    compile "org.hibernate:hibernate-ehcache"
 +
}
 +
</syntaxhighlight>
 +
 
 +
*''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">
 +
# gradle.properties
 +
gormVersion=6.1.12.RELEASE
 +
</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">
 +
// build.gradle
 +
configurations.all {
 +
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
 +
        if( details.requested.group == 'org.grails' &&
 +
            details.requested.name.startsWith('grails-datastore')) {
 +
            details.useVersion("6.1.12.RELEASE")
 +
        }
 +
    }
 +
}
 +
dependencies {
 +
    ...
 +
}
 +
</syntaxhighlight>
 +
 
 +
===[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 />
 +
 
 +
==Eine andere Hibernate-Version definieren==
 +
Siehe [http://gorm.grails.org/6.1.x/hibernate/manual/#hibernateVersions Offizielle Dokumentation]<br />
 +
 
 +
==GORM in einer Spring-Boot-Applikation verwenden==
 +
Siehe [http://gorm.grails.org/6.1.x/hibernate/manual/#springBoot Offizielle Dokumentation]<br />
 +
 
 +
==[http://gorm.grails.org/6.1.x/hibernate/manual/#outsideGrails GORM ausserhalb von Grails oder Spring verwenden]==
 +
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">
 +
compile "org.grails:grails-datastore-gorm-hibernate5:6.1.12.RELEASE"
 +
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>
 +
 
 +
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">
 +
import org.grails.orm.hibernate.HibernateDatastore
 +
Map configuration = [
 +
    'hibernate.hbm2ddl.auto':'create-drop',
 +
    '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>
 +
 
 +
 
 +
==Quick-Start Anleitung (Grundlegends CRUD)==
 +
Siehe [http://gorm.grails.org/6.1.x/hibernate/manual/#quickStartGuide Offizielle Dokumentation]
 +
 
 +
<br />
 +
==[http://gorm.grails.org/6.1.x/hibernate/manual/#domainClasses Domain-Modelling in GORM: Einführung]==
 +
 
 +
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.
 +
 
 +
'''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.
 +
 
 +
Betrachte man z.B. die folgende Domänenklasse:
 +
{| 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
 +
 
 +
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)
 +
 
 +
'''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.
 +
 
 +
<br />
 +
==[http://gorm.grails.org/6.1.x/hibernate/manual/#gormAssociation Domain-Modelling in GORM: Einführung Assoziationen]==
 +
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.
 +
 
 +
===Unidirektionales Viele-zu-Eins ohne Kaskadierung===
 +
In diesem Fall haben wir eine '''uni'''direktionale Viele-zu-Eins-Beziehung von <code>Face</code>-to-<code>Nose</code>.
 +
{| 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 {}
 +
</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)...]"
 +
 
 +
Nose myNose = new Nose()
 +
new Face(nose: myNose).save()
 +
 
 +
Face myFace = new Face(nose: myNose).save() // Creating a new and valid Face-Instance and saving it
 +
myFace.setNose(new Nose()) // Updating/Editing it
 +
myFace.save() // Successfully saving new data also means bumping up the internal 'version' property.
 +
</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)
 +
 
 +
myFace.save() // The saving of 'myFace' also triggers the saving of the associated 'nose' property
 +
 
 +
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>
 +
|[[Datei:Grails-gorm-unidirectional many to one-example code2-resulting table view.png|alternativtext=|zentriert]]
 +
|}
 +
 
 +
 
 +
===Bidirektionales Viele-Zu-Eins mithilfe von belongsTo===
 +
Um die obige Beziehung '''bi'''direktional zu gestalten, definieren wir die andere Seite (Nose) wie folgt
 +
{| class="wikitable"
 +
|+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>
 +
|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
 +
 
 +
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>
 +
|[[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
 +
 
 +
myNose.delete() // Results in the error mentioned in the next column
 +
</syntaxhighlight>Jedoch:
 +
|<syntaxhighlight>
 +
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();
 +
 
 +
// Thanks to the bidirectionality, a Nose can also access the Face it's associated to/it belongs to:
 +
println(myNose.face) // "grailstesting.Face : 1"
 +
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.
 +
 
 +
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.
 +
 
 +
Um einfach nur das Kaskadierungsverhalten zu definieren (ohne Rückrefrenz auf den "Eigentümer") kann man folgende Syntax benutzen:
 +
{| 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 {
 +
}
 +
 
 +
class Nose {
 +
    static belongsTo = [face: Face]
 +
    static constraints = {
 +
        face unique:true
 +
    }
 +
}
 +
</syntaxhighlight>
 +
|[[Datei:Grails-GORM-unidirectional one to one-db schema view.png|alternativtext=|zentriert]]
 +
|[[Datei:Grails-GORM-unidirectional one to one-association view.png|alternativtext=|zentriert|215x215px]]
 +
|}
 +
 +
 
 +
===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]
 +
}
 +
 
 +
class Nose {
 +
    Face face
 +
}
 +
</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.
 +
 
 +
 
 +
(Alle Beispiel-Codes vom obigen "Bidirektionale Many-To-One" verhalten sich auch hier gleich)
 +
 
 +
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">
 +
class Face {
 +
    static hasOne = [nose:Nose]
 +
 
 +
    static constraints = {
 +
        nose unique: true
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
 
 +
===[http://gorm.grails.org/6.1.x/hibernate/manual/#_controlling_the_ends_of_the_association Kontrollieren der Enden von Assoziationen]===
 +
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
 +
{| 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
 +
 
 +
    // Bildet die rechts zu sehende, selbst-referenzielle Eins-zu-Eins Assoziation
 +
    static belongsTo = [ supervisor: Person ]
 +
 
 +
    static constraints = {
 +
        supervisor nullable: true
 +
        parent nullable: true
 +
    }
 +
}
 +
</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.
 +
 
 +
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>
 +
{| 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 Person {
 +
    String name
 +
    Person parent
 +
 
 +
    static belongsTo = [ supervisor: Person ]
 +
   
 +
    // Die Eigenschaft "none" wird als umgekehrte Richtung der Zuordnung (oder als "Rückverweis") behandelt.
 +
    static mappedBy = [ supervisor: "none", parent: "none" ]
 +
 
 +
    static constraints = {
 +
        supervisor nullable: true
 +
        parent nullable: true
 +
    }
 +
}
 +
</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
 +
 
 +
 
 +
===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]]
 +
|}
 +
 
 +
Mit dieser Struktur kann man wie folgt einen Beispiel-Datensatz aus einem Buch mit 2 Reviews anlegen:<syntaxhighlight lang="groovy" line="1">
 +
new Book(name: 'Daemon')
 +
    .addToReviews(new Review(quote: 'Daemon does for surfing the Web what Jaws did for swimming in the ocean.', author: 'Chicago Sun-Times'))
 +
    .addToReviews(new Review(quote: 'Daemon is wet-yourself scary, tech-savvy, mind-blowing!', author: 'Paste Magazine'))
 +
    .save()
 +
</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()
 +
}
 +
 
 +
void clearReviews(Book book) {
 +
    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.
 +
 
 +
<u>Standardmäßig konfiguriert GORM eine Kaskadenrichtlinie von</u>
 +
 
 +
*"all" für den Fall, dass eine Entität "zu einer anderen gehört" (belongsTo),
 +
*"save-update" für Szenarien ohne existierende "gehört zu" (belongsTo)-Anweisung
 +
</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>
 +
|[[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]]
 +
|}
 +
 
 +
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">
 +
Book replaceReviews(Serializable idParam, List<Review> newReviews) {
 +
    Book book = Book.where { id == idParam }.join('reviews').get()
 +
    book.reviews.clear()
 +
    newReviews.each { book.addToReviews(it) }
 +
    book.save()
 +
}
 +
</syntaxhighlight><br />
 +
 
 +
===[http://gorm.grails.org/6.1.x/hibernate/manual/#oneToMany Mehr zur "Eins-zu-Viele"-Assoziation mit <code>hasMany</code>]===
 +
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>:
 +
{| 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]]
 +
|}
 +
 
 +
 
 +
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
 +
 
 +
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 = [outboundFlights: Flight, inboundFlights: Flight]
 +
    static mappedBy = [outboundFlights: "departureAirport",
 +
                      inboundFlights: "destinationAirport"]
 +
}
 +
class Flight {
 +
    Airport departureAirport
 +
    Airport destinationAirport
 +
}
 +
</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]]
 +
|}
 +
 
 +
 
 +
===(Bidirektionales) Viele-zu-Viele===
 +
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:
 +
 
 +
{| 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
 +
}
 +
</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]]
 +
|}
 +
 
 +
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.
 +
 
 +
<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>
 +
{| class="wikitable"
 +
|+Beispiel-Code der Bidirektionalen Viele-zu-Viele Beziehung von <code>Author</code> zu <code>Book</code>, inklusive die daraus resultierende Datenbankansicht (IntelliJ).
 +
!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]]
 +
|}
 +
 
 +
 
 +
===Grundlegende Kollektions-Typen  + Einstellen der Join-Table===
 +
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:
 +
{| class="wikitable"
 +
|+Beispiel einer Unidirektionalen Eins-zu-Viele Beziehung von <code>Person</code> zu <code>String</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 Person {
 +
    static hasMany = [nicknames: String]
 +
   
 +
   
 +
    // NUR ZU SCHAUZWECKEN ::: BITTE NICHT VERWENDEN!
 +
    String[] strings
 +
}
 +
</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]]
 +
|}
 +
 
 +
{| class="wikitable"
 +
|+Beispiel-Code einer Unidirektionalen Eins-zu-Viele Beziehung von <code>Person</code> zu <code>String</code>, inklusive die daraus resultierende Datenbankansicht (IntelliJ).
 +
!Beispiel-Code
 +
!Resultat
 +
|-
 +
|<syntaxhighlight lang="groovy">
 +
new Person(
 +
    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]]
 +
|}
 +
 
 +
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>
 +
{| 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]
 +
 
 +
    static mapping = {
 +
      nicknames joinTable: [name: 'bunch_o_nicknames',
 +
                          key: 'person_id',
 +
                          column: 'nickname',
 +
                          type: "text"]
 +
    }
 +
}
 +
</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']
 +
}
 +
 
 +
// grails-app/domain/Address.groovy
 +
class Address {
 +
    String number
 +
    String code
 +
}
 +
</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.
 +
 
 +
;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.
 +
 
 +
 
 +
 
 +
Die gewünschte Vererbungshierarchie kann mithilfe von ORM-DSL-Mapping wie folgt festgelegt werden:<syntaxhighlight lang="groovy">
 +
class Payment {
 +
    int amount
 +
    static mapping = {
 +
        tablePerHierarchy false
 +
        // ODER
 +
        tablePerConcreteClass true
 +
    }
 +
}
 +
 
 +
class CreditCardPayment extends Payment {
 +
    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>
 +
 
 +
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">
 +
def content = Content.list() // list all blog entries, books and podcasts
 +
content = Content.findAllByAuthor('Joe Bloggs') // find all by author
 +
 
 +
def podCasts = PodCast.list() // list only podcasts
 +
</syntaxhighlight>
 +
 
 +
 
 +
==[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]==
 +
Wenn man eine Viele-Beziehung in GORM definiert, handelt es sich standardmäßig um eine <code>java.util.Set</code>.
 +
 
 +
===SortedSet===
 +
<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>:
 +
{| class="wikitable"
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(dataSource: SQL)
 +
|-
 +
|<syntaxhighlight lang="groovy">
 +
class Author {
 +
    SortedSet books
 +
    static hasMany = [books: Book]
 +
}
 +
</syntaxhighlight>
 +
|[[Datei:Grails-GORM-sortedset-db schema view.png|alternativtext=|zentriert]]
 +
|}
 +
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">
 +
class Book implements Comparable {
 +
    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>
 +
|[[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)
 +
 
 +
// Richtiger Weg
 +
def book = new Book(title: 'Misery')
 +
author.addToBooks(book)
 +
author.save()
 +
</syntaxhighlight><br />
 +
===Hibernate Bags (Collection)===
 +
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.
 +
 
 +
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
 +
}
 +
 
 +
def a = new Author()
 +
a.books = ['1590597583':"My Book"]
 +
a.save()
 +
</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.
 +
 
 +
Wenn man eine Map mit Objekten möchte, kann man dies wie folgt tun:
 +
{| class="wikitable"
 +
!Domänenklasse
 +
!IntelliJ's Datenbank-Schema-Ansicht dieser Domänenklasse
 +
(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>
 +
|[[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.
 +
 
 +
'''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">
 +
def book = new Book(title:"New Grails Book")
 +
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>
 +
 
 +
 
 +
==[http://gorm.grails.org/6.1.x/hibernate/manual/#persistenceBasics Persistenz Grundlagen]==
 +
<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>
 +
 
 +
 
 +
===Transaktions-Write-Behind===
 +
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.''
 +
 
 +
 
 +
'''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.
 +
 
 +
 
 +
'''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.
 +
 
 +
===Speichern und Aktualisieren von Objekten===
 +
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>
 +
 
 +
 
 +
===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">
 
<syntaxhighlight lang="groovy">
testApp('--debug-jvm')
+
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>
 
</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]
  
= [https://docs.grails.org/3.3.10/guide/single.html#theWebLayer Die Web-Ebene] =
+
===Controller Traits===
== Controller ==
 
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 ===
+
*[http://gsp.grails.org/latest/api/grails/artefact/gsp/TagLibraryInvoker.html grails.artefact.gsp.TagLibraryInvoker]
Mit dem Befehl <code>$ grails create-controller (PACKET.)KLASSEN-NAME </code> erstellt man das Skelett eines Controllers,
+
*[http://async.grails.org/latest/api/grails/artefact/AsyncController.html grails.artefact.AsyncController]
welches in <code>grails-app/controllers/APP-NAME/DEIN/PACKET/KLASSEN-NAME.groovy</code> gespeichert wird.  
+
*[https://docs.grails.org/3.3.9/api/grails/artefact/controller/RestResponder.html grails.artefact.controller.RestResponder]
''(Dieser Befehl ist nur zur vereinfachten Erstellung, man kann es auch manuell oder mit einer IDE machen)''
+
*[https://docs.grails.org/3.3.9/api/grails/artefact/Controller.html grails.artefact.Controller]
  
=== Aktionen ===
+
===Interceptor Trait===
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>
+
*[https://docs.grails.org/3.3.9/api/grails/artefact/Interceptor.html grails.artefact.Interceptor]
Das folgende Beispiel ist hiermit unter ".../mahlzeit/index" erreichbar:
+
 
 +
===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">
 
<syntaxhighlight lang="groovy">
 
package myapp
 
package myapp
  
 
class MahlzeitController {
 
class MahlzeitController {
 +
    // Standard-Aktion
 
     def index() { }
 
     def index() { }
 +
   
 +
    // Aktion "/list"
 +
    def list {
 +
        //..
 +
        // Controller-Logik
 +
        //..
 +
        return model
 +
    }
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
Ein Controller kann mehrere öffentliche Methoden haben, welche sich (Wie oben beschrieben) jeweils zur einer URI binden.  
 
Ein Controller kann mehrere öffentliche Methoden haben, welche sich (Wie oben beschrieben) jeweils zur einer URI binden.  
  
==== Standard-Aktion ====
+
====[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 nur den Namen des Controllers, wie "/mahlzeit/" anstatt zB. "/mahlzeit/login") ''
+
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.  
versucht Grails eine Standard-Aktion ausfindig zu machen, welche diese Anfrage annimmt.  
+
 
* Wenn es nur eine Aktion ''(aka. Methode)'' gibt, wird diese als Standard-Aktion anerkannt
+
*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
+
*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.
 
Alternativ kann man auch eine eigenen Standard setzten, indem man <code>static defaultAction = "DEINE-METHODE"</code> in den Quellcode des Controllers einfügt.
  
== Scope-Variablen ==
+
<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:
 
"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 ===
+
===[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'''.<br>
+
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.]''
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:
 
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 ===
+
*'''Applikations-Eigenschaften''' zu speichern
Das [http://docs.grails.org/latest/ref/Controllers/session.html session]-Objekt ist eine Instanz  
+
*'''lokale Server-Ressourcen zu laden''' und
von [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSession.html] der Java(EE) Servlet-API.
+
*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).'''
 
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 ===
+
===[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  
+
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.  
von [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html HttpServletRequest] der Java(EE) Servlet API.
 
  
 
Es ist nützlich um:
 
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,
+
*'''Mitgesendete''' [https://de.wikipedia.org/wiki/Liste_der_HTTP-Headerfelder '''Request-Header''']-'''Felderdaten''' zu bekommen
''die das standardmäßige [https://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpServletRequest.html HttpServletRequest]-Objekt nicht hat,'' hinzu:
+
*'''Anfragen-bezogene Attribute zwischen-zu-speichern''' und
* <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
+
*Informationen des aktuellen Klienten zu erhalten
* <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
 
* <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:
+
[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">
 
<syntaxhighlight lang="xml">
 
<book>
 
<book>
Zeile 549: Zeile 3.208:
 
</book>
 
</book>
 
</syntaxhighlight>
 
</syntaxhighlight>
Können wir dies ganz einfach mithilfe des <code>request</code>-Objekts serialisieren:
+
ganz einfach mithilfe des <code>request</code>-Objekts serialisiert und im Code genutzt werden:
 
<syntaxhighlight lang="groovy">
 
<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)
 
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)
Zeile 555: Zeile 3.214:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== ([http://docs.grails.org/latest/ref/Controllers/params.html params]): (Veränderbare,) Mitgesendete CGI Informationen ===
+
===([http://docs.grails.org/latest/ref/Controllers/params.html params]): (Veränderbare,) Mitgesendete CGI Informationen===
Eine veränderbare, mehrdimensionale HashMap von Anforderungsparametern (CGI).<br>
+
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].
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
+
Beispiel:
ausgeben lassen:
 
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
println params.foo
+
def save() {
 +
    def book = new Book(params) // bind request parameters onto properties of book
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>
=== [http://docs.grails.org/latest/ref/Controllers/flash.html flash]: Speicher zwischen 2 Anfragen ===
+
===[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.  
+
[[Datei:PostRedirectGet DoubleSubmitSolution.png|mini|Diagramm eines Anwendungsbeispiels der Post/Redirect/Get-Architektur. Quelle: [[commons:File:PostRedirectGet_DoubleSubmitSolution.png|Wikimedia]]|400x400px]]
Die darin enthaltenen Objekte werden nach Abschluss der nächsten Anforderung automatisch gelöscht.
+
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.  
  
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])
+
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">
und Werte beibehalten, die vom Flash-Objekt abgerufen werden können. Beispiel:
+
def delete() {
<syntaxhighlight lang="groovy">
+
    def b = Book.get(params.id)
class BookController {
+
     if (!b) {
 
+
         flash.message = "User not found for id ${params.id}"
     def index() {
+
         redirect(action:list)
         flash.message = "Welcome!"
 
         redirect(action: 'home')
 
 
     }
 
     }
 +
    ... // remaining code
 +
}
  
    def home() {}
+
def list(){
 +
    // use "flash.message" in the rendered gsp-template or something like that
 
}
 
}
</syntaxhighlight>
+
</syntaxhighlight>Bemerke: Die Namen und Typen die man dem <code>flash</code>-Objekt setzt können willkürlich sein.
== Scope eines Controllers ==
+
<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:
 
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>prototype</code> (Standard) - Für jeden Request wird ein neuer Controller erstellt
* <code>singleton</code> - Für die gesamte Zeit gibt es nur eine Instanz des Controllers (Wird geraten für Aktionen mit Methoden)
+
*<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:
 
Dieser Standard-Wert kann man unter <code>grails-app/conf/application.yml</code> wie folgt abändern:
Zeile 596: Zeile 3.257:
 
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.
 
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)] ==
 
Ein "Model" ist eine Map, die die Ansicht (View) beim Rendern verwendet.
 
* Die Schlüssel der Map korrespondieren mit dem Variablen-Namen über dessen sie von zB. einer <code>.gsp</code>-Datei erreichbar sind.<br>
 
  
Es gibt einige Wege um ein Model zu übergeben:
+
==[https://docs.grails.org/3.3.9/guide/theWebLayer.html#modelsAndViews Model und Ansicht (Model and View)]==
* 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>
+
[[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">
 
<syntaxhighlight lang="groovy">
 
def VIEW_NAME() {
 
def VIEW_NAME() {
Zeile 608: Zeile 3.277:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Eine erweiterte/bessere Weiße wäre es:
+
*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:
* Springs [https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/ModelAndView.html ModelAndView]
 
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
 
import org.springframework.web.servlet.ModelAndView
 
import org.springframework.web.servlet.ModelAndView
Zeile 621: Zeile 3.289:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
zu benutzen.
+
<br />
  
Zu beachten ist, dass bestimmte Variablennamen in Ihrem Modell nicht verwendet werden können:
+
===View-Datei selber selektieren mit der render()-Methode===
* <code>attributes</code>
+
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>)''
* <code>application</code>
 
 
 
=== Views 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">
 
<syntaxhighlight lang="groovy">
 
class BookController {
 
class BookController {
Zeile 642: Zeile 3.305:
 
def show() {
 
def show() {
 
     def map = [book: Book.get(params.id)]
 
     def map = [book: Book.get(params.id)]
     render(view: "display", model: map)
+
    // 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>
 
</syntaxhighlight>
Zeile 648: Zeile 3.314:
 
''(Anmerkung: Wenn Grails keine <code>.gsp</code>-Datei finden kann, sucht es auch nach <code>.jsp</code>-Dateien.)''
 
''(Anmerkung: Wenn Grails keine <code>.gsp</code>-Datei finden kann, sucht es auch nach <code>.jsp</code>-Dateien.)''
  
=== Views für Namespaced-Controller selektieren ===
+
<br />
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
+
===Selektions-Verfahren von Controllern die einem speziellen Namensraum angehören===
* zuerst nach ob die View-Datei unter <code>grails-app/views/business/...</code> zu finden ist
+
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
* und falls nicht sucht es unter dem normalen Namespace <code>grails-app/views/...</code> nach.
+
 
 +
*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:
 
Beispiel:
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
Zeile 658: Zeile 3.327:
  
 
     def humanResources() {
 
     def humanResources() {
         // This will render grails-app/views/business/reporting/humanResources.gsp
+
         // Diese Methode wird "grails-app/views/business/reporting/humanResources.gsp" rendern, falls die Datei existiert.
        // if it exists.
+
         //             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.
         // If grails-app/views/business/reporting/humanResources.gsp does not
 
        // exist the fallback will be grails-app/views/reporting/humanResources.gsp.
 
 
 
         // The namespaced GSP will take precedence over the non-namespaced GSP.
 
  
 
         [numberOfEmployees: 9]
 
         [numberOfEmployees: 9]
Zeile 671: Zeile 3.336:
  
 
     def accountsReceivable() {
 
     def accountsReceivable() {
         // This will render grails-app/views/business/reporting/numberCrunch.gsp
+
         // Diese Methode wird "grails-app/views/business/reporting/numberCrunch.gsp" rendern, falls die Datei existiert.
        // if it exists.
+
         //             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.
         // If grails-app/views/business/reporting/numberCrunch.gsp does not
 
        // exist the fallback will be grails-app/views/reporting/numberCrunch.gsp.
 
 
 
         // The namespaced GSP will take precedence over the non-namespaced GSP.
 
  
 
         render view: 'numberCrunch', model: [numberOfEmployees: 13]
 
         render view: 'numberCrunch', model: [numberOfEmployees: 13]
 
     }
 
     }
 
}
 
}
</syntaxhighlight>
+
</syntaxhighlight><br />
 
+
==[https://docs.grails.org/3.3.9/guide/theWebLayer.html#redirectsAndChaining Weiterleitungen]==
== [https://docs.grails.org/3.3.10/guide/single.html#redirectsAndChaining Redirects] ==
+
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">
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 {
 
class OverviewController {
  
Zeile 701: Zeile 3.359:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
  
 
Auf welche Seite <code>[http://docs.grails.org/latest/ref/Controllers/redirect.html redirect()]</code> umleitet, kann man Grails auf mehrere Weisen mitteilen:
 
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)
+
 
 +
*Der Name der Aktion (Sowie den namens des Controllers, falls sich die Aktion in einem anderen Controller befindet)
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
// Redirects to the "index()"-Action in the "home"-Controller
+
// Weiterleitung zur "index()"-Aktion im "home"-Controller
 
redirect(controller: 'home', action: 'index')
 
redirect(controller: 'home', action: 'index')
 
</syntaxhighlight>
 
</syntaxhighlight>
  
* URI zu einer Resource relativ vom Kontext-Pfad:
+
*URI zu einer Resource relativ vom Kontext-Pfad:
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
// Redirect to an explicit URI
+
// Weiterleitung zu einer explizit definierten URI
 
redirect(uri: "/login.html")
 
redirect(uri: "/login.html")
 
</syntaxhighlight>
 
</syntaxhighlight>
  
* Eine Komplette URL:
+
*Eine Komplette URL:
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
// Redirect to a URL
+
// Weiterleitung zu einer explizit definierten und vollständige URL
 
redirect(url: "http://grails.org")
 
redirect(url: "http://grails.org")
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Parameter können von einer zu der anderen Aktion mithilfe des <code>params</code>-Arguments übergeben werden:
+
*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">
 
<syntaxhighlight lang="groovy">
 
redirect(action: 'myaction', params: [myparam: "myvalue"])
 
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.
+
<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>
  
=== Aktionen aneinander-reihen ===
+
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">
<code>[http://docs.grails.org/3.1.1/ref/Controllers/chain.html chain()]</code> fungiert ähnlich wie <code>redirect()</code>,  
+
redirect(controller: "test", action: "show", fragment: "profile")
indem es auf eine andere Aktion springt und diese ausführt. <br>
+
</syntaxhighlight><br />
Chaining hingegen erlaubt es uns das Model beizubehalten und Model-Daten '''zusätzlich''' zu übergeben.
+
===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:
 
Beispiel:
Zeile 754: Zeile 3.426:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
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:
+
 
 +
 
 +
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">
 
<syntaxhighlight lang="groovy">
 
chain(action: "action1", model: [one: 1], params: [myparam: "param1"])
 
chain(action: "action1", model: [one: 1], params: [myparam: "param1"])
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== 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>
+
==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">
 
<syntaxhighlight lang="groovy">
// renders text to response
+
// Simplen Text rendern
 
render "some text"
 
render "some text"
  
// renders text for a specified content-type/encoding
+
// Rendert Text für einen bestimmten Inhaltstyp/eine bestimmte Codierung
 
render(text: "<xml>some xml</xml>", contentType: "text/xml", encoding: "UTF-8")
 
render(text: "<xml>some xml</xml>", contentType: "text/xml", encoding: "UTF-8")
  
// render a template to the response for the specified model
+
// Rendern einer Vorlage für die Antwort für das angegebene Modell
 
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
 
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
 
render(template: "book", model: [book: theShining])
 
render(template: "book", model: [book: theShining])
  
// render each item in the collection using the specified template
+
// Rendert jedes Elements in der Sammlung unter Verwendung der angegebenen Vorlage
 
render(template: "book", collection: [b1, b2, b3])
 
render(template: "book", collection: [b1, b2, b3])
  
// render a template to the response for the specified bean
+
// eine Vorlage für die Antwort für die angegebene Bean rendern
 
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
 
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
 
render(template: "book", bean: theShining)
 
render(template: "book", bean: theShining)
  
//! render the view with the specified model
+
//! Rendern der Ansicht mit dem angegebenen Modell
 
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
 
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
 
render(view: "viewName", model: [book: theShining])
 
render(view: "viewName", model: [book: theShining])
  
// render the view with the controller as the model
+
// Rendern der Ansicht mit dem Controller als Modell
 
render(view: "viewName")
 
render(view: "viewName")
  
// render some markup to the response
+
// Eigenen Markup zur Response hinzurendern
 
render {
 
render {
 
     div(id: "myDiv", "some text inside the div")
 
     div(id: "myDiv", "some text inside the div")
 
}
 
}
  
// render some XML markup to the response
+
// etwas XML-Markup für die Antwort rendern
 
render(contentType: "text/xml") {
 
render(contentType: "text/xml") {
 
     books {
 
     books {
Zeile 800: Zeile 3.486:
 
}
 
}
  
//! render a JSON ( http://www.json.org ) response with the builder attribute:
+
//! Rendern einer JSON ( http://www.json.org ) Antwort mit dem Builder-Attribut:
 
render(contentType: "application/json") {
 
render(contentType: "application/json") {
 
     book(title: b.title, author: b.author)
 
     book(title: b.title, author: b.author)
 
}
 
}
  
//! render with status code
+
//! Rendern mit Statuscode
 
render(status: 503, text: 'Failed to update book ${b.id}')
 
render(status: 503, text: 'Failed to update book ${b.id}')
  
//! render a file
+
//! Rendern einer Datei
 
render(file: new File(absolutePath), fileName: "book.pdf")
 
render(file: new File(absolutePath), fileName: "book.pdf")
 
</syntaxhighlight>
 
</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.
  
== Data Binding ==
+
Beispiel:<syntaxhighlight lang="groovy">
"Data-Binding" ist das Verfahren, einkommende Request-Parameter an ein Objekt zu binden.
+
package example
* "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.
 
  
 +
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">
 
<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  
 
// Klasse  
 
class Person {
 
class Person {
Zeile 835: Zeile 3.660:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Mit Unterklassen ===
+
 
Dieser Binder kann auch unterklassen verwalten:
+
 
 +
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">
 
<syntaxhighlight lang="groovy">
 
// Klassen
 
// Klassen
Zeile 863: Zeile 3.700:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Mit Arrays ===
+
===Mit Arrays===
 
Auch Arrays sind möglich:
 
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">
 
<syntaxhighlight lang="groovy">
 
// Klassen
 
// Klassen
Zeile 891: Zeile 3.730:
 
assert band.albums[1].title == 'Nursery Cryme'
 
assert band.albums[1].title == 'Nursery Cryme'
 
assert band.albums[1].numberOfTracks == 7
 
assert band.albums[1].numberOfTracks == 7
</syntaxhighlight>
+
</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)
  
=== Mit Maps ===
+
/*
Sowie auch mit Maps:
+
* 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">
 
<syntaxhighlight lang="groovy">
 
// Klassen
 
// Klassen
Zeile 922: Zeile 3.786:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== File-Uploads ==
+
 
<code>.gsp</code>-Code:
+
'''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)'''
<syntaxhighlight lang="grails">
+
 
 +
 
 +
==[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 />
 
Upload Form: <br />
 
<g:uploadForm action="upload">
 
<g:uploadForm action="upload">
Zeile 930: Zeile 3.798:
 
   <input type="submit" />
 
   <input type="submit" />
 
</g:uploadForm>
 
</g:uploadForm>
</syntaxhighlight>
+
</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.
* <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.
+
 
 +
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:
  
Nun gibt es viele Möglichkeiten mit dem File-Upload umzugehen. Eine davon ist mithilfe von
+
*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.
[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.)
 
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
 
def upload() {
 
def upload() {
Zeile 948: Zeile 3.818:
 
     response.sendError(200, 'Done')
 
     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>
 
</syntaxhighlight>
  
=== Upload-Größen-Limit von Grails ===
+
 
 +
===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:
 
Grails Standard-Wert für die maximale Dateigröße liegt bei 128KB. Wenn dieses Limit überschritten wird taucht folgender fehler auf:
 
<syntaxhighlight>
 
<syntaxhighlight>
Zeile 961: Zeile 3.849:
 
     controllers:
 
     controllers:
 
         upload:
 
         upload:
             maxFileSize: 2000000
+
             maxFileSize: 2000000 # Die maximal zulässige Größe für hochgeladene Dateien.
             maxRequestSize: 2100000
+
             maxRequestSize: 2100000 # Die maximal zulässige Größe für Mehrteil-/Formulardatenanfragen.
</syntaxhighlight>
+
</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]
* <code>maxFileSize</code>: Maximale größe einer Datei bei einem Request
+
<br />
* <code>maxRequestSize</code>: Maximale Größe eines gesamten Requests
+
==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.
 +
 
  
== URL-Mappings ==
+
Beispiel-Mapping (URI <code>/product</code> auf die Aktion <code>list()</code> vom <code>ProductController</code> leiten):
In der [http://docs.grails.org/latest/guide/single.html Dokumentation] ist die Konvention des URL-Mappings (standartmäßig) wie folgt eingestellt:
 
<code>/controller/action/id</code>.<br>
 
Dies kann man aber auch sehr leicht für spezielle Seiten in der Datei <code>grails-app/conf/UrlMappings.groovy</code> umändern.
 
Beispiel (URL "/product" auf die Aktion "list()" vom Controller "Product" leiten):
 
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
 
"/product"(controller: "product", action: "list")
 
"/product"(controller: "product", action: "list")
 
</syntaxhighlight>
 
</syntaxhighlight>
Man kann die URL-Mappings auch verschachteln:
+
Alternative-Syntax (Closure/Block-Code):
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
group "/store", {
+
"/product" {
     group "/product", {
+
     controller = "product"
        "/$id"(controller:"product")
+
    action = "list"
    }
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
+
Beispiel-Mapping (URI <code>/product</code> auf die Standard-Aktion vom <code>ProductController</code> leiten):
=== (Explizites) REST-Mapping ===
 
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
"/books"(resources:'book')
+
"/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>
ist gleich wie
 
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
get "/books"(controller:"book", action:"index")
+
// Nested Grouping
get "/books/create"(controller:"book", action:"create")
+
group "/store", {
post "/books"(controller:"book", action:"save")
+
    group "/product", {
get "/books/$id"(controller:"book", action:"show")
+
        "/$id"(controller:"product")
get "/books/$id/edit"(controller:"book", action:"edit")
+
    }
put "/books/$id"(controller:"book", action:"update")
+
}
delete "/books/$id"(controller:"book", action:"delete")
+
</syntaxhighlight><br />
</syntaxhighlight>
+
===[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.
 
 
<syntaxhighlight lang="groovy">
 
// Results in /books/1/authors/2
 
<g:link controller="author" action="show" method="GET" params="[bookId:1]" id="2">The Author</g:link>
 
</syntaxhighlight>
 
  
=== Redirects ===
+
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">
<syntaxhighlight lang="groovy">
 
 
"/viewBooks"(redirect: '/books/list')
 
"/viewBooks"(redirect: '/books/list')
 
"/viewAuthors"(redirect: [controller: 'author', action: 'list'])
 
"/viewAuthors"(redirect: [controller: 'author', action: 'list'])
Zeile 1.013: Zeile 3.897:
 
</syntaxhighlight>
 
</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] ===
+
===[https://docs.grails.org/3.3.10/guide/single.html#embeddedVariables Integrierte Variablen im Mapping]===
<syntaxhighlight lang="groovy">
+
Im vorigen Abschnitt wurde gezeigt, wie einfache URLs mit '''konkreten "Token"''' abgebildet werden können.
"/product/$id"(controller: "product")
+
 
 +
*'''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>
 
</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:
+
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">
 
<syntaxhighlight lang="groovy">
 
class ProductController {
 
class ProductController {
Zeile 1.030: Zeile 3.926:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==== Optionale Variablen ====
+
====[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.
 
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:
 
Beispiel-Mapping:
Zeile 1.037: Zeile 3.933:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Das obrige Beispiel-Mapping schlägt bei allen folgenden URLs zu:
+
Das obige Beispiel-Mapping schlägt bei allen folgenden URLs zu:
<syntaxhighlight lang="groovy">
+
<syntaxhighlight lang="text">
 
/graemerocher/2007/01/10/my_funky_blog_entry
 
/graemerocher/2007/01/10/my_funky_blog_entry
 
/graemerocher/2007/01/10
 
/graemerocher/2007/01/10
Zeile 1.046: Zeile 3.942:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==== Optionale File-Extension ====
+
====[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">
Zeile 1.058: Zeile 3.954:
 
</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">
Zeile 1.066: Zeile 3.962:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
 
+
====Dynamisch aufgelöste Mapping-Variablen====
 
 
==== Dynamisch aufgelöste Variablen ====
 
 
Die oben-genannten, selbst setzbaren statischen Variablen sind sehr nützlich. <br>
 
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.  
+
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.
Dies kann man erzielen, indem man einen eigenen Block zur Variable hinzufügt.
 
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
 
"/holiday/win" {
 
"/holiday/win" {
Zeile 1.080: Zeile 3.973:
 
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".
 
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) ====
+
<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:
 
Nehmen wir folgendes Beispiel:
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
Zeile 1.110: Zeile 4.004:
 
     }
 
     }
 
}
 
}
</syntaxhighlight>
+
</syntaxhighlight><br />
 
+
===[https://docs.grails.org/3.3.9/guide/theWebLayer.html#mappingToResponseCodes Error-Code Mapping]===
=== [https://docs.grails.org/3.3.10/guide/single.html#mappingToResponseCodes Error-Codes] ===
+
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:
Grails lässt uns zudem jeglichen HTTP-Fehlercode auf eine eigene Aktion (Seite) mappen:
 
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
 
"403"(controller: "errors", action: "forbidden")
 
"403"(controller: "errors", action: "forbidden")
Zeile 1.123: Zeile 4.016:
 
<syntaxhighlight lang="groovy">
 
<syntaxhighlight lang="groovy">
 
"500"(controller: "errors", action: "illegalArgument", exception: IllegalArgumentException)
 
"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>
 +
 +
===[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
 +
 +
*die Kopfzeilen der eingehenden Anfragen manipulieren:
 +
<syntaxhighlight lang="groovy">
 +
void testJavascriptOutput() {
 +
    def controller = new TestController()
 +
    controller.request.addHeader "Accept",
 +
              "text/javascript, text/html, application/xml, text/xml, */*"
 +
 +
    controller.testAction()
 +
    assertEquals "alert('hello')", controller.response.contentAsString
 +
}
 +
</syntaxhighlight>
 +
 +
*oder den <code>format</code>-Parameter der Anfrage setzen, um einen ähnlichen Effekt auszulösen:
 +
<syntaxhighlight lang="groovy">
 +
void testJavascriptOutput() {
 +
    def controller = new TestController()
 +
    controller.params.format = 'js'
 +
 +
    controller.testAction()
 +
    assertEquals "alert('hello')", controller.response.contentAsString
 +
}
 
</syntaxhighlight>
 
</syntaxhighlight>

Aktuelle Version vom 21. Oktober 2020, 15:30 Uhr

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.


Inhaltsverzeichnis