Welche Klassen sind im Classpath während des Buildvorgangs?
jre11 ./gradlew buildEnvironment
jre11 ist ein kurzes Shellskript welches mir die Java Umgebung in der Version 11 für das Programm zur Verfügung stellt und ist daher optional.
Welche Klassen werden in einer bestimmten Konfiguration geladen?
Gradle bindet je nach Aufgabe unterschiedliche Klassen ein. Welche sind diese? Das ist zum Beispiel die Frage die man sich stellt, wenn die Ausführung des Code Generators von Jooq unter JDK 11 nicht funktioniert.
Mit –configuration kann man den Dependency Tree auf eine Konfiguration einschränken. Hier ist es also die jooqRuntime die uns interressiert.
Fügt man als Dependency spring-boot-starter-jooq ein, dann werden alle Abhängigeiten sowie Jooq als Abhängigkeiten eingefügt. Soweit so gut. Aber was ist, wenn man eine neuere Version von Jooq (zum Beispiel aufgrund eines Bugs) verwenden möchte?
Override Version
Unter Gradle kann man mit ext[jooq.version] die zu verwendende Version vorgeben.
// Override supported Jooq version 3.11.9
ext['jooq.version'] = '3.11.11'
Nun wird die aktuelle Version von Jooq 3.11.11 verwendet.
Das Google Container Tool JIB ist ein Werkzeug um einfach aus einer Anwendung ein Docker Image zu erzeugen. Docker Images sind im Prinzip auch nur Archive, so dass man diese auch ohne Docker bauen kann. Und genau tut das Tool JIB von Google. Es gibt jeweils ein Plugin für Maven und für Gradle. In diesem Beispiel konzentriere ich mich auf die Gradle Variante von JIB.
Setup
Für das JIB Plugin muss Gradle in mindestes der Version 4.6 vorhanden sein. Die aktuelle Version 1.0.2 wird wie gewohnt eingefügt.
plugins {
id 'com.google.cloud.tools.jib' version '1.0.2'
}
Der Erste Build
Ein beherzigtes gradle jib erstellt uns ein Docker Image der Anwendung.
gradle jib
Den Default Server ändern
Benutzt eine Anwendung den TomCat, dann muss man das Basis Image ändern. Der Default nimmt JIB eine auf Jetty basierendes Distroless Image. Hier nun ein Beispiel für die Verwendung von TomCat 8.5, ein auf alpine basieredes Image.
jib {
from.image = 'tomcat:8.5-jre8-alpine'
// ROOT muss angepasst werden
container.appRoot = '/usr/local/tomcat/webapps/ROOT'
}
Alternative
Mit Source 2 Image (S2I) von OpenShift steht hierzu auch eine Alternative zur Verfügung. Diese habe ich aber noch nicht evaluiert. Die Quellen findet man hier: https://github.com/openshift/source-to-image
Um neue Feature zu testen, kann man auf die Milestone Repositories zurückgreifen. Da das Spring Boot Framework über ein Plugin eingebunden wird, muss das dem PluginManagement mit geteilt werden. Dazu muss man in der settings.gradle das Snapshot und/oder Milestone Repository hinzufügen. Ansonsten wird Spring Boot 2.2.0M1 Version von gradle nicht gefunden.
Plugin
Folgende Änderungen an der settings.gradle Datei sind nötig, um die Milestone Variante zu finden…
Beim Starten eines Queries wird normalerweise das Logo von Jooq ausgegeben. Das ist auch nicht weiter schlimm, es ist nur beim Debuggen der Anwendung manchmal ein wenig störend, weil es viel Platz auf der Konsole einnimmt.
Dank geht an dieser Stelle an Lukas Eder für dieses tolle Werkzeug.
Wie kann nun das Logo abgeschaltet werden? Dazu muss nur die Property org.jooq.no-logo auf true gesetzt werden. Ich löse das in dem ich eine Klasse JooqConfig mit einem Bean erstelle. Die Komponente wird so beim Component Scan gefunden und die Property gesetzt, sodass beim Starten kein Logo angezeigt wird.
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class JooqConfig {
@Bean
private void disableLogo() {
System.setProperty("org.jooq.no-logo", "true");
}
}
Credentials auslagern
Neben der Möglichkeit die Credentials mit dem Gradle Plugin Credentials zu verwalten, kann man auch zum Beispiel dieses in eine weitere Datei auszulagern. Hierfür bietet sich YAML an.
Hier die jooq.yaml
---
jdbc:
password: secret
username: usr
In dem Buildscript jooq.gradle lesen wir die Daten aus dem YAML ein. Das SnakeYaml nutze ich hierfür um die cfg zu definieren.
Mit gradle publish kann ich ein Artefakt auf das Nexus Repository schieben, um es dort für andere Anwednungen vorzuhalten. Dazu binde ich per apply from: ein weiteres Skript ein, dass die Aufgabe erledigt.
// Include Gradle Skript um auf ein Nexus Repository zu deployen.
apply from: 'publishToNexus.gradle'
Das Skript publishToNexus.gradle sieht wie folgt aus:
Es liegt in der Natur der Sache, dass das Plugin die Creditials benötigt. Es muss sich ja gegenüber dem Repository Authentifizieren. Nun ist es aber keine gute Idee das Skript so im SCM abzulegen, da jetzt jedem, der Zugriff auf die Sourcen hat, es möglich ist auch auf das Nexus Repository Zugriff zu erlangen.
Credentials Plugin für Gradle
Das Gradle Credentials Plugin löst genau dieses Problem, indem es eine vershlüsselte Version ablegt und dieses auf Anfrage dem Skript zur Verfügung stellt. Dazu muss man nur die Zeile wo das Nexus Passwort festgelegt wird abändern.
def nexusPassword = credentials."${nexusUser}"
Das Password bekannt machen
Damit wir jetzt auf das Passwort zugreifen können, muss dieses gesetzt werden. Mit dem Task addCredentials kann ein Value für ein Key, hier der Benutzername bestehend aus der Emailaddresse, gesetzt werden.
Im allgemeinen funktioniert die Spring Tool Suite 4 (Version 4.0.2) unter dem aktuellen Java 11 LTS. Leider ist die Erkennung Java Versionen anscheinend sehr fragil, da zum Beispiel die aktuelle Version 11.0.1 nicht erkannt wird.
Dadurch funktioniert das Gradle Tooling (Eclipse Buildship Plugin) nicht und es wird das öffnen eines Gradle Projektes mit einer Fehlermeldung der Editoren quittiert.
An internal error occurred during: "Initializing Java Tooling".
Could not determine java version from '11.0.1'.
Bestimmung der JVM
Unter Arch Linux oder Manjaro werden die verschiedenen Java Versionen parallel unterhalb /usr/lib/jvm abgelegt. Mit dem kleinen Hilfsprogramm archlinux-java kann die Default JVM angezeigt und bestimmt werden. Ist diese nun zum Beispiel auf die aktuelle Version 11 eingestellt, dann läuft auch Eclipse bzw. die RCP und somit auch die Spring Tool Suite unter der JVM. Abhilfe schafft die .ini Datei im Root-Verzeichnis. Hier lässt sich u.a. die zu verwendende JVM angegeben.
[sp@Laptop ~]$ sudo archlinux-java status
Available Java environments:
java-11-openjdk (default)
java-8-graal
java-8-openjdk
Der Parameter vm bestimmt den Pfad zur JVM. Dieser muss unbedingt vor dem Parameter vm-args angegeben werden!
Nun fügen sich alle Puzzelteile zusammen und es läßt sich unter dem aktuellen LTS von Java, dem JDK 11, ein Beispiel mit Hibernate mit SQLite Datenbank, Jooq und Spring Boot 2.1 unter der Verwendung von Gradle als Buildsystem umsetzen. Bis hierhin gab es einige Baustellen, die im Zuge der Modularisierung (Projekt Jigsaw), noch zu beseitigen waren. Jetzt ist eine Umsetzung mit den aktuellsten Versionen möglich und dieses möchte ich hier vorstellen.
Hier die benötigten Komponenten im Einzelnen:
Spring Boot 2.1
Spring Data JPA (spring-boot-starter-data-jpa)
Jooq 3.11.4 (im Spring Boot Starter definiert)
Gradle 5.0-RC4
sqlite-dialect (Hibernate Dialekt)
sqlite-jdbc (Treiber)
Die Demo
Die Anwendung erstellt zwei Invocation Objekte und persistiert sie mit JPA (Hibernate) in der Datenbank. Im Anschluss wird mit Hilfe von Jooq die Tabelle aus SQLite Datenbank wieder gelesen und in Tabellenform auf der Konsole ausgegeben. Das Ganze passiert in der Klasse InitDatabase, die das CommandLineRunner Interface implemenmtiert und somit beim Start der Anwendung ausgeführt wird. Die SQLite Datenbank legt die Daten in der Datei application.db ab. Eine initiale Version ist im Git Repository vorhanden (s.u.).
Ausführen der Anwendung
Der Build wird wie immer mit dem Dreisatz git clone, cd, ./gradlew clean build bootRun angestoßen.
git clone https://mrpeacockgit.duckdns.org:443/Public/spring-hibernate-jooq-sqlite-demo.git
cd spring-hibernate-jooq-sqlite-demo/
./gradlew clean build bootRun
Hier sehen wir den schön formatierten Output von Jooq auf der Konsole.
ACHTUNG: Es muss der Gradle Wrapper unter JDK 11 ausgeführt werden, weil Java 11 als Source- und TargetCompatibility angegeben ist. Wird Gradle z.B. unter Java 8 ausgeführt, dann bricht Gradle den Buildvorgang mit > Could not target platform: ‘Java SE 11’ using tool chain: ‘JDK 8 (1.8)’ ab. Hat man keine JDK 11 zur Hand, dann kann Source und Target auf Java 8 reduziert werden, da keine Sprachfeatures von Java 11 genutzt werden.
Import in Eclipse
Damit in Eclipse die Anwendung fehlerfrei ist, müssen 2 Schritte ausgeführt werden.
Generierung des Java Codes. Es muss die Generierung der Java Quelltexte, aus dem gegebenen Datenbankschema mit Hilfe des Jooq Code Generators, durchgeführt werden. Der Task generateSpringhibernatesqliteJooqSchemaSource wird von dem Jooq Plugin bereitgestellt.
Damit die statischen Importe gefunden werden, müssen sie auch im Eclipse Buildpath vorhanden sein. Dazu muss einmalig der Task eclipse ausgeführt werden.
Jetzt sollte das Projekt fehlerfrei im Eclipse sein.
Die Entität
Die Entity die hier verwendet wird, ist der Demo bedingt, sehr einfach gehalten. Wie immer verwende ich das Project Lombok, um den Code sauber von Boilerplate Code zu halten.
@Entity
@Data
public class Invocation {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
}
GenerationType
Der GenerationType muss hier auf AUTO stehen, ansonsten wird der Start der Anwendung mit einer Exception quittiert.
Caused by: org.sqlite.SQLiteException: [SQLITE_CONSTRAINT] Abort due to constraint violation (NOT NULL constraint failed: invocation.id)
Hibernate Schema Erzeugung / Jooq Code Generation
Hier haben wir ein Henne-/Ei-Problem! Der Code Generator von Jooq benötigt eine Datenbank aus der er das Schema ausliest, um den Code zu erzeugen. Im Quelltext wird aber bereits per Import auf generierten Code zugegriffen, was zu einer Fehlermeldung beim Kompilieren während des Builds führen würde. Da die Code Generierung vor der Kompilierung erfolgt, habe ich eine leere Datenbank mit in das Git Repository aufgenommen. Somit kann die Anwendung über den Gradle Build gebaut und auch ausgeführt werden.
Problem der automatischen Schema Generierung
Anhand des oben beschriebenen Henne-/Ei-Problems, sollte eigentlich jedem schnell klar werden, dass das keine Lösung für große Anwendungen ist. Es sollte hier besser auf andere Lösungsansätze ausgewichen werden. Z.B. Flyway als Datenbankschemamigrationstool oder einfach die Verwendung von Spring Boot Bordmitteln (Spring liest schema.sql ein und verarbeitet die SQL Anweisungen).
Jooq Konfiguration
Für das Erstellen der Konfiguration Jooq Code Generator wird das Plugin von Etienne Struder verwendet. Hier in den Beispiel ist nur eine Grundkonfiguration vorgenommen worden. Alle Einstellungsmöglichkeiten die möglich sind, lassen sich über das XSD für Jooq Code Generator erfahren. Das lesen und verstehen des recht umfangreichen XSD ist nicht einfach. Man kann aber z.B. über Visual XSD sich das XSD visualisieren lassen und so den Aufbau schneller verstehen.
Autovervollständigung in Eclipse
Bei der Eingabe in Eclipse werden nicht automatisch die statischen Imports für die generierten Tabellen von Jooq angezeigt. Hier muss man in Eclipse erst den Umweg über eine nicht statischen Import nehmen, um ihn dann per STRG + 1 in einen statischen Import zu überführen.
Beispiel
In der Klasse InitDatabase die den CommadLineRunner implementiert, möchten wir alle Invocations aus der Datenbank listen.
bekannt. Damit die Autovervollständigung funktioniert, muss der Import vorhanden sein. Das heißt erst nach dem man den Import eingefügt hat, lässt sich mit STRG + SPACE das Code-Fragment .from(INVOCATION) einfügen. Das ist nicht sehr effektiv, da man immer erst den Import einfügen muss.
Einstellung der Favoriten
Der Generator von Jooq ist so konfiguriert, dass die Tabelle aus der Datenbank im Java Package de.Tables.* gelistet werden. Die Quelltexte werden außerhalb von main in src/db gespeichert, so dass jederzeit durch löschen von dem Verzeichnis src/db die generierten Quelltext sauber neu erstellt werden können, ohne das alte Artefakte noch verhanden sind.
Man kann Eclipse anweisen, bestimmte static Members anzuzeigen, auch wenn das Import noch fehlt. Und genau das führt hier zum Ziel und erleichtert die Eingabe von Jooq Queries mit der hervorragenden DSL enorm.
In den Präferenzen von Eclipse wählen Sie java –> Editor –> Content Assist –> Favorites und erstellen einen neuen Typen. Hier ist es *db.Tables.**.
Jetzt kann die Eingabe vervollständigt werden, ohne das der Import zuvor vorhanden war
In diesem Beitrag möchte ich auf die Änderungen der bevorstehenden Release von Gradle 5 eingehen. Aktuell ist das 3. Release Candidate veröffentlicht.
Update des Wrappers
Der Wrapper läßt sich wie immer mit wrapper –gradle-version updaten. Etwas verwirrend ist hier das Versionsschema. Es ist mit GIT v5.0.0-RC3 getagt, aber die Version heißt 5.0-rc-3!
./gradlew wrapper --gradle-version=5.0-rc-3
Was gibt es neues in Gradle 5.0
Java 11 Support
Nun kann man endlich Gradle mit Java 11 verwenden. Mit Versionen kleiner 5.0 konnten in einer Java 11 Umgebung keine Builds ausgeführt werden. Es gab diverse Fehlermeldungen.
$ ./gradlew -version
------------------------------------------------------------
Gradle 5.0-rc-3
------------------------------------------------------------
Build time: 2018-11-14 16:01:47 UTC
Revision: 63f11c722124617f7cbe2f95ad5a5e045b8b42f6
Kotlin DSL: 1.0.3
Kotlin: 1.3.0
Groovy: 2.5.3
Ant: Apache Ant(TM) version 1.9.13 compiled on July 10 2018
JVM: 11.0.1 (Oracle Corporation 11.0.1+13)
OS: Linux 4.19.1-1-MANJARO amd64
Was wurde deprecated
Annotation Prozessoren auf dem Klassenpfad
Verwendet man Project Lombok, dann läuft man potenziell Gefahr auf diese Meldung zu stoßen, wenn man einen build mit Warnhinweisen anstößt.
$ ./gradlew --warn --warning-mode=all build
Detecting annotation processors on the compile classpath has been deprecated. Gradle 5.0 will ignore annotation processors on the compile classpath. The following annotation processors were detected on the compile classpath: 'lombok.launch.AnnotationProcessorHider$AnnotationProcessor' and 'lombok.launch.AnnotationProcessorHider$ClaimingProcessor'. Please add them to the annotation processor path instead. If you did not intend to use annotation processors, you can use the '-proc:none' compiler argument to ignore them.
Gradle möchte ab Version 4, dass ein Annotation Processor in der Konfiguration annotationProcessor hinterlegt wird. Also muss in den Dependencies compileOnly und annotationProcessor wie folgt angegeben werden:
// Project Lombok
// Since Gradle warns if an AnnotationProcessor is found on classpath, put
// it into annotationProcessor directive
compileOnly('org.projectlombok:lombok')
annotationProcessor('org.projectlombok:lombok')