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!
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.
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
null
Beides führt zu einer Constraint Violation in der unterliegenden relationalen Datenbank, und damit zu einer Hibernate-Exception.
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 SpeichernBekommt 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.
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.
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.
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.
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!
@Entity
, @Embeddable
und @Service
gemeinsam an einer KlasseDas 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;
//...
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.
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”.
Merge Nodes
und Decision Nodes
in ZustandsdiagrammenWenn 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.
(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.)