Core API revision

Java 8 basierend

Die gesamte Codebasis wird seit 5.0 auf Java 8 umgestellt. Dieses wurde nun weiter vorangetrieben. Teilweise jetzt erst wirklich auf Java 8 umgestellt.

Java 8 API Typen

Rückgabetypen wurden Stream, Duration, Instant, Executable und so weiter eingeführt.

Interfaces mit Defaultmethoden

Interfaces nutzen nun Default Methoden, um neue Funktionen bereitzustellen ohne dabei die Rückwärtskompatabilität zu brechen.

Nullability

In der gesamten Codebasis werden alle Methoden mit @NonNull oder @Nullable deklariert. Dadurch können die IDEs wie Eclipse und IntelliJ die Interaktion mit dem Spring Framework validieren. Viele der vorhandenen Null Checks konnten somit in dem Spring Framework entfernt werden.

Eigene APIs die auf Spring Framework Rückgabewerten basieren sollten nun auch von den Annotationen Gebrauch machen und somit im allgemeinen den Code von weiteren Null Checks befreien.

Performance Tuning

Component Scanning

Das Scannen des gesammten Classpath beim Programmstart kann viel Zeit in Anspruch nehmen. Dabei werden alle Klassen nach der Annotation @ComponentScan durchsucht. Im allgemeinen wird man die Startupzeit durch Angabe eines base packages verkürzen, da nicht mehr alle Klassen durchsucht werden müssen. Alternativ kann ein Scanning auch komplett verhindert werden, in dem man alle Komponentenklassen aufzählt.

Seit Spring Framework 5 kann man einen Annotationsprozessor zur Kompilierzeit verwenden (spring-context-indexer). Dieser erstellt eine META-INF/spring.components per JAR. Diese wird automatisch zur Laufzeit ausgewertet.

Component Model

Am effizientesten werden Komponenten in einer reinen Funktionalen Methode registriert. Dadurch entfällt das Component Scanning und es wird auch kein Reflection für die Factory Methoden verwendet. Ferner werden auch keine Annotationśkonfigurationen (Annotationpostprocessor) verwendet.

ProxyBeanMethods

Mit @Configuration(proxyBeanMethos=false) kann nun verhindert werden dass zur Laufzeit CGLIB Subklassen erzeugt werden. Dieses wird für die Imageerstellung mit der GraalVM benötigt.

Nachteil: Kein Abfangen von Cross @Bean Calls mehr möglich!

3rd Party Libs

Spring Boot Data unterstützen spring.data.jpa.repositories.bootstrap-mode=deferred. Damit werden der ORM Hibernate lazy initialisiert. Das ist ein nicht unerheblicher Anteil der Startupzeit einer Spring Anwendung. Die Initialisierung wird nun bei dem Ersten Zugriff durchgeführt.

Hibernate wurde auf 5.4.5 aktualisiert, welches interne Performance Steigerungen und weniger Speicherverbrauch zur Folge hat. Desweiteren wurde der Umgang mit Bytecode verbessert (Voraussetzung für lazy loading).

Jackson wurde auf mindesten 2.9.7 angehoben. Auch hier wurde die Startupzeit sowie die Responsezeit beim parsen von JSON verbessert.

Nur durch die Anhebung des Spring / Spring Boot Frameworks lassen sich Performancesteigerungen also erzielen.

GraalVM

Die Verwendung der GraalVM ist bereits seit Spring Framework 5.1 in Vorbereitung. Es werden unnötige interne Reflection vermieden. Nun ist in der Zwischenzeit set Spring Framework 5.1 das 19 GA von GraalVM erschienen. Spring Framework 5.2 kann mit native Image verwendet werden, aber es müssen die explizite Reflection Konfiguration und benötigte Kommandozeilenparameter angegeben werden damit ein Image erzeugt werden kann. Ziel der Entwicklung für Spring Framework 5.3 ist ein automatisches Setup (ootb) der Reflection Einstellungen durch eine Custom Graal Konfiguration zu ermöglichen.

DefaultNamingStrategy von Hibernate

Per default werden die Tabellen nach den Klassennamen und die Columns nach den Feldern benannt. Manchmal stört es einen oder es passt nicht zur der restlichen Struktur in der Datenbank.

Eine Custom NamingStrategy definieren

In diesem Beipsiel hat die Anwendung Tabellen die Über Hibernate verwaltet werden, sowie Tabellen die losgelöst davon im Schema vorhanden sind. Um diese Gruppen von Tabellen sauber zu trennen, kann man z.B. ein Prefix vor jeden Tabellenname einfügen und schon lassen sich schnell die Tabellen unterscheiden.

