JAX Blog

Hibernate 6: Die neuen Features vorgestellt (mit Beispielen)

Neue Features für einfachere und bessere Persistenzschichten

Apr 17, 2023

Vor wenigen Monaten wurde nach langer Entwicklungszeit Hibernate 6.0.0 veröffentlicht. Neben vielen internen Verbesserungen bietet das inzwischen bereits in Version 6.1 vorliegende Framework auch einige neue Features, die die Entwicklung neuer und bestehender Persistenzschichten vereinfachen.

Hibernate 6 enthält viele Änderungen, von denen einige Anpassungen am bestehenden Anwendungscode erfordern. Wenn man diese Anpassungen vorgenommen hat, steht einer erfolgreichen Migration nichts mehr im Wege. Im Anschluss erhält man nicht nur eine verbesserte Abfragegenerierung, die zu einer besseren Anwendungsperformanz führen soll, sondern auch die Möglichkeit mit Hilfe der neu eingeführten Features die eigene Persistenzschicht zu verbessern und zu vereinfachen. In diesem Artikel werden wir einige der nützlichsten Änderungen in Hibernate 6 genauer betrachten.

Reihenfolge von @ManyToManyBeziehungen beibehalten

Laut Javadoc soll eine java.util.List eine geordnete Sammlung von Werten darstellen. Viele Entwickler erwarten daher, dass die Elemente einer To-many-Beziehung ihre Reihenfolge beibehalten, wenn diese als java.util.List modelliert ist. Dies ist im Standardfall allerdings nicht der Fall, da Hibernate die Reihenfolge der Elemente nicht persistiert. Daher ist diese nicht mehr definiert, wenn die Beziehung aus der Datenbank gelesen wird.

Bis Hibernate 6.0.0 konnte das ausschließlich mit einer zusätzlichen @OrderColumn-Annotation an der beziehungsdefinierenden Seite einer @ManyToMany-Beziehung geändert werden (Listing 1). Hibernate persistiert den Index jedes Elements in einer separaten Spalte der Beziehungstabelle. Dieser Index wird für alle Elemente während Einfüge-, Aktualisierungs- und Löschoperationen verwaltet und bei Leseoperationen mit ausgelesen. Falls der Name dieser Spalte nicht über die @OrderColumn-Annotation definiert wird, erzeugt Hibernate den Spaltennamen aus dem Namen der Beziehungseigenschaft und dem Postfix _ORDER.

Listing 2 zeigt ein Beispiel der ausgeführten SQL-Operationen, wenn eine neue PurchaseOrder-Entität mit einer Liste von drei Product-Entitäten persistiert und anschließend gelesen wird. Die Beziehungsdatensätze in der Tabelle PurchaseOrder_Product enthalten neben den Referenzen zur PurchaseOrder und dem jeweiligen Product auch die entsprechende Position innerhalb der Produktliste. Hibernate verwendet diese während der Entitätsinstanziierung, um die ursprüngliche Reihenfolge der referenzierten Product-Entitäten wiederherzustellen.

@Entity
public class PurchaseOrder {
  
  @ManyToMany
  @OrderColumn
  private List<Product> products = new ArrayList<>();
 
  ...
}
16:33:39,608 DEBUG [org.hibernate.SQL] - insert into PurchaseOrder (customer, version, id) values (?, ?, ?)
16:33:39,617 DEBUG [org.hibernate.SQL] - insert into PurchaseOrder_Product (orders_id, products_ORDER, products_id) values (?, ?, ?)
16:33:39,623 DEBUG [org.hibernate.SQL] - insert into PurchaseOrder_Product (orders_id, products_ORDER, products_id) values (?, ?, ?)
16:33:39,626 DEBUG [org.hibernate.SQL] - insert into PurchaseOrder_Product (orders_id, products_ORDER, products_id) values (?, ?, ?)
16:33:39,694 DEBUG [org.hibernate.SQL] - select p1_0.id,p1_0.customer,p1_0.version from PurchaseOrder p1_0 where p1_0.id=?
16:33:39,723 DEBUG [org.hibernate.SQL] - select p1_0.orders_id,p1_0.products_ORDER,p1_1.id,p1_1.name,p1_1.price,p1_1.version from PurchaseOrder_Product p1_0 join Product p1_1 on p1_1.id=p1_0.products_id where p1_0.orders_id=?

