12.3. JPA (Java Persistence API)

Die Java Plattform Erweiterung JPA ermöglicht dem Modellierer das Modell mit persistenzspezifische Informationen auf Basis der Java Persistence API (JPA) in der Version 1.0 zu versehen. GeneSEZ erzeugt aus diesen Informationen die entsprechenden Annotationen und fügt sie dem Quellcode hinzu. Die Generierung von XML-Konfigurationsdateien wird nicht unterstützt. Es wird ein grundlegendes Verständnis der JPA-Technologie vorausgesetzt.

Im nächsten Abschnitt wird das UML-Profil für die Java Plattform-Erweiterung JPA vorgestellt. Danach wird am Beispiel einer Persistent Entity gezeigt was ausgewählte Stereotypen bewirken und wie die automatische Quellcode-Ergänzung den Modellierer unterstützt. Die nachfolgenden Abschnitte beschreiben das Verhalten von Stereotypen, welche die automatische Quellcode-Ergänzung verwenden. Sie können unabhängig voneinander gelesen werden. Als Beispiel dient durchgängig das Bank-Projekt.

12.3.1. Das UML-Profil "jpa"

Figure 12.2. UML-Profil der Java Plattform-Erweiterung JPA

UML-Profil der Java Plattform-Erweiterung JPA

Figure 12.2, “UML-Profil der Java Plattform-Erweiterung JPA” zeigt das UML-Profil für die Java Plattform-Erweiterung JPA. In dem Profil sind die verschiedenen Stereotypen mit ihren TaggedValues und den dazugehörigen Enumerations enthalten. Die Enumerations werden als Datentypen einiger TaggedValues verwendet, falls diesen fest definierte Werte zugewiesen werden müssen. Dadurch werden dem Modellierer direkt die möglichen Werte (am besten durch das Modellierungstool) vorgegeben und Tippfehler vermieden.

12.3.2. Eine Einführung - Die (Persistent) Entity

Eine normale UML-Klasse wird zu einer (Persistent) Entity, indem der Modellierer diese mit Hilfe des Stereotyps "jpaPersistentEntity" als solche charakterisiert. Jede Persistent Entity benötigt einen Primärschlüssel, der durch das Anhängen des Stereotyps jpaPrimaryKey an ein Attribut oder eine Methode (field oder property access, siehe auch Kapitel über field/property access-Umschaltung) festgelegt wird (siehe Figure 12.3, “Persistent Entity mit den Stereotypen "jpaPersistentEntity" und "jpaPrimaryKey"”).

Figure 12.3. Persistent Entity mit den Stereotypen "jpaPersistentEntity" und "jpaPrimaryKey"

Persistent Entity mit den Stereotypen "jpaPersistentEntity" und "jpaPrimaryKey"

GeneSEZ erzeugt aus dieser Klasse folgenden Quellcode (ohne die automatisch erzeugten getter und setter-Methoden):

			@Entity
			@Table(name = "tbl_Bank")
			public class Bank implements Serializable {
			
				@Id
				@GeneratedValue(strategy = GenerationType.AUTO)
				private int sortCode;
				
				private String name;
				
				@Version
				private int version;
				
				public Bank {
				}
				
				[...]		
						
			}	
		

Die Generierung der Annotationen @Entity sowie @Id lassen sich durch die beiden verwendeten Stereotypen erklären. Woher kommen aber die anderen Stereotypen, das Attribut version, der Standardkonstruktor und das Interface Serializable?