Tabellenname über @Table festelegen

Über die @Table Annoatation kann über den Parameter name der Tabellenname festgelegt werden. Dieses bietet natürlich größtmögliche flexibilität ist aber aufwändig, da jede Entität das angepasst werden muss.

@Table(name="hibernate_user")

Tabellenname über eine NamingStrategy festlegen

Wie oben beschrieben, wird es in den meisten Fällen nicht angebracht sein alles von Hand einzufügen. In Hibernate gibt es für den Zweck 2 Strategien, um die Generierung der Namen der Tabellen, Felder und Objekten zu beeinflussen. In diesem Beispiel interessiert uns die PhysicalnamingStrategy. Dazu implementieren wir das Interface PhysicalnamingStrategy wie folgt:

/**
 * Adds a prefix "hibernate_" to all tablenames.
 * @author sascha
 *
 */
public class PrefixNamingStrategy implements PhysicalNamingStrategy {

    @Override
    public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        return name;
    }

    @Override
    public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        return name;
    }

    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        return stringToIdentifier("hibernate_" + name);
    }

    @Override
    public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        return name;
    }

    @Override
    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        return name;
    }

    /**
     * String to Identifier
     */
    private Identifier stringToIdentifier(String name) {
        return Identifier.toIdentifier(name);
    }

Als Prefix habe ich hier hibernate_ hinzugefügt. Alle anderen Bezeichnungen werden 1:1 übernommen.

Einbindung über eine Bean

Damit die Strategy auch angewandt wird, muss diese gesetzt werden. Das kann man über die Properties oder eine Bean machen. Hier zeige ich kurz die Bean Variante:

@Bean
public PhysicalNamingStrategy physicalNamingStrategy() {
    return new PrefixNamingStrategy();
}

Wer hat sich nicht schon Mal gefragt, was die Blackbox Hibernate im Hintergrund so alles macht?

Statistiken

Statistiken von Hibernate

Hibernate umfangreiche Statistiken für uns erstellen, um zum Beispiel besser zur verstehen warum es an der einen oder anderen Stelle klemmt. Die automatische Erstellung der Statistiken im Hintergrund nagt natürlich an der Performance der Anwendung und sollte daher nur in der Entwicklung verwendet werden. Zu Testzwecken kann das Feature gegebenenfalls auch auf einem PreProduktion System aktiviert werden, damit man eine realere Umgebung Simulieren kann.

Um das Feature zu aktivieren muss man generate_statistics auf true setzen. Dieses kann über die persistence.xml erledigt werden.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
    ...
    <persistence-unit name="default" >
         <properties>
         ...
             <property name="hibernate.generate_statistics" value="true" />
         ...
         </properties>
    </persistence-unit> 
</persistence>

Um nicht die persistence.xml zu modifizierem, welche mit deployt wird, sollte man für die Testfälle besser die System Properties anpassen. Diese über das Kommandozeilenparameter -D

java -jar app.jar -Dhibernate.generate_statistics=true

oder in der IDE angegeben werden:

Spring Boot

Möchte man das Feature unter Spring Boot verwenden und trägt das so in die application.properties ein, dann wird das nicht funktionieren. Um die Properties an den JPA Provider (hier also Hibernate) zu übergeben, muss dieses in einen Prefix in der application.properties bekommen. In dem Fall von Hibernate ist das spring.jpa.properties.hibernate.

# Generate additional statistics. Do not use in production
spring.jpa.properties.hibernate.generate_statistics=true

Statistiken

Wenn man wie oben beschrieben die Generierung der Statistiken aktiviert hat, dann bekommt man eine nette Ausgabe auf der Konsole zu den abgesetzten Queries zur Datenbank.

40999 nanoseconds spent acquiring 2 JDBC connections;
25173 nanoseconds spent releasing 1 JDBC connections;
327100 nanoseconds spent preparing 3 JDBC statements;
1215257 nanoseconds spent executing 3 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
901269 nanoseconds spent executing 1 flushes (flushing a total of 1 entities and 0 collections);
0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)

Queries

Queries formatieren

