GEDOPLAN
Jakarta EE (Java EE)

RESTful Lazy-Load mit Jackson und Hibernate

Jakarta EE (Java EE)

Lazy-Loading ist eine feine Sache, denn Daten nur dann zu Laden wenn sie benötigt werden klingt nach einer optimalen Lösung. Allerdings dürfte die org.hibernate.LazyInitializationException den Meisten ein Begriff sein, wird diese Exception doch ausgelöst wenn auf eine eben solche Lazy-Abhänigkeit zugegriffen wird ohne das sie Initialisiert wurde oder die Möglichkeit besteht dies direkt zu tun. Grundsätzlich kein großes Problem, eine Serialisierung solcher Objekte in ein JSON-Format stellt allerdings erst einmal eine Hürde da…

Schauen wir uns das sehr einfache Beispiel einer Schnittstelle an die eine Entität mittels Hibernate aus einer Datenbank ausliest und als JSON-String ausliefert.


@Path("material")
@Produces("application/json")
@Consumes("application/json")
public class MaterialResourceWS {

    @Inject
    private MaterialResource materialResource;

    @GET
    public List<Material> getMaterials() {
        return materialResource.readAllMaterials();
    }
...

angular_layzyload_class
Entität
angular_layzyload_vorher
JSON-Ausgabe

 

Was passiert? Bei der Serialisierung unserer Entität werden alle Attribute ermittelt welche serialisiert werden sollen. Anschließend werden diese Attribute abgerufen und in ein JSON Format gespeichert. Das geschieht nicht nur mit primitiven Datentypen sondern auch mit abhängigen Objekten. Das geht so lange gut bis der Parser auf eine Lazy-Abhängigkeit stößt, denn auch hier versucht dieser die Werte zu ermitteln und läuft damit auf eine LazyInitializationException.

Welche Möglichkeiten haben wir?

  • @JsonIgnore
    • Diese Annotation (am Attribut einer Entität) teilt dem Parser mit das dieses Element ignoriert werden soll. Diese Lösung ist einfach, aber nicht besonders flexibel da wir auf Klassenebene einmal entscheiden welche Attribute ins JSON übertragen werden sollen und welche nicht. Eine Unterscheidung zwischen verschiedenen Szenarien ist hier nicht möglich
  • DTOs
    • Ein bewährtes Vorgehen ist die Erzeugung von DTOs (Data Transfer Objects). Wir erstellen demnach für jedes Szenario eine entsprechende Java Klasse, ohne JPA spezifischen Annotation und übertragen nur die Werte in das DTO die von der Schnittstelle benötigt werden. Dieses Vorgehen ist sicherlich das Mittel der Wahl wenn es darum geht große Objekte auf das benötigte Sub Set von Attributen zu minimieren, Tools wir z.B. Dozer helfen dabei unnötige Mapping-Methoden zu schreiben.
  • Dem Parser „Hibernate beibringen“
    • DTOs zu verwenden mag in einigen Fällen eher unhandlich sein da wir selbst für überschaubare Entitäten mit Lazy-Abhängigkeiten entsprechende DTO-Klassen erzeugen müssten. Viel einfacher wäre es doch wenn nicht initialisierte Objekte / Collections einfach ignoriert werden würden:

 

jackson-datatype-hibernate

Dieses Addon für Jackson tut (unter anderem) genau das: Berücksichtigung von LazyLoading. Die Verwendung ist denkbar einfach. Neben einer entsprechenden Maven-Abhängigkeit (s. Maven ) müssen wir das Modul lediglich registrieren. Hierfür bietet sich eine JAX-RS-Provider Klasse an:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class JacksonHibernate4ModuleConfig implements ContextResolver<ObjectMapper> {

    private ObjectMapper objectMapper = new ObjectMapper() {
    private static final long serialVersionUID = 1L;
        {
            Hibernate4Module hibernate4Module = new Hibernate4Module();
            //hibernate4Module.configure(Hibernate4Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS, true);
            registerModule(hibernate4Module);
        }
    };

    @Override
    public ObjectMapper getContext(Class<?> objectType) {
        return objectMapper;
    }
}

Dank dieser Konfiguration erkennt der JSON Parser nicht initialisierte Objekte und Collections und setzte diese auf „null“ anstatt eine Exception aus zu lösen:

angular_layzyload_vorhernachher
JSON-Ausgabe

! Vorsicht ist hier bei schreibenden Schnittstellen geboten. Sollten diese Objekte unverändert an Hibernate übergeben werden kommt es in folgenden beiden Fällen zu einem „Fehler“:

  • Collections mit „orphan removal“
    • Hibernate wird bei einer solchen Liste die durch Jackson auf „null“ gesetzt wurde einen Fehler auslösen.
  • @ManyToOne(fetch = FetchType.LAZY)
    • Diese Referenz wird von Jackson entfernt / auf null gesetzt. Hibernate wird dies bei der Speicherung berücksichtigen und ungewollt die entsprechende Referenz aus der Datenbank entfernen.

Im obigen Beispiel wird bereits eine weitere Möglichkeit angedeutet. Durch die Konfiguration des Features: SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS werden Lazy-ManyToOne Abhängigkeiten nicht auf „null“ gesetzt sondern mit einem Hinweis auf die Ziel-Entität versehen. Das obige Beispiel würde mit aktivierter Option für das Attribut Purchaser folgendes JSON Format erzeugen:

"purchaser": {
   "de.gedoplan.angular.model.Purchaser": 1
}...

(zu beachten ist, dass dieses Format natürlich nicht ohne Eingriff vom Server in ein Material-Objekt zurück gewandelt werden kann)

jackson-datatype-hibernate bietet, wie wir sehen konnten, eine sehr einfache Art und Weise mit Lazy-Loading um zu gehen ohne zusätzliche Klassen oder eigene Parser zu schreiben. Vorsicht ist nur bei schreibenden Schnittstellen geboten, sodass hier gegeben falls noch selber Anpassungen nötig sind.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Bitte füllen Sie dieses Feld aus.
Bitte füllen Sie dieses Feld aus.
Bitte gib eine gültige E-Mail-Adresse ein.
Sie müssen den Bedingungen zustimmen, um fortzufahren.

Autor

Diesen Artikel teilen

LinkedIn
Xing

Gibt es noch Fragen?

Fragen beantworten wir sehr gerne! Schreibe uns einfach per Kontaktformular.

Kurse

weitere Blogbeiträge

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!

Work Life Balance. Jobs bei Gedoplan

We are looking for you!

Lust bei GEDOPLAN mitzuarbeiten? Wir suchen immer Verstärkung – egal ob Entwickler, Dozent, Trainerberater oder für unser IT-Marketing! Schau doch einfach mal auf unsere Jobseiten! Wir freuen uns auf Dich!