Kontakt
stefan.bente[at]th-koeln.de
+49 2261 8196 6367
Discord Server
Prof. Bente Personal Zoom
Adresse
Steinmüllerallee 4
51643 Gummersbach
Gebäude LC4
Raum 1708 (Wegbeschreibung)
Sprechstunde nach Vereinbarung
Terminanfrage: calendly.com Wenn Sie dieses Tool nicht nutzen wollen, schicken Sie eine Mail und ich weise Ihnen einen Termin zu.

Häufige Fehler bei der Verwendung von Spring

Im ST2-Praktikum begegnen uns die nachfolgenden Fehler häufig. Will man eine DDD-Architektur mit Spring JPA umsetzen, dann sollte man diese vermeiden. Die nachfolgenden Fehler führen in der Regel dazu, dass eine Spring-Applikation nicht mehr läuft, also z.B. die allseits beliebte Failed to load Application Context Exception wirft. Die gelisteten Fehler haben keine Prioritätsreihenfolge, das ist eine ungeordnete Sammlung - ohne Anspruch auf Vollständigkeit. Wenn Ihnen ein Fehler auffällt, der hier nicht gelistet ist, dann schreiben Sie den gern!

Video(s) hierzu

Fehler bei Entities

Entity ohne Repo

Zu jedem Entity, das ein Aggregate Root ist, muss es ein Repository (Interface, z.B. von CrudRepository abgeleitet) geben.

Innere Entities eines Aggregates können Sie ohne Repo machen, wenn Sie eine cascade Anweisung in die Beziehung aufnehmen. Also: Wenn R das Root ist, und I das innere Entity, dann gibt es z.B. @OneToMany Deklaration für eine List<I> in R. Bei dieser Deklaration muss dann ein CascadeType.MERGE stehen - damit würde bei Persistenzoperationen auf R die eingeschalteten Objekte vom Typ I mit behandelt.

Soweit die “reine Lehre”. Allerdings - jetzt kommt die Einschränkung - besonders praxistauglich finde ich das nicht. Ich würde gern mal Produktiv-Code sehen, der innere Entities hat - wäre mal ein kleines feines Forschungsthema. Ich würde fast wetten, dass man da doch doch Repos macht, und einfach per Konvention diese Repos nicht zur “unabhängigen” Erzeugung innerer Entities nutzt. Einfach, weil man dadurch weniger Umwege / Einschränkungen / … im Code hat.

Entity ohne ID

Wenn man bei einem Entity vergisst, eine ID mit @Id zu spezifizieren, dann bekommt man in etwa folgende Exception:

Invocation of init method failed; nested exception is org.hibernate.AnnotationException:
        No identifier specified for entity: thkoeln.st.st2praktikum.planet.domain.Planet

Versuch, ein Entity zu speichern, bei dem die ID schon bei einem anderen Entity vorhanden ist, oder null

Beides führt zu einer Constraint Violation in der unterliegenden relationalen Datenbank, und damit zu einer Hibernate-Exception.

Abgeleitetes Entity, und die Oberklasse ist nicht auch als Entity getaggt

Vererbung sollte man sich grundsätzlich überlegen, weil man sich ein paar Nachteile einhandelt, was enge Kopplung und z.B. Serialisierbarkeit in REST-APIs angeht. Die Alternative ist Komposition (googeln Sie mal “Composition over Inheritance” für mehr Hintergrund dazu).

Wenn man Entities ableitet, dann muss man aufpassen, dass auch der Parent als @Entity getaggt ist.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class MyAbstractParentEntity {
    @Id
    @GeneratedValue
    private UUID id;
    //...
}

@Entity
public class ConcreteEntity extends MyAbstractParentEntity {
    // id wird geerbt ...
}

@Entity
public class AnotherConcreteEntity extends MyAbstractParentEntity {
    //...
}

EntityNotFoundException für eine Entity-Referenz beim Speichern

Bekommt man eine EntityNotFoundException für eine Entity-Referenz beim Speichern, dann wird möglicherweise versucht, eine Referenz zu einem noch transienten Entity abzuspeichern. Hibernate bekommt eine ID, geht davon aus, dass es die in der DB gibt, und bekommt dann eine Constraint Violation.

@OneToOne ( cascade = CascadeType.MERGE )

Wenn es aber gewollt ist, dass die Referenz ggfs. noch nicht persistiert ist, dann kann man die Referenz entsprechen mit einer Cascade-Anweisung annotieren. Mit der “Merge” Direktive legt Hibernate das noch transiente referenzierte Objekt dann vorab in der DB an.

Fehler bei Repositories

Repository mit falschem Entity- oder ID-Typ