Normalerweise werden die Queries inline geloggt. D.h. je Zeile ein Query. Das macht insgesamt das Log gut lesbar, aber dafür sind die SQL Queries um so schlechter zu lesen. Auch hierfür bietet Hibernate eine Lösung an. Man kann die Ausgabe der Queries im Log formatieren lassen. Auch hier gilt natürlich der Grundsatz, dieses nicht in Produktion zu verwenden. Setzt man format_sql auf true, dann erhält man eine Mehrzeilige, formatierte Ausgabe der Queries in dem Log.

Hibernate: 
   create table customer (
       id bigint not null,
        lastname varchar(50),
        name varchar(50),
        primary key (id)
    ) engine=MyISAM

In der application.properties trägt man analog zu dem obigen Beispiel es mit dem Prefix spring.jpa.properties.hibernate ein.

# Prettify Hibernate SQL Queries in log
spring.jpa.properties.hibernate.format_sql=tru

Binding Parameter

Parameter der Queries

Mit den beiden oben genannten Methoden kann man schon sehr viel Informationen aus dem Log von Hibernate entnehmen. Eine wichtige Zutat um das Geheimnis der Blackbox Hibernate zu lüften fehlt allerdings noch. Man kann zwar jetzt die konkreten Queries im Log sehen, aber man kann nicht erkennen welche Parameterwerte gebunden sind.

Konfiguration von Spring Boot

Mit dem sogenannten Tracing kann man sich die Werte der Parameter mit in dem Log ausgeben lassen. Gesteuert wird das Ganze über den Hibernate Type. Dieser muss auf Trace gestellt werden:

# Show binding parameter values
logging.level.org.hibernate.type=trace

Die Ausgabe im Log erscheint dann wie folgt:

Hibernate: 
    insert 
    into
        customer
        (lastname, name, id) 
    values
        (?, ?, ?)
2018-11-15 12:54:47.652 TRACE 6971 --- [  restartedMain] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [Bernhard]
2018-11-15 12:54:47.652 TRACE 6971 --- [  restartedMain] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Lisa]
2018-11-15 12:54:47.652 TRACE 6971 --- [  restartedMain] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [BIGINT] - [11]

SQLite Dialect

Um Hibernate zur Zusammenarbeit mit SQLite zu bewegen, gibt es seit kurzem ein Release für https://github.com/gwenn/sqlite-dialect auf Maven. D.h. es kann direkt über Maven Central bezogen werden. Früher wurde das Programm nur über Jitpack.io verteilt, was zusätzliche Konfiguration im Buildskript erforderte.

Nun kann man die beötigten Abhängigkeiten mit 2 Zeilen hinzufügen:

// SQLite
compile('org.xerial:sqlite-jdbc:3.25.2')
compile('com.github.gwenn:sqlite-dialect:0.1.0')

Jetzt muss nur noch Konfiguration angepasst werden:

spring:
  datasource:
    url: jdbc:sqlite:empty.db

oder

spring.datasource.url=jdbc:sqlite:empty.db

Nun sollten in der Anwendung erstellte Repositories auf der SQLite Datenbank arbeiten.

Methode 1 Einfach

Die wohl einfachste Methode ist einfach spring.jpa.show-sql auf true zu setzen.

spring.jpa.show-sql=true

Damit das ganze lesbarer ist, kann man auch gleich die Formatierung des SQL Queries veranlassen. Das ganze wird mit spring.jpa.properties.hibernate.format_sql gesteuert. Setzt man auch dieses Wert auf treu, dann erhält man das gewünschte Ergebnis. Leider ist dieses komplett unoptimiert und geht direkt am Loggingframework vorbei. Schlimmer noch die Parameter der Prepared Statements können nicht angezeigt werden.

Methode 2 Loggingframework

Mit den Einstellungen:

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

Bekommt man das gleiche Ergebnis, aber die Ausgaben werden über das Loggingframework ausgegeben. Enums werden so aber nicht ausgegeben. Dafür muss separat folgender Schalter verwendet werden:

logging.level.org.hibernate.type.EnumType=TRACE

Spring Boot mit Hibernate

Kommt es beim Starten einer Spring Boot Anwendung zu einer ClassNotFoundException, weil die javax.xml.bind.JAXBException nicht gefunden worden ist, dann fehlt hier die Dependency für JAXB.

Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582) ~[na:na]
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:190) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:499) ~[na:na]

Hibernate benötigt typischerweise JAXB, welches per default ab Java 9 nicht mehr sichtbar ist. Entweder mit –add-modules java.xml.bind zur Laufzeit oder als Abhängigkeit einbinden.

Dependency JAXB hinzufügen

Unter Gradle (letzte Version unter Maven Central suchen JAXB)

compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.0'