Hibernate 6 unterstützt die @OrderColumn-Annotation auch weiterhin. Darüber hinaus kann die Behandlung einer java.util.List für die besitzende Seite einer @ ManyToMany-Beziehung und für ElementCollections nun auch global definiert werden. Dazu muss die Konfigurationseigenschaft hibernate.mapping.default_list_semantics in der Datei persistence.xml auf LIST gesetzt werden (Listing 3). Das führt zu der bereits beschriebenen Persistierung der Reihenfolge der Listenelemente, ohne dass jede @ManyToMany-Beziehung mit einer zusätzlichen @OrderColumn-Annotation versehen werden muss.

<persistence>
  <persistence-unit name="my-persistence-unit">
    ...
    <properties>
      <property name="hibernate.mapping.default_list_semantics" value="LIST" />
      ...
    </properties>
  </persistence-unit>
</persistence>

Bevor man die Persistierung der Reihenfolge von @ ManyToMany-Beziehungen global aktiviert, sollte man jedoch die Auswirkungen auf die Performanz genau abwägen. Die Verwaltung des Index erfordert einigen Aufwand, durch den vor allem Änderungen an einer Beziehung verlangsamt werden.

NEUES AUS DER JAVA-ENTERPRISE-WELT

Serverside Java-Track entdecken

 

Hibernate persistiert die Position eines jeden Elements in der Beziehungstabelle. Das erfordert kaum zusätzlichen Aufwand, wenn neue Elemente am Ende der Liste hinzugefügt oder die letzten Elemente aus einer Liste entfernt werden. Anders sieht es aus, wenn ein Element am Anfang oder in der Mitte hinzugefügt oder entfernt wird.

Listing 4 zeigt ein Beispiel, in dem ein neues Product angelegt und an zweiter Stelle zur Liste der Produkte einer PurchaseOrder hinzugefügt wird. Dadurch werden die Elemente, die sich bisher an Position zwei oder dahinter befunden haben, um einen Platz nach hinten geschoben. Unter Verwendung der Standardkonfiguration würde Hibernate die Reihenfolge der Produkte nicht persistieren, und das in Listing 4 gezeigte Beispiel würde ausschließlich zwei SQL-INSERT-Operationen auslösen. Da in der persistence.xml der Parameter default_list_semantics auf LIST konfiguriert wurde (Listing 3), müssen nun die Beziehungsdatensätze aller Elemente aktualisiert werden, deren Position durch das neue Produkt verändert wurde (Listing 5). Je nach Anzahl der betroffenen Listenelemente kann das zu einem deutlichen Mehraufwand führen, der die Performanz der Anwendung negativ beeinflusst. Dieselbe Problematik besteht für alle Löschoperationen, die nicht den letzten Listeneintrag betreffen.

// Bestehende PurchaseOrder lesen
// PurchaseOrder order = em.find(PurchaseOrder.class, order.getId());
 
// Ein neues Produkt an 2. Stelle einfügen
Product prod = new Product("Product 4", 2.00);
prod.getOrders().add(order);
order.getProducts().add(1, prod);
em.persist(prod);
16:58:54,095 DEBUG [org.hibernate.SQL] - insert into Product (name, price, version, id) values (?, ?, ?, ?)
16:58:54,098 DEBUG [org.hibernate.SQL] - update PurchaseOrder set customer=?, version=? where id=? and version=?
16:58:54,104 DEBUG [org.hibernate.SQL] - update PurchaseOrder_Product set products_id=? where orders_id=? and products_ORDER=?
16:58:54,107 DEBUG [org.hibernate.SQL] - update PurchaseOrder_Product set products_id=? where orders_id=? and products_ORDER=?
16:58:54,110 DEBUG [org.hibernate.SQL] - insert into PurchaseOrder_Product (orders_id, products_ORDER, products_id) values (?, ?, ?)

Um diese zusätzlichen Operationen so weit wie möglich zu vermeiden, sollte daher für jede @ManyToMany-Beziehung genau geprüft werden, ob die Speicherung der Reihenfolge erforderlich ist. Wie die Erfahrung aus Projekten mit Hibernate 4 und 5 gezeigt hat, ist das für die meisten Projekte und Beziehungen nicht der Fall. Wenn das auch in der untersuchte Anwendung so ist, sollte auf die Aktivierung des neuen Konfigurationsparameters hibernate.mapping.default_list_semantics verzichtet werden. Wenn die Reihenfolge nur für einzelne Beziehungen persistiert werden soll, kann das mit Hilfe der bereits bekannten Annotation @OrderColumn definiert werden. Der neue Konfigurationsparameter sollte hingegen nur eingesetzt werden, wenn für alle @Many-ToMany-Beziehungen die Reihenfolge der Elemente persistiert werden muss.