Die Antwort lautet automatische Quellcode-Ergänzung. Gehen wir die verschiedenen Punkte der Reihe nach durch:

  • @Table(name = "tbl_bank"): Diese Annotation entsteht durch die Verwendung des Stereotyps jpaPersistentEntity. Dies ist auch der Grund warum jpaPersistentEntity kein 1:1 Mapping der Annotation @Entity ist und auch nicht genauso benannt wurde. Er enthält zusätzlich die TaggedValues der Annotation @Table. Für die Zusammenfassung der beiden Annotationen zu einem Stereotyp spricht vor allem, dass diese nicht getrennt verwendet werden. Aufgrund der "convention over configuration" ist die Angabe von @Table zwar optional, es können aber bei bestimmten Klassennamen, welche Schlüsselworte in Datenbanken sind, Probleme entstehen. Aus diesem Grund wird automatisch ein Prefix (Standard "tbl_") vor den Klassennamen gesetzt und somit ein Tabellenname erzeugt, welcher Probleme mit reservierten Schlüsselworten in Datenbanken verhindert.

  • Serializable: Die Implementierung dieses Interfaces ermöglicht die Serialisierung der Klasse, was beispielsweise bei der Übertragung über ein Netzwerk benötigt wird. Der Programmierer hat durch die Implementierung dieses Interfaces keinerlei Mehraufwand. Durch die automatische Quellcode-Ergänzung muss die Implementierung dieses Interfaces nicht modelliert werden. Das Modell wird frei von technologiespezifischen Elementen gehalten.

  • @GeneratedValue(strategy = GenerationType.AUTO): Wie im Klassendiagramm zu sehen ist dem TaggedValue generatedValue des Stereotyps jpaPrimaryKey der Wert true zugewiesen worden (Default-Wert: false). Wie bei jpaPersistentEntity verbergen sich auch hinter jpaPrimaryKey mehrere Annotationen, weshalb der Stereotyp auch nicht jpaId genannt wurde. Die Begründung ist analog zu jpaPersistentEntity. Die Annotation @GeneratedValue wird nur in Verbindung mit @Id verwendet. Durch die convention over configuration wird die Standard-Generierungsstrategie der Datenbank überlassen. Dies spiegelt sich im TaggedValue strategy wieder, der als Datentyp die Enumeration jpaGenerationType mit dem Standardwert AUTO besitzt.

    Bei diesem Beispiel wurden GeneSEZ die notwendigen Informationen zur Generierung manuell vom Modellierer vorgegeben. Es wurden keine Werte logisch hergeleitet oder automatisch ergänzt. Die Vorgabe der Standardstrategie basiert auf der JPA-Spezifikation.

  • @Version und das Attribut "version": Die automatische Quellcode-Ergänzung erzeugt für jede Persistent Entity, die über kein Attribut mit @Version verfügt, dieses Attribut und die dazugehörige Annotation für das optimistische Locking. Es handelt sich wiederum um ein technologiespezifisches Element, welches nicht modelliert werden muss.

  • Standardkonstruktor: Zur Erzeugung von JavaBeans durch den Container ist ein parameterloser Standardkonstruktor notwendig. Dieser muss bei einer JavaBean (also auch SessionBeans, MessageDrivenBeans oder Seam-Komponenten) explizit vorhanden sein. Auch um diese technologiespezifische Eigenheit kümmert sich die automatische Quellcode-Ergänzung.

Bei dieser Klasse gibt es bezüglich des Primärschlüssels zwei notwendige Anmerkungen. Da mit der Bankleitzahl (sortCode) nicht gerechnet werden soll, ist die Wahl des Datentyps String hier dem int vorzuziehen. Aus Performanzgründen empfiehlt die JPA-Spezifikation aber auf Primärschlüssel mit dem Datentyp String zu verzichten. Hinzu kommt noch, dass die Vermischung von fachlichen und technischen Merkmalen nicht besonders guter Programmierstil ist. Aus diesem Grund wird dem Attribut sortCode mit Hilfe des Stereotyps jpaColumn eine Unique-Beschränkung verpasst (fachlicher Primärschlüssel) und als technischer Primärschlüssel wird das Attribut id mit dem Datentyp int hinzugefügt. Für das nächste Beispiel (siehe Figure 12.4, “Persistent Entity mit unique-Attribut und ohne jpaPrimaryKey” und ???) wird noch ein Attribut mit dem Namen index hinzugefügt, welches den Platz der Bank in einem fiktiven Bewertungsindex repräsentiert.

Figure 12.4. Persistent Entity mit unique-Attribut und ohne jpaPrimaryKey

Persistent Entity mit unique-Attribut und ohne jpaPrimaryKey

GeneSEZ erzeugt aus dieser Klasse folgenden Quellcode (ohne die automatisch erzeugten getter und setter-Methoden):

			@Entity
			@Table(name = "tbl_Bank")
			public class Bank implements Serializable {
			
				@Id
				@GeneratedValue(strategy = GenerationType.AUTO)
				private int id;
				
				@Column(name = "sortCode", unique=true, nullable=false)
				private String sortCode;
				
				private String name;
				
				@Column(name = "bank_index")
				private String index;
				
				@Version
				private int version;
				
				public Bank {
				}
				
				[...]
								
			}	
		

Auch in diesem Beispiel war die automatische Quellcode-Ergänzung wieder am Werke. Wir schauen uns die Punkte genauer an:

  • @Id und @GeneratedValue(strategy = GenerationType.AUTO): GeneSEZ hat diese Annotationen automatisch an das Attribut id generiert, weil ein Attribut mit diesem Namen in der Java Plattform-Erweiterung JPA für einen Primärschlüssel reserviert ist, sofern kein anderes Attribut den Stereotyp jpaPrimaryKey besitzt. Nur unter bestimmten Umstände wird diese automatische Quellcode-Ergänzung unterlassen (siehe jpaInheritance).

    Bei diesem Beispiel hat GeneSEZ alle notwendigen Informationen durch das Modell erhalten (ein Attribut mit dem Namen id und dem Datentyp int existiert und gleichzeitig besitzt kein anderes Attribut dieser PersistentEntity den Stereotyp jpaPrimaryKey). Es hat eine vollautomatische Ergänzung stattgefunden.

  • @Column(name = "sortCode", unique=true, nullable=false): Die Beschränkung unique verlangt, dass der Wert des entsprechende Attributs ungleich null ist. Leider reicht das Setzen des Annotation-Attributs unique auf den Wert true nicht aus, um diese Folgebedingung zu erfüllen. GeneSEZ sorgt in diesem Fall vollautomatisch für das Setzen des Annotation-Attributs nullable auf den Wert false und lässt eine Kombination unique=true, nullable=false bei der Generierung auch nicht zu.

  • @Column(name = "bank_index"): Der Name dieses Attributs ist ein reserviertes Schlüsselwort in der Datenbank MySQL. Damit es nicht zu Problemen kommt, sorgt GeneSEZ dafür, dass ein anderer Spaltenname verwendet wird (Klassenname + "_" + Attribut-Name). Eine Festlegung des Spaltennamens durch den Modellierer hat natürlich eine höhere Priorität. Es wird dabei von einem mündigen Modellierer ausgegangen, der über die Problematik von Schlüsselwortkonflikten Bescheid weiß. Schließlich treten die Konflikte nicht mit jeder verwendeten Datenbank auf.

Figure 12.5. Persistent Entity ohne technischen Primärschlüssel

Persistent Entity ohne technischen Primärschlüssel

Ein Ziel der Java Plattform-Erweiterung JPA ist das Modell möglichst frei von technologiespezifischen Elementen zu halten. Dies wird nicht immer gelingen, aber bei der Persistent Entity lässt sich noch etwas verbessern. Der technische Primärschlüssel ist ein technologiespezifisches Element und hält man sich an die Trennung von fachlichen und technischen Merkmalen, dann läuft es in den meisten Klassen auf ein zusätzliches Attribut hinaus. Um das Modell von technologiespezifischen Elementen freizuhalten und den Arbeitsaufwand des Modellierers zu verringern, sorgt die automatische Quellcode-Ergänzung dafür, dass jede Persistent Entity einen technischer Primärschlüssel bekommt (Name: id, Datentyp: int), sofern der Modellierer kein anderes Attribut als Primärschlüssel definiert hat. Figure 12.5, “Persistent Entity ohne technischen Primärschlüssel” zeigt die Klasse Bank ohne technischen Primärschlüssel. Am generierten Quellcode ändert sich gegenüber dem letzten Beispiel nichts.