Ein Repository sieht wie folgt aus:

public interface PlanetRepository extends CrudRepository<Planet, UUID> {
}

Hier wird gern mal jeder möglich Unfug :-) in die spitzen Klammern geschrieben: Mal die Repo-Klasse selbst, mal ein falscher ID-Typ (z.B. wenn das Entity UUID als ID-Typ verwendet, im Repo aber Long steht). Nicht immer sind die Fehlermeldungen sofort verständlich.

Parameter-Type-Mismatch bei Repository-Methoden

Wenn man den Spring-Mechanismus nutzt, Repository-Queries in die Namen der Repository-Methoden zu codieren, dann kann es zu einem Type Mismatch bei Parametern kommen - wenn nämlich der Typ des Parameters nicht mit dem Typ des im Methodennamen angegebenen Properties übereinstimmt. Nehmen wir als Beispiel eine Beziehung zwischen Order
(Bestellung) und User (Kunde). Nachfolgend sieht man ein Order-Repository mit einer solchen falschen Methode.

Beziehung zwischen Order und User

public interface OrderRepository extends CrudRepository<Order, UUID> {
	List<Order> findByUserId( User user );
}

Das Dumme ist, dass der Compiler hier nicht meckert, stattdessen bekommt man eine erstmal rätselhaft erscheinende Runtime-Exception.

org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value 
[thkoeln.archilab.ecommerce.solution.user.domain.User@1f] did not match expected type [java.util.UUID (n/a)]; 

Sprich: Nach Methoden-Signatur wird eine UUID erwartet, es kommt aber ein User-Objekt.

Fehler bei Services

Autowiring in nicht von Spring gemanagedten Klassen

Versucht man ein Autowiring in außerhalb von Spring gemanagedten Klassen, dann funktioniert das nicht - beim Zugriff erhält man eine NullPointerException. Dazu zählen alle Klassen, die nicht mit @Service, @Component oder @Bean getaggt sind - also auch Entities!

“Viel hilft viel” - @Entity, @Embeddable und @Service gemeinsam an einer Klasse

Das war auch in einem Codebeispiel dabei - aber “viel hilft viel” gilt hier leider nicht.

@Embeddable
@Entity
@Component
public class Planet {
    //...
    @OneToOne ( fetch = FetchType.EAGER )
    private Planet northNeighbour;
//...

Sonstige Fehler

Falscher Transaktion-Kontext, zu merken z.B. durch LazyInitializationException

Manchmal bekommt man aus seinem Domain-Layer-Code folgende Exception. Grund ist, dass der Aufruf außerhalb des Transaktionskontexts ist, und das dynamische Nachladen (“Lazy Loading”) von Entity-Referenzen schlägt fehl.

org.hibernate.LazyInitializationException: could not initialize proxy
        [thkoeln.st.st2praktikum.planet.domain.Planet#aa225c5c-d72f-4d15-bd90-547d818ef338] - no Session

Der gern genutzte Fix hier ist, den Fetchtype auf “eager” umzustellen. Damit wird das Lazy Loading ausgeschaltet, und alle Referenzen werden direkt der Query des Objekts mit aus der Datenbank geladen. Das kann ok sein, kann bei verschachtelten Strukturen aber auch sehr “teuer” werden.

@Entity
public class Planet {
    //...
    @OneToOne ( fetch = FetchType.EAGER )
    private Planet northNeighbour;
//...

Alternativ, und meistens die bessere Variante, ist es, die Transaktionskontexte zu analysieren. Hier kann man mit einer @Transactional Annotation an Methoden und Klassen arbeiten. Muss man im Einzelfall sorgfältig analysieren.

Mögliche Fehler aufgrund unserer Test-Library

Unsere Test-Library für Java und UML-Diagramme hat den Anspruch, mit allen praxisnahen und validen Umsetzungen auch umgehen zu können. Es gibt aber Ausnahmen, die zwar valide wären, die unsere Lib aber nicht “kann”.

Verwendung von Merge Nodes und Decision Nodes in Zustandsdiagrammen

Wenn in einem Zustandsdiagramm gewisse Zustandsübergänge erwartet werden, dann muss man leider auf Merge Nodes und Decision Nodes verzichten, weil die Verbindungen sonst nicht erkannt werden.

Verwendung von `Merge Nodes` und `Decision Nodes` in Zustandsdiagrammen

(Das liegt daran, dass die Lib Verbindungen immer zwischen zwei Knotenelementen erwartet, und die Verbindung nicht über die Merge / Decision Node hinaus weiterverfolgt. Das zu ändern würde einen Major Rewrite bedeuten.)