Behandlung zeitzonenbehafteter Zeitstempel

Seit mit Java 8 das Date and Time API eingeführt wurde, sind die Klassen OffsetDateTime und ZonedDateTime die offensichtlichsten und am häufigsten verwendeten Typen zur Modellierung eines Zeitstempels mit Zeitzoneninformationen. Und man könnte erwarten, dass die Auswahl eines dieser Typen das Einzige ist, was man tun muss, um einen solchen Zeitstempel in der Datenbank zu persistieren.

Aber leider ist das nicht der Fall, wenn diese Informationen mit Hilfe von JPA in einer relationalen Datenbank abgelegt werden sollen. Obwohl der SQL-Standard den Spaltentyp TIMESTAMP_WITH_TIMEZONE definiert, wird dieser nicht von allen Datenbanken in gleicher Weise unterstützt. Aus diesem Grund unterstützt die JPA-Spezifikation OffsetDateTime und ZonedDateTime nicht als Attributtypen.

 

In Hibernate 5 wurde eine proprietäre Unterstützung eingeführt, bei der der Zeitstempel in die Zeitzone der Java-Anwendung normalisiert und ohne Zeitzoneninformationen gespeichert wird. Das führte zu Pro-blemen, falls sich die Zeitzone der Anwendung änderte bzw. mehrere Java-Anwendungen aus unterschiedlichen Zeitzonen auf dieselbe Datenbank zugriffen. Zeitzonen mit Sommer- und Winterzeit führten ebenfalls zu Problemen.

In Hibernate 6 wurde die Behandlung von Zeitstempeln mit Zeitzoneninformationen flexibler gestaltet und bietet dadurch verschiedene Möglichkeiten, diese Probleme zu vermeiden. Die in Hibernate 5 verwendete Normalisierung wird aber weiterhin unterstützt, sodass bestehende Entitätsabbildungen nicht migriert werden müssen.

Die bevorzugte Abbildung von OffsetDateTime und ZonedDateTime kann in Hibernate 6 entweder global konfiguriert oder mittels Annotation für jede Entitätseigenschaft festgelegt werden. Die globale Konfiguration erfolgt über den Parameter hibernate.timezone.default_storage, der in der persistence.xml-Datei konfiguriert werden kann (Listing 6). Die gültigen Parameterwerte werden dabei durch das TimeZoneStorageType-Enum definiert. Mit Hibernate 6.1.4 stehen die folgenden Werte zur Auswahl:

<persistence>
  <persistence-unit name="my-persistence-unit">
    ...
     <properties>
      <property name="hibernate.timezone.default_storage" value="NORMALIZE"/>
      ...
    </properties>
  </persistence-unit>
</persistence>
  • NATIVE bildet die Zeitstempel auf eine Datenbankspalte vom Typ TIMESTAMP_WITH_TIMEZONE ab. Dieser Spaltentyp muss von der verwendeten Datenbank unterstützt werden.

  • NORMALIZE führt zu einer Normalisierung des Zeitstempels in die Zeitzone der Java-Anwendung oder die durch den Konfigurationsparameter hibernate.jdbc.time_zone definierte Zeitzone. Anschließend wird der Zeitstempel ohne Zeitzoneninformationen in der Datenbank gespeichert. Das entspricht dem Verhalten von Hibernate 5 und wird von Hibernate 6 weiterhin als Standardabbildung verwendet.

  • NORMALIZE_UTC führt zu einer Normalisierung des Zeitstempels in die Zeitzone UTC. Der Vorteil dieser Normalisierung liegt darin, dass sie nicht von der Zeitzone der Java-Anwendung abhängt und nicht über Sommer- und Winterzeit verfügt. Somit werden die vorher genannten Probleme der Normalisierung vermieden.

  • COLUMN bestimmt die Differenz zwischen der Zeitzone des Zeitstempels und UTC. Anschließend werden die Differenz und der nach UTC normalisierte Zeitstempel in getrennten Datenbankspalten gespeichert. Somit wird für den Zeitstempel 1.1.2023 15:00 CET eine Zeitzonendifferenz von +01:00 bestimmt, der Zeitstempel nach 1.1.2023 14:00 normalisiert und die beiden Informationen in zwei getrennten Datenbankspalten gespeichert.

Neben der globalen Konfiguration des Zeitstempels kann die Zeitzonenbehandlung auch für jede Entitätseigenschaft vom Typ OffsetDateTime und ZonedDateTime einzeln definiert werden. Dazu muss die Entitätseigenschaft mit @TimeZoneStorage annotiert und ein Wert des TimezoneStorageType-Enum übergeben werden. Listing 7 zeigt ein Beispiel für eine Abbildung der ZonedDateTime orderDateTime-Eigenschaft auf eine Datenbankspalte vom Typ TIMESTAMP_WITH_TIMEZONE. Dieser Spaltentyp ist von der SQL-Spezifikation zur Speicherung von zeitzonenbehafteten Zeitstempeln vorgesehen. Wenn die verwendete Datenbank diesen Spaltentyp unterstützt, ist das daher die empfohlene Abbildungsart.

@Entity
public class PurchaseOrder {
  
  @TimeZoneStorage(TimeZoneStorageType.NATIVE)
  private ZonedDateTime orderDateTime;
  
  ...
}

Sollte die Datenbank diesen Typ jedoch nicht unterstützen, vermeiden TimeZoneStorageType.NORMALIZE_UTC und TimeZoneStorageType.COLUMN die aus Hibernate 5 bekannten Abbildungsprobleme. TimeZoneStorageType.NORMALIZE_UTC kann auf dem in Listing 7 gezeigten Weg konfiguriert werden. Bei der Verwendung von TimeZoneStorageType.COLUMN kann zusätzlich der Name der Datenbankspalte, in der die Zeitzonendifferenz abgelegt wird, mit Hilfe der Annotation @TimeZoneColumn definiert werden (Listing 8). Sollte der Name der Spalte nicht konfiguriert werden, generiert Hibernate diesen aus dem Namen der Datenbankspalte, auf die der Zeitstempel abgebildet wird, und dem Postfix _tz.

@Entity
public class PurchaseOrder {
  
  @TimeZoneStorage(TimeZoneStorageType.COLUMN)
  @TimeZoneColumn(name = "orderDateTime_timezone")
  private ZonedDateTime orderDateTime;	
  ...
}

Getrennte Interfaces für Lese- und Schreiboperationen

Eine der kleineren Änderungen in Hibernate 6 ist die Einführung der Interfaces MutationQuery und SelectionQuery. Diese ermöglichen die Trennung zwischen Abfragen, die Daten ändern, und solchen, die Daten lesen.

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Ältere Hibernate-Versionen und die JPA-Spezifikation bilden beide Arten von Abfragen über das Query- und das TypedQuery-Interface ab. Der einzige Unterschied zwischen diesen Interfaces liegt in der strikteren Typisierung des TypedQuery-Interface. Beide Interfaces erweitern ab Hibernate 6 die neu eingeführten SelectionQuery– und MutationQuery-Interfaces und können auch weiterhin verwendet werden. Die beiden neuen Interfaces sind jedoch deutlich übersichtlicher, da sie jeweils nur die Methoden definieren, die mit dem entsprechenden Abfragetyp verwendet werden können. Somit stellt SelectionQuery z. B. die Methoden getResultList, getResultStream und getSingleResult bereit. Die Methoden setFirstResult und setMaxResult sind ebenfalls dem SelectionQuery-Interface vorbehalten. Die Methode executeUpdate hingegen wird ausschließlich vom MutationQuery-Interface definiert.

Da es sich hierbei um zwei Hibernate-spezifische Interfaces handelt, können diese nur über die Methoden createSelectionQuery und createMutationQuery von Hibernates Session-Interface, jedoch nicht über den EntityManager von JPA erzeugt werden. Eine Implementierung des Session-Interface kann man entweder über Hibernates SessionFactory erzeugen oder mit Hilfe der unwrap-Methode aus einer Instanz des EntityManager extrahieren (Listing 9).

EntityManager em = emf.createEntityManager();
Session s = em.unwrap(Session.class);
 
SelectionQuery<Book> q = s.createSelectionQuery("SELECT b FROM Book b WHERE b.title = :title", Book.class);
q.setParameter("title", "Hibernate Tips - More than 70 solutions to common Hibernate problems");
List<Book> books = q.getResultList();

Flexible Instanziierung von Embeddables

Eine weitere Neuerung in Hibernate 6 ist das EmbeddableInstantiator-Interface, mit dem eine Klasse zur Instanziierung von Embeddables implementiert werden kann. Embeddables sind einfache Java-Klassen, mit denen eine Reihe von Attributen definiert werden kann, die Teil von Entitäten werden (Listing 10). Man verwendet sie häufig, um wiederverwendbare Abbildungsinformationen zu erstellen, die in verschiedenen Entitäten verwendet und durch die Geschäftslogik auf dieselbe Art verarbeitet werden.

@Embeddable
public class Address {
 
  private String street;
  private String city;
  private String postalCode;
 
  public String getStreet() {
    return street;
  }
 
  public String getCity() {
    return city;
  }
 
  public String getPostalCode() {
    return postalCode;
  }
  
  public void setStreet(String street) {
    this.street = street;
  }
 
  public void setCity(String city) {
    this.city = city;
  }
 
  public void setPostalCode(String postalCode) {
    this.postalCode = postalCode;
  }
}

Die JPA-Spezifikation und Hibernate bis Version 6.0.0 verlangen, dass jedes Embeddable einen Standardkonstruktor anbietet. Hibernate ruft diesen zur Objektinstanziierung auf, wenn eine Entität mit einem Embeddable aus der Datenbank gelesen wird. Für die Verwendung im eigenen Anwendungscode ist dieser Konstruktor jedoch häufig nicht geeignet. Hier wäre ein Konstruktor, der alle erforderlichen Eigenschaften setzt, oftmals die bessere Wahl.

SIE LIEBEN JAVA?

Den Core-Java-Track entdecken

 

Seit Hibernate 6.0.0 kann mit Hilfe des Embeddable-Instantiator-Interface eine Klasse erzeugt werden, die Hibernate anstatt des Standardkonstruktors zur Instanziierung eines Embeddables aufruft, womit der Standardkonstruktor überflüssig wird.

public class AddressInstantiator implements EmbeddableInstantiator {
 
  Logger log = LogManager.getLogger(this.getClass().getName());
 
  public boolean isInstance(Object object, SessionFactoryImplementor sessionFactory) {
    return object instanceof Address;
  }
 
  public boolean isSameClass(Object object, SessionFactoryImplementor sessionFactory) {
    return object.getClass().equals( Address.class );
  }
 
  public Object instantiate(ValueAccess valuesAccess, SessionFactoryImplementor sessionFactory) {
    // valuesAccess enthält die Eigenschaftswerte in alphabetischer Reihenfolge der     // Eigenschaftsnamen
    final String city = valuesAccess.getValue(0, String.class);
    final String postalCode = valuesAccess.getValue(1, String.class);
    final String street = valuesAccess.getValue(2, String.class);
    log.info("Instantiate Address embeddable for "+street+" "+postalCode+" "+city);
    return new Address( street, city, postalCode );
  }
 
}

Wie in Listing 11 zu sehen ist, werden für die Implementierung des EmbeddableInstantiator lediglich drei Methoden benötigt. Zur Instanziierung von Embeddable ruft Hibernate die Methode instantiate auf und übergibt ein ValueAccess- und ein SessionFactoryImplementor-Objekt. Das ValueAccess-Objekt enthält alle Eigenschaftswerte des zu instanziierenden Embeddable in alphabetischer Reihenfolge der Eigenschaftsnamen. In dem in Listing 11 gezeigten Beispiel gehört der erste Wert im ValuesAccess-Objekt somit zur Eigenschaft city des AddressEmbeddable, der zweite Wert zur Eigenschaft postalCode und der dritte Wert zur Eigenschaft street. Nachdem diese aus dem ValuesAccess-Objekt extrahiert wurden, können sie zur Instanziierung des Embeddable verwendet werden.

Nachdem das EmbeddableInstantiator-Interface implementiert ist, muss es noch mit dem Embeddable verknüpft werden. Das kann auf dem Embeddable mit Hilfe der EmbeddableInstantiator-Annotation definiert werden. Zusätzlich benötigt das AddressEmbeddable den in der instantiate-Methode aufgerufenen Konstruktor, und der Standardkonstruktor kann entfernt werden (Listing 12). Nachdem diese Änderungen durchgeführt wurden, ruft Hibernate für jede Instanziierung des AddressEmbeddable die Methode initiate der AddressInstantiator-Klasse auf.

@Embeddable
@EmbeddableInstantiator(AddressInstantiator.class)
public class Address {
 
  private String street;
  private String city;
  private String postalCode;
 
  public Address(String street, String city, String postalCode) {
    this.street = street;
    this.city = city;
    this.postalCode = postalCode;
  }
 
  public String getStreet() {
    return street;
  }
 
  public String getCity() {
    return city;
  }
 
  public String getPostalCode() {
    return postalCode;
  }
}

Zusammenfassung

Hibernate 6 bietet neben vielen internen Änderungen auch einige neue Features. Einige der interessantesten wurden in diesem Artikel vorgestellt. Dazu zählen neben neuen Interfaces zur Separierung von lesenden und modifizierenden Abfragen auch die Möglichkeit, für alle als java.util.List modellierten @ManyToMany-Beziehungen die Reihenfolge ihrer Elemente zu persistieren. Das kann nun mit Hilfe des Konfigurationsparameters hibernate.mapping.default_list_semantics global aktiviert werden. Damit kann die initiale Sortierung der Beziehung dauerhaft beibehalten werden. Allerdings erfordert das auch zusätzliche Datenbankoperationen, wenn neue Elemente nicht am Ende der Liste hinzugefügt und andere als das letzte Element der Liste gelöscht werden. Das sollte vor der Konfiguration des neuen Parameters bedacht werden, um das Risiko für daraus entstehende Performanceprobleme frühzeitig bewerten zu können.

Die Abbildung von Zeitstempeln mit Zeitzoneninformationen wurde in Hibernate 6 ebenfalls verbessert. Die in Hibernate 5 verwendete Normalisierung von Zeitstempeln war von einigen Rahmenbedingungen abhängig und führte in der Praxis häufiger zu Problemen. Mit der @TimeZoneStorage-Annotation und dem Konfigurationsparameter hibernate.timezone.default_storage kann nun bestimmt werden, ob der Zeitstempel weiterhin in die Zeitzone der Java-Anwendung oder nach UTC normalisiert oder in einer Datenbankspalte vom Typ TIMESTAMP_WITH_TIMEZONE abgelegt werden soll. Darüber hinaus gibt es die Möglichkeit, die Zeitzonendifferenz zu UTC und den nach UTC normalisierten Zeitstempel in zwei getrennten Datenbankspalten zu speichern. Insbesondere durch die Verwendung einer Datenbankspalte vom Typ TIMESTAMP_WITH_TIMEZONE und die Normalisierung der Zeitstempel nach UTC können die aus Hibernate 5 bekannten Probleme vermieden werden.

Und durch Hibernates neues Interface Embeddable-Instantiator benötigt Version 6 zur Instanziierung eines Embeddable keinen Standardkonstruktor mehr. Stattdessen kann das neue Interface implementiert und mit dem Embeddable verknüpft werden. Dabei gilt es zu beachten, dass das ein Hibernate-spezifisches Feature ist, das nicht von der JPA-Spezifikation und deren anderen Implementierungen unterstützt wird.

Hibernate 6 bietet weitere Neuerungen und Verbesserungen, etwa für die Implementierung von mehrmandantenfähigen Systemen und die Abbildung eigener Datentypen, die in diesem Artikel nicht vorgestellt werden konnten. Weitere Informationen dazu gibt es in der offiziellen Hibernate-Dokumentation und auf dem Blog des Autors.

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Alle News der Java-Welt:

Behind the Tracks

Agile, People & Culture
Teamwork & Methoden

Clouds & Kubernetes
Alles rund um Cloud

Core Java & Languages
Ausblicke & Best Practices

Data & Machine Learning
Speicherung, Processing & mehr

DevOps & CI/CD
Deployment, Docker & mehr

Microservices
Strukturen & Frameworks

Performance & Security
Sichere Webanwendungen

Serverside Java
Spring, JDK & mehr

Software-Architektur
Best Practices

Web & JavaScript
JS & Webtechnologien

Digital Transformation & Innovation
Technologien & Vorgehensweisen

Domain-driven Design
Grundlagen und Ausblick

Spring Ecosystem
Wissen in Spring-Technologien

Web-APIs
API-Technologie, Design und Management