distributed-orm.html 48.8 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<p>Als <i>Object-Relational Mapping</i> (kurz <i>ORM</i>) bezeichnen wir in der Softwareentwicklung die Abbildung der Objekte aus einer objektorientierten Programmiersprache auf ein relationales Datenbankschema. In der Praxis ist diese objektrelationale Abbildung in einer Vielzahl von Projekten zu bewerkstelligen, da die Entscheidung für eine objektorientierte Programmiersprache als auch für ein relationales Datenbanksystem sehr verbreitet ist. Zwischen dem objektorientierten und dem relationalen Datenmodell ergeben sich gewisse Unterschiede, die als <a href="https://de.wikipedia.org/wiki/Object-relational_impedance_mismatch">Object-Relational Impedance Mismatch</a> bezeichnet werden. Zwei typische Unterschiede zwischen objektorientiertem und relationalem Modell sind die Abbildung von N:N-Beziehungen und die Abbildung von Vererbungsbeziehungen.</p>

<span>Die Abweichungen zwischen den Modellen werden am Beispiel der folgenden Abbildung verdeutlicht.</span>
<ul>
<li>Im objektorientierten Modell links besteht eine N:N-Beziehung zwischen den Klassen <code>Person</code> und <code>Skill</code>, die im relationalen Modell rechts durch eine zusätzliche Beziehungstabelle <code>skill_acquisition</code> mit Fremdschlüsseln dargestellt werden muss.</li>
<li>Die Vererbungsbeziehung zwischen der allgemeinen Oberklasse <code>Person</code> und ihren Unterklassen <code>Employee</code> und <code>Freelancer</code> im objektorientierten Modell kann im relationalen Modell in drei bekannten, unterschiedlichen Varianten abgebildet werden. In diesem Fall wird die Vererbungshierarchie in einer gemeinsamen Tabelle <code>person</code> zusammengefasst (Variante <i>Single Table</i>).</li>
<li>Explizit erlaubte oder ausgeschlossene Navigierbarkeit – wie zwischen den Klassen <code>Person</code> und <code>Department</code> – kann im relationalen Modell gar nicht dargestellt werden.</li>
</ul>

<img src="media/distributed_orm_impedance_mismatch.png" style="width:600px">
<label>Object-Relational Impedance Mismatch</label>

<p>Einem ORM-Framework kommt nun die Aufgabe zu, die Abbildung zwischen objektorientiertem und relationalem Modell auf Ebene der Programmiersprache durch Annotationen (oder früher durch XML-Konfigurationsdateien) zu ermöglichen. Es ergeben sich allgemein folgende Anforderungen an ein ORM-Framework:</p>
<ul>
<li>Klassen, ihre Attribute, ihre Assoziationen und ihre Spezialisierungen abbilden</li>
<li>Kein SQL in Klassen des Datenmodells</li>
<li>Mittels Assoziationen des objektorientierten Modells in Anfragen an die Datenbank navigieren</li>
<li>Einzelne Objekte und Mengen von Objekten über Anfragen ermitteln</li>
<li>Identität von Objekten gewährleisten</li>
<li>Transaktionen auf Ebene von Objekten</li>
<li>Verbesserte Performance durch Caching</li>
</ul>

<p>Für viele aktuelle Programmiersprachen existieren ORM-Frameworks, wie z.B. das <a href="https://docs.microsoft.com/de-de/ef/core/">Entity Framework</a> für .NET-Sprachen, <a href="https://www.doctrine-project.org/projects/orm.html">Doctrine</a> für PHP oder <a href="https://www.sqlalchemy.org/">SQLAlchemy</a> für Python. In Java gibt es durch <a href="https://jcp.org/en/jsr/detail?id=317">JSR 317</a> und <a href="https://jcp.org/en/jsr/detail?id=338">JSR 338</a> eine standardisierte API für die objektrelationale Abbildung im Package <code>javax.persistence</code>, die sogenannte <i>Java Persistence API (JPA)</i>. Für die JPA existieren verschiedene Implementierungen: die Referenzimplementierung <a href="https://www.eclipse.org/eclipselink/">EclipseLink</a>, <a href="https://openjpa.apache.org/">OpenJPA</a> und insbesondere <a href="https://hibernate.org/">Hibernate</a>. Hibernate ist seit vielen Jahren in der professionellen Praxis am stärksten verbreitet und hat selbst maßgeblich zur Weiterentwicklung der JPA beigetragen. Jede dieser Implementierungen unterstützt die <a href="https://db-engines.com/de/ranking">geläufigen relationalen Datenbanksysteme</a> (u.a. <a href="https://mariadb.org/">MariaDB</a>, <a href="https://www.mysql.com/">MySQL</a>, <a href="https://www.postgresql.org/">PostgreSQL</a>, <a href="https://www.oracle.com/database/">Oracle</a>, <a href="https://www.microsoft.com/en-us/sql-server/">Microsoft SQL Server</a>) sowie ihre z.T. spezifischen SQL-Funktionen und -Datentypen. Die Verwendung der JPA kann also dazu beitragen, den <i>Lock-in-Effekt</i> zugunsten des Datenbanksystem-Herstellers, der üblicherweise mit der Entscheidung für ein konkretes Datenbanksystem einhergeht, abzuschwächen.</p>

<p>Wenn die JPA in einem Projekt eingesetzt werden soll, wird als Abhängigkeit eine JPA-Implementierung (z.B. <a href="https://mvnrepository.com/artifact/org.eclipse.persistence/org.eclipse.persistence.jpa">EclipseLink JPA</a>) und ein JDBC-Treiber für das verwendete Datenbanksystem (z.B. <a href="https://mvnrepository.com/artifact/org.mariadb.jdbc/mariadb-java-client">MariaDB Java Client</a>) benötigt. JPA baut intern auf den Grundlagen von <i><a href="https://jcp.org/en/jsr/detail?id=221">JDBC (Java Database Connectivity)</a></i> auf, um dem Entwickler eine API mit höherem Abstraktionsgrad anzubieten. Um die Unterschiede zwischen der JDBC-API und der JPA-API zu verdeutlichen, werden im Folgenden die Grundlagen von JDBC wiederholt.</p>

<h4>Java Database Connectivity (JDBC)</h4>

<span>JDBC ist die seit langer Zeit standardisierte API für den Zugriff auf relationale Datenbanksysteme aus Java. JDBC ist implementiert im Java-Modul <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/module-summary.html"><code>java.sql</code></a>. Es gibt JDBC-Treiber von nahezu jedem Hersteller eines relationalen Datenbanksystems (z.B. <a href="https://mariadb.com/kb/en/library/about-mariadb-connector-j/">MariaDB</a>, <a href="https://dev.mysql.com/downloads/connector/j/">MySQL</a>, <a href="https://jdbc.postgresql.org/">PostgreSQL</a>, <a href="https://www.oracle.com/technetwork/database/application-development/jdbc/downloads/">Oracle</a>, <a href="https://github.com/Microsoft/mssql-jdbc">Microsoft SQL Server</a>). Der typische Ablauf eines Zugriffs mittels JDBC auf die Datenbank erfolgt in folgenden Schritten:</span>
<ul>
<li>Herstellung einer Verbindung zur Datenbank</li>
<li>Senden eines SQL-Statement, das dynamisch zusammengestellt wird</li>
<li>Verarbeitung der Datensätze, die in einem <code>ResultSet</code>-Objekt zurückgeliefert werden</li>
<li>Mapping zwischen relationalen und objektorientierten Datenstrukturen</li>
</ul>

<img src="media/distributed_orm_jdbc.png" style="width:400px">
<label>Zugriff auf ein relationales Datenbanksystem per JDBC</label>

<p>Der Verbindungsaufbau zur Datenbank (DB) erfolgt über die Klasse <code>java.sql.DriverManager</code>. Es wird implizit auf dem Java-Klassenpfad des Projekts nach einem passenden JDBC-Treiber für das angegebene Protokoll in der URL zum Verbindungsaufbau gesucht. Das im folgenden Code-Beispiel angegebene Protokoll <code>jdbc:mariadb</code> kann z.B. durch den Treiber <code>org.mariadb.jdbc.Driver</code> bedient werden.</p>

<pre><code class="language-java line-numbers">Connection conn = DriverManager.getConnection("jdbc:mariadb://localhost:3306/test?user=foo&password=pass");
conn.setAutoCommit(false);</code></pre>

<p>Der URL können initial Konfigurationsparameter mitgegeben werden, z.B. die Credentials für den DB-User (<code>user=foo&password=pass</code>), eine Zeitzone (<code>serverTimezone=CET</code>), eine Einschränkung auf verschlüsselte Kommunikation mit der DB (<code>useSSL=true</code>) oder der Hinweis, dass auch mehrere SQL-Statements gleichzeitig gesendet werden können (<code>allowMultiQueries=true</code>). Diese Parameter sind z.T. spezifisch für ein konkretes Datenbanksystem und werden in der jeweiligen Hersteller-Dokumentation beschrieben, z.B. <a href="https://mariadb.com/kb/en/library/about-mariadb-connector-j/#optional-url-parameters">URL-Parameter für MariaDB</a>. Wenn die Verbindung zur Datenbank gelingt, wird ein <code>java.sql.Connection</code>-Objekt erzeugt. Mittels des <code>Connection</code>-Objekts können <code>Statement</code>-Objekte erzeugt werden, über die beliebige SQL-Statements ausgeführt werden können. Die SQL-Statements können dynamisch als String zusammengestellt werden, wobei darauf zu achten ist, <a href="https://de.wikipedia.org/wiki/SQL-Injection">SQL-Injections</a> zu vermeiden. Insbesondere Anführungszeichen müssen <i><a href="https://en.wikipedia.org/wiki/Escape_character">escaped</a></i> werden, damit über diese nicht der angedachte Zweck eines SQL-Statement verändert werden kann.</p>

<pre><code class="language-java line-numbers">Statement stmt = conn.createStatement();
stmt.execute("CREATE TABLE department(id INT KEY AUTO_INCREMENT, label VARCHAR(50))");
stmt.execute("CREATE TABLE person(id INT KEY AUTO_INCREMENT, name VARCHAR(50), dept_id INT)");;</code></pre>

<p>Im nächsten Code-Beispiel werden mehrere SQL-Statements zu einer Transaktion gebündelt, die erst mit dem abschließenden Commit (Zeile 9) beendet wird. Eine Transaktion ist insbesondere dann sinnvoll, wenn ein komplexes Objekt persistiert werden soll, das im relationalen Modell Insert- bzw. Update-Statements für mehrere voneinander abhängige Datensätze in verschiedenen Tabellen verursacht. Im Code-Beispiel soll eine Abteilung (<code>Department</code>) inkl. der ihr zugeordneten Personen gespeichert werden, wobei die jeweiligen Ids automatisch durch das Datenbanksystems vergeben werden.</p>

<pre><code class="language-java line-numbers">Department d = new Department("R&D");
d.getStaff().add( new Person("Hendricks") );
d.getStaff().add( new Person("Gilfoyle") );

int deptId = stmt.executeUpdate("INSERT INTO department(label) VALUES('" + d.getLabel() + "')", Statement.RETURN_GENERATED_KEYS);
for (Person p : d.getStaff()) {
	stmt.execute("INSERT INTO person(name, dept_id) VALUES('" + p.getName() + "', " + deptId + ")");
}
conn.commit();</code></pre>

<p>Das folgende Code-Beispiel zeigt ein <code>PreparedStatement</code>, das zum einen bei wiederholter Ausführung effizient ist, da das SQL-Statement vorab geparst und validiert werden kann, und zum anderen mehr Sicherheit bietet, da durch die typsichere Parameterwertzuweisung SQL-Injections verhindert werden.</p>

66
67
<pre><code class="language-java line-numbers">PreparedStatement prepStmt = conn.prepareStatement(
  "SELECT p.id, p.name FROM person p JOIN department d ON p.dept_id = d.id WHERE d.label = ?");
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
prepStmt.setString(1, "R&D");
ResultSet res = prepStmt.executeQuery();</code></pre>

<p>Über das zurückgegebene <code>ResultSet</code> einer SQL-Anfrage kann iteriert werden, um die einzelnen Datensätze zu verarbeiten. Die Werte aus dem <code>ResultSet</code> werden wieder in einer objektorientierten Datenstruktur zusammengeführt – wie im folgenden Code-Beispiel anhand der Klasse <code>Person</code> (Zeilen 2-3) exemplarisch gezeigt wird. Bei einem großen Projekt mit einer Vielzahl von Klassen ist dieses Mapping zwischen objektorientiertem und relationalem Modell mit viel Aufwand verbunden. Ein ORM-Framework automatisiert diesen Schritt, indem es intern zunächst leere Objekte instantiiert (Default-Konstruktur erforderlich) und deren Attribute dann mittels der Reflection API befüllt.</p>

<pre><code class="language-java line-numbers">while (res.next()) {
	Person p = new Person(res.getString("name"));
	p.setId(res.getInt("id"));
	System.out.println(">>> " + p.getId() + ": " + p.getName());
}</code></pre>

<h4>ORM mit der Java Persistence API (JPA)</h4>

<p>Das Mapping mittels Annotationen zwischen objektorientiertem und relationalem Modell soll anhand der beiden Beispielklassen <code>Person</code> und <code>Department</code> erklärt werden, die über eine bidirektionale Assoziation (Attribut <code>Department department</code> in Klasse <code>Person</code> ↔ Attribut <code>List&lt;Person> staff</code> in Klasse <code>Department</code>) und eine unidirektionale Assoziation (Attribut <code>Person lead</code> in Klasse <code>Department</code>) miteinander verbunden sind. Sämtliche Annotationen kommen aus dem Package <code>javax.persistence</code>.</p>

<img src="media/distributed_orm_mapping.png" style="width:400px">
<label>Object-Relational Mapping</label> 

<ul class="nav nav-tabs" id="orm-mapping-tabs" role="tablist">
  <li class="nav-item"><a href="#orm-mapping-tabs-classes" class="nav-link" data-toggle="tab" role="tab">Klassen ohne JPA-Annotationen</a></li>
  <li class="nav-item"><a href="#orm-mapping-tabs-sql" class="nav-link" data-toggle="tab" role="tab">SQL-Statements</a></li>
  <li class="nav-item"><a href="#orm-mapping-tabs-classes-annoted" class="nav-link active" data-toggle="tab" role="tab">Klassen mit JPA-Annotationen</a></li>
</ul>
<div class="tab-content" id="orm-mapping-tabs-content">
  <div class="tab-pane" id="orm-mapping-tabs-classes" role="tabpanel">
	<pre><code class="language-java line-numbers">public class Department {
    int id;
    String label;
    Set&lt;Person> staff;
    Person lead;
}

public class Person {
    int id;
    String name;
    Department department;
}</code></pre></div>
  <div class="tab-pane" id="orm-mapping-tabs-sql" role="tabpanel">
	<pre><code class="language-sql line-numbers">CREATE TABLE department ( 
	id INT PRIMARY KEY AUTO_INCREMENT, 
	label VARCHAR(45) NOT NULL, 
	lead_id INT NOT NULL, 
	FOREIGN KEY (lead_id) REFERENCES person(id) 
);
	
CREATE TABLE person ( 
	id INT PRIMARY KEY AUTO_INCREMENT, 
	name VARCHAR(45) NOT NULL, 
	dept_id INT, 
	FOREIGN KEY (dept_id) REFERENCES department(id) 
);</code></pre></div>
  <div class="tab-pane show active" id="orm-mapping-tabs-classes-annoted" role="tabpanel">
	<pre><code class="language-java line-numbers">import javax.persistence.Entity;
// ...
	
@Entity
public class Department {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    int id;

    @Column(name = "dept_label", nullable = false, length = 45)
    String label;

    @OneToMany(mappedBy = "department")  // bidirectional relationship
    Set&lt;Person> staff;

    @ManyToOne @JoinColumn(name = "lead_id", nullable = false)  // unidirectional relationship
    Person lead;
}

@Entity
public class Person {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    int id;

    @Column(nullable = false, length = 45)
    String name;

    @ManyToOne @JoinColumn(name = "dept_id")  // bidirectional relationship
    Department department;
}</code></pre></div>	
</div>

<ul>
<li><b>@Entity</b>: Jede Klasse, deren Objekte mittels JPA geschrieben oder gelesen werden sollen, müssen mit der Annotation <code>@Entity</code> versehen werden. Ein Objekt, das im JPA-Persistenzkontext bekannt ist, wird als Entität (engl. <i>Entity</i>) bezeichnet.</li>
<li><b>@Id</b>: Das Attribut, das als Primärschlüssel fungieren soll, ist mit der Annotation <code>@Id</code> zu kennzeichnen. Für numerische Ganzzahl-Datentypen kann ein eindeutiger Wert generiert werden (<code>@GeneratedValue</code>). Für jede Entity ist ein Primärschlüssel verpflichtend. Zusammengesetzte Primärschlüssel sind auch möglich.</li>
<li><b>@Column</b>: Die Annotation <code>@Column</code> ist optional. Auch Attribute ohne diese Annotation werden persistiert. Wenn Attribute explizit nicht persistiert werden sollen, sind sie mit der Annotation <code>@Transient</code> zu kennzeichnen. Mittels <code>@Column</code> kann die Spalte im relationalen Modell umbenannt und der Wertebereich des Attributs gezielt eingeschränkt werden, z.B. durch Angabe von <code>nullable=true</code>, <code>unique=true</code>, <code>length=&lt;n></code>, <code>precision=&lt;n></code> oder <code>scale=&lt;n></code>.</li>
<li><b>@OneToMany</b> und <b>@ManyToOne</b>: Die beiden Annotationen kennzeichnen jeweils eine Seite einer 1:N-Beziehung. Auf der Seite von <code>@ManyToOne</code> wird immer ein singuläres Entity-Attribut erwartet (keine Collection). Die Bezeichnung der Fremdschlüsselspalte (<code>@JoinColumn</code>) kann hier optional konfiguriert werden. Auf Seite von <code>@OneToMany</code> wird immer ein Collection&lt;Entity-Attribut erwartet und im Fall einer bidirektionalen Beziehung kann die Steuerung der Beziehung hier an die andere Seite delegiert werden. Im Code-Beispiel kennzeichnet z.B. <code>@OneToMany(mappedBy="department") Set&lt;Person>  ...</code> (Zeile 13), dass diese bidirektionale Beziehung zwischen einer Abteilung und den ihr zugeordneten Personen über das Attribut <code>department</code> in der Klasse <code>Person</code> gesteuert wird. Bei einer unidirektionalen Beziehung gibt es keine entsprechende Gegenseite.</li>
<li><b>@OneToOne</b> und <b>@ManyToMany</b>: Entsprechend den eben genannten Annotationen für 1:N-Beziehungen gibt es auch Annotationen für 1:1-Beziehungen (<code>@OneToOne</code>) und N:N-Beziehungen (<code>@ManyToMany</code>). Bei einer N:N-Beziehung lassen sich mittels der <code>@ManyToMany</code>-Annotation auch die Bezeichnungen der erforderlichen Beziehungstabelle und ihrer Fremdschlüsselspalten konfigurieren.</li>
</ul>

<h4>JPA-Persistenzkontext</h4>

Blanke, Daniela's avatar
Blanke, Daniela committed
163
<p>Der JPA-Persistenzkontext je Session wird durch ein sogenanntes <a href="https://javaee.github.io/javaee-spec/javadocs/javax/persistence/EntityManager.html"><code>EntityManager</code></a>-Objekt verwaltet. Wie in der folgenden Abbildung dargestellt wird, erfolgt in JPA jeder Lese- oder Schreibzugriff auf die Datenbank über Methoden des <i>EntityManager</i>. Das Pendant zum eher abstrakten Begriff <i>EntityManager</i> wird in Hibernate etwas sprechender als <i>Session</i> bezeichnet. Das Interface <code>org.hibernate.Session</code> erweitert dementsprechend das Interface <code>javax.persistence.EntityManager</code>. Der EntityManager verwaltet also sämtliche Entitäten, die während einer Session mit der Datenbank ausgetauscht werden.</p>
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216

<img src="media/distributed_orm_entitymanager.png" style="width:700px">
<label>EntityManager in JPA</label>

<p>Das folgende Code-Beispiel zeigt, wie ein EntityManager über eine zugehörige <a href="https://javaee.github.io/javaee-spec/javadocs/javax/persistence/EntityManagerFactory.html">Fabrik</a> erzeugt werden kann. Die Fabrik selbst ist ein Singleton, das nur einmal im Anwendungskontext benötigt wird.</p>

<pre><code class="language-java line-numbers">EntityManagerFactory emf = Persistence.createEntityManagerFactory("mariadb-localhost");
EntityManager em = emf.createEntityManager();</code></pre>

<p>Bei der Erzeugung der Fabrik wird der Name einer sogenannten <i>Persistence Unit</i> angegeben, hier z.B. <code>mariadb-localhost</code>. Per Konvention wird diese <i>Persistence Unit</i> in einer XML-Konfigurationsdatei namens <code>META-INF/persistence.xml</code> spezifiziert und vom JPA-Framework dort nachgeschlagen. Es folgt die exemplarische Spezifikation der <i>Persistence Unit</i> namens <code>mariadb-localhost</code>, in der u.a. die URL für den Verbindungsaufbau per JDBC und die Credentials für den DB-User angegeben sind. Zu berücksichtigen ist, dass die Eigenschaft <code>&lt;property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/></code> (Zeile 9) dafür sorgt, dass sämtliche Tabellen in der Datenbank bei jedem Anwendungsstart neu erzeugt werden. Das ist natürlich nur zur Entwicklungszeit eine sinnvolle Konfiguration.</p>

<pre><code class="language-xml line-numbers"><!-- <persistence-unit name="mariadb-localhost">
	<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
	<exclude-unlisted-classes>false</exclude-unlisted-classes>
	<properties>
		<property name="javax.persistence.jdbc.driver" value="org.mariadb.jdbc.Driver"/>
		<property name="javax.persistence.jdbc.url" value="jdbc:mariadb://localhost:3306/test"/>
		<property name="javax.persistence.jdbc.user" value="foo"/>
		<property name="javax.persistence.jdbc.password" value="pass"/>
		<property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
		<property name="eclipselink.logging.level.sql" value="FINE"/>
	</properties>
</persistence-unit> --></code></pre>

<p>Das Interface <a href="https://javaee.github.io/javaee-spec/javadocs/javax/persistence/EntityManager.html"><code>EntityManager</code></a> bietet eine Vielzahl von Methoden zur Synchronisation von Entitäten mit der Datenbank über den JPA-Persistenzkontext. Die folgende Tabelle stellt eine Auswahl der grundlegenden Methoden vor.</p>

<table class="table table-bordered table-sm" style="width: auto;">
<tr class="rest-resources-head"><th>Type</th><th>Method and description</th></tr>
<tr><td>boolean</td><td><code>contains(Object entity)</code><br>Check if the instance is a managed entity instance belonging to the current persistence context.
</td></tr>
<tr><td>T</td><td><code>find(Class&lt;T> entityClass, Object primaryKey)</code><br>Find by primary key.
</td></tr>
<tr><td>void</td><td><code>persist(Object entity)</code><br>Make an instance managed and persistent.</td></tr>
<tr><td>T</td><td><code>merge(T entity)</code><br>Merge the state of the given entity into the current persistence context.
</td></tr>
<tr><td>void</td><td><code>remove(Object entity)</code><br>Remove the entity instance.</td></tr>
<tr><td>void</td><td><code>refresh(Object entity)</code><br>Refresh the state of the instance from the database, overwriting changes made to the entity, if any.
</td></tr>
<tr><td>void</td><td><code>detach(Object entity)</code><br>Remove the given entity from the persistence context, causing a managed entity to become detached.</td></tr>
<tr><td>void</td><td><code>clear()</code><br>Clear the persistence context, causing all managed entities to become detached.</td></tr>
</table>

<p>Eine Entität (= Objekt einer Entitätsklasse) ist entweder im Zustand <i>managed</i>, d.h. aktuell durch den JPA-Persistenzkontext zur Synchronisation mit der Datenbank verwaltet, oder im Zustand <i>detached</i>, d.h. losgelöst aus dem JPA-Persistenzkontext. Der EntityManager synchronisiert die Entitäten auf dem Java-Heap im Zustand <i>managed</i> mit deren Abbild in der relationalen Datenbank. Die dazu erforderlichen Schreibzugriffe per SQL erfolgen spätestens beim Commit einer Transaktion oder können manuell mittels der Methode <code>flush</code> forciert werden. Mittels der Methoden <code>persist</code> oder <code>merge</code> können Entitäten in den Zustand <i>managed</i> gebracht werden. Auch die Methode <code>find</code> gibt eine Entität im Zustand <i>managed</i> zurück. Mittels der Methoden <code>detach</code> und <code>clear</code> können Entitäten in den Zustand <i>detached</i> gebracht werden, ohne sie vorher aus der Datenbank zu löschen. Die Methode <code>remove</code> löscht eine Entität aus der Datenbank und bringt sie ebenfalls in den Zustand <i>detached</i>. Wenn die Datensätze in der Datenbank außerhalb des JPA-Kontexts verändert werden, geraten die zugehörigen Entitäten in einen inkonsistenten Zustand und können über die Methode <code>refresh</code> durch Lesezugriffe auf die Datenbank aktualisiert werden.</p>

<p>Neben dem JPA-Persistenzkontext je EntityManager/Session, der als 1st-Level-Cache angesehen werden kann, existiert i.d.R. ein gemeinsamer 2nd-Level-Cache, den sich alle EntityManager-Instanzen teilen. Zur Veranschaulichung dient die folgende Abbildung. Selbst wenn aus einer Anwender-Session noch nicht auf eine bestimmte Entität zugegriffen worden ist, kann eine Anfrage ggf. aus dem 2nd-Level-Cache bedient werden, so dass keine SQL-Anfrage an die Datenbank gesendet werden muss, falls diese Entität kürzlich in einer parallelen Session angefragt worden ist.</p>

<img src="media/distributed_orm_caching.png" style="width:740px">
<label>Caching in JPA</label>

<p>Das folgende Code-Beispiel zeigt wie beide Caches explizit geleert werden können.</p>
<pre><code class="language-java line-numbers">em.clear(); // clear 1st level cache
em.getEntityManagerFactory().getCache().evictAll();  // clear 2nd level cache</code></pre>

Blanke, Daniela's avatar
Blanke, Daniela committed
217
<h4>Transaktionen in JPA</h4>
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296

<p>In dem folgenden Code-Beispiel wird ein <code>Department</code>-Objekt mit einem zugeordneten <code>Person</code>-Objekt persistiert. Zu beachten ist, dass jeder schreibende Zugriff auf die Datenbank explizit in eine Transaktion eingebettet werden muss.</p>

<pre><code class="language-java line-numbers">Department dept = new Department("R&D");
Person person = new Person("Hendricks", dept); // person is associated with dept

em.getTransaction().begin();
em.persist(dept);
em.persist(person);
em.getTransaction().commit();</code></pre>

<p>Der Versuch nur das <code>Person</code>-Objekt ohne das abhängige <code>Department</code>-Objekt zu persistieren, misslingt mit einer aussagekräftigen Fehlermeldung:</p>

<pre><code class="language-java line-numbers">em.getTransaction().begin();
em.persist(person); // try to persist person without persisting associated department in the same transaction
em.getTransaction().commit();</code></pre>

<p><code>>>> Exception in thread "main" javax.persistence.RollbackException: java.lang.IllegalStateException:<br><b>During synchronization a new object was found through a relationship that was not marked cascade PERSIST: model.Department@xxxxxxxx.</b></code></p>

<p> Eine Transaktion kann mittels <code>em.getTransaction().rollback()</code> abgebrochen werden. Im Fehlerfall kann eine <code>RollbackException</code> gefangen und behandelt werden. Es ist davon auszugehen, dass mehrere Transaktionen parallel ausgeführt werden, wenn mehrere Anwender zeitgleich mit einer Anwendung interagieren.</p>

<h4>Kaskadierende Zugriffe auf Entitäten und Lazy Loading</h4>

<p>Voneinander abhängige Objekte können auch als ein zusammengehöriges Aggregat behandelt werden. Dabei werden Attribute mit komplexem Datentyp (d.h. Datentyp entspricht einer anderen Entitätsklasse wie in Zeile 6 des folgenden Code-Beispiels) kaskadierend gespeichert (<code>CascadeType.PERSIST</code>), aktualisiert (<code>CascadeType.MERGE</code>), aus der Datenbank gelöscht (<code>CascadeType.REMOVE</code>), neu geladen (<code>CascadeType.REFRESH</code>) oder aus dem Persistenzkontext gelöst (<code>CascadeType.DETACH</code>). Dazu muss die entsprechende Assoziation zwischen den Klassen als kaskadierend hinsichtlich einer oder mehrerer Methoden des EntityManager eingestellt werden (Zeile 4).</p>

<ul class="nav nav-tabs" id="orm-cascade-tabs" role="tablist">
  <li class="nav-item"><a href="#orm-cascade-tabs-entity" class="nav-link active" data-toggle="tab" role="tab">Person</a></li>
  <li class="nav-item"><a href="#orm-cascade-tabs-main" class="nav-link" data-toggle="tab" role="tab">Application</a></li>
</ul>
<div class="tab-content" id="orm-cascade-tabs-content">
  <div class="tab-pane show active" id="orm-cascade-tabs-entity" role="tabpanel">
	<pre><code class="language-java line-numbers">@Entity
public class Person {

    @ManyToOne(cascade = CascadeType.ALL) // ALL includes PERSIST, MERGE, REMOVE, REFRESH, DETACH
    @JoinColumn(name = "dept_id")
    Department department;
	
	// ...
}</code></pre></div>
  <div class="tab-pane" id="orm-cascade-tabs-main" role="tabpanel">
	<pre><code class="language-java line-numbers">public class Application {

    public static void main(String[] args) {

        // connect to database
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("mariadb-localhost");
        EntityManager em = emf.createEntityManager();

        // create example objects
        Department dept = new Department("R&D");
        Person person = new Person("Hendricks", dept);

        // persist objects to database
        em.getTransaction().begin();
        em.persist(person); // persists person and implicitly associated department
        em.getTransaction().commit();

        // close session
        em.close();
    }
}</code></pre></div>	
</div>

<p>Entsprechend den Schreibzugriffen kann auch das Lesen eines komplexen Objekts aus der Datenbank schrittweise in Teilen (<code>FetchType.LAZY</code>) oder als Ganzes (<code>FetchType.EAGER</code>) erfolgen. Der Default ist das einzelne Entity-Attribute stets <i>eager</i> (<code>@OneToOne</code>- und <code>@ManyToOne</code>-Assoziationen) und Collection&lt;Entity>-Attribute stets <i>lazy</i> geladen werden (<code>@OneToMany</code>- und <code>@ManyToMany</code>-Assoziationen). Das folgende Code-Beispiel zeigt die Klasse <code>Department</code>, deren Attribut <code>lead</code> vom Typ <code>Person</code> <i>eager</i> geladen wird, während das Attribut <code>staff</code> vom Typ <code>Set&lt;Person></code> <i>lazy</i> geladen wird. Die angegebenen <code>FetchType</code>-Werte entsprechen hierbei dem Default und wären daher nicht notwendig.</p>

<pre><code class="language-java line-numbers">@Entity
public class Department {

    @ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = "lead_id")
    Person lead;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "department")
    Set&lt;Person> staff;
	
	// ...
}</code></pre>

Blanke, Daniela's avatar
Blanke, Daniela committed
297
<p>Beim <i>Lazy Loading</i> wird das entsprechende Attribut erst bei einem späteren Zugriff befüllt. Im folgenden Code-Beispiel löst die Zeile 1 eine oder zwei SQL-Anfragen aus: zum einen wird stets der gesuchte Datensatz aus der Tabelle <code>department</code> gelesen, zum anderen der zugehörige Abteilungsleiter ermittelt, falls das Attribut <code>lead</code> in der Klasse <code>Department</code> gesetzt ist. Das Laden der zugeordneten Personen im Attribut <code>staff</code> durch eine SQL-Anfrage auf die Tabelle <code>person</code> erfolgt erst beim ersten Zugriff auf eine Methode dieses Attributs in Zeile 8. Bis dahin ist das Attribut nicht instantiiert (vgl. Zeilen 5-6). Im Debug-Modus kann das Lazy Loading nicht ohne Weiteres beobachtet werden, da der Debugger selbst das Laden der Attribute verursacht, um deren aktuellen Status anzeigen zu können.</p>
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327

<pre><code class="language-java line-numbers">Department dept = em.find(Department.class, 1); // finds person with id = 1
// causes department query: SELECT id, label, lead_id FROM department WHERE id = ?
// causes department lead query, if lead_id is not null: SELECT id, name, dept_id FROM person WHERE id = ?

System.out.println(">>> persons in department: " + dept.getStaff());
// >>> persons in department: {IndirectSet: not instantiated}

System.out.println(">>> persons in department: " + dept.getStaff().size());
// causes department staff query: SELECT id, name, dept_id FROM person WHERE dept_id = ?
// >>> persons in department: 10
</code></pre>

<p>Die Vorteile von Lazy Loading sind eine Verkürzung der initialen Ladezeit und ein zunächst geringerer Hauptspeicherbedarf. Allerdings kann das verzögerte Laden 
später zu einem ungewollten Zeitpunkt während der Anwenderinteraktion geschehen.</p>

<h4>Java Persistence Query Language (JPQL)</h4>

<p>Das  <a href="https://javaee.github.io/javaee-spec/javadocs/javax/persistence/EntityManager.html"><code>EntityManager</code></a>-Interface bietet neben der Methode <code>find</code>, um ein Objekt über seinen Primärschlüssel aus der Datenbank zu lesen, noch weitere Methoden für Datenbankanfragen.</p>

<table class="table table-bordered table-sm" style="width: auto;">
<tr class="rest-resources-head"><th>Type</th><th>Method and description</th></tr>
<tr><td>Query</td><td><code>createNativeQuery(String sqlString)</code><br>Create an instance of Query for executing a native SQL statement, e.g., for update or delete.</td></tr>
<tr><td>StoredProcedureQuery</td><td><code>createStoredProcedureQuery(String procedureName)</code><br>Create an instance of StoredProcedureQuery for executing a stored procedure in the database.</td></tr>
<tr><td>Query</td><td><code>createQuery(String qlString)</code><br>Create an instance of Query for executing a Java Persistence query language statement.</td></tr>
<tr><td>TypedQuery&lt;T></td><td><code>createQuery(String qlString, Class&lt;T> resultClass)</code><br>Create an instance of TypedQuery for executing a Java Persistence query language statement.</td></tr>
<tr><td>TypedQuery&lt;T></td><td><code>createQuery(CriteriaQuery&lt;T> criteriaQuery)</code><br>Create an instance of TypedQuery for executing a criteria query.</td></tr>
</table>

<p>Dazu zählen eine Methode für native SQL-Anfragen und eine Methode zum Aufruf von <i>Stored Procedures</i>. Bevorzugt werden aber Anfragen über die <i>Java Persistence Query Language (JPQL)</i>, die ihrerseits stark an SQL angelehnt ist. JPQL orientiert sich in seiner deklarativen Syntax mit <code>SELECT</code> ... <code>FROM</code> ... <code>WHERE</code> ... <code>GROUP BY</code> ... <code>ORDER BY</code> ... an dem gleichen Gerüst an Schlüsselworten für eine Anfrage wie SQL.
Blanke, Daniela's avatar
Blanke, Daniela committed
328
Viele Operatoren (z.B. <code>LIKE</code>, <code>IN</code>) und Aggregatfunktionen (z.B. <code>COUNT</code>, <code>SUM</code>, <code>AVG</code>) verhalten sich ebenfalls wie in SQL. Ein wichtiger Unterschied ist, dass in der <code>FROM</code>-Klausel nicht die zugrundeliegende Tabelle, sondern die objektorientierte Klasse adressiert wird. Demzufolge ist auch die Navigation entlang von Assoziationen über den Operator <code>.</code> möglich, welcher bei der Übersetzung in eine SQL-Anfrage implizit einen Join verursachen kann. Zur Veranschaulichung dienen die folgenden Beispiele.</p>
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513

<pre><code class="language-sql line-numbers">-- Select all persons and their associated department label (causes a join of the person table and the department table)
SELECT p.id, p.name, p.department.label FROM Person p; 

-- Select the number of persons per department label (causes a join of the person table and the department table)
SELECT p.department.label, COUNT(p) FROM Person p GROUP BY p.department.label; 

-- Select the head of the sales department (result is of type Person)
SELECT d.lead FROM Department d WHERE d.label = "Sales";</code></pre>

<p>Aus einer Java-Anwendung heraus kann JPQL in ungetypten Anfragen (<code>javax.persistence.Query</code>) oder getypten Anfragen (<code>javax.persistence.TypedQuery&lt;T></code>) verwendet werden. Bei getypten Anfragen entspricht die Rückgabe der JPQL-Anfrage einer vorab erstellten Klasse des objektorientierten Datenmodells. Bei einer ungetypten Anfrage ist der Typ der Rückgabe nicht definiert. Das folgende Code-Beispiel veranschaulicht eine ungetypte und eine getypte JQPL-Anfrage. Optional kann eine Anfrage parametrisiert werden (Zeilen 8-9) und hinsichtlich der Anzahl der zurückgegebenen Objekte beschränkt werden (Zeile 3). Wenn max. 1 Objekt in der Rückgabe erwartet wird, ist die Methode <code>getSingleResult</code> zu verwenden (Zeile 10), ansonsten die Methode <code>getResultList</code> (Zeile 4).</p>

<pre><code class="language-java line-numbers"> // Untyped query
Query q = em.createQuery("SELECT p.id, p.name, p.department.label FROM Person p");
q.setMaxResults(100);
List&lt;Object[]> result = q.getResultList();
result.forEach(object -> System.out.println( Arrays.toString(object) ));

// Typed query
TypedQuery&lt;Person> q = em.createQuery(String jpql = "SELECT d.lead FROM Department d WHERE d.label = :dept", Person.class);
q.setParameter("dept", "Sales");
Person leadSales = q.getSingleResult();</code></pre>

<p>Als Alternative zu einer deklarativen JPQL-Anfrage, die als String an die Methode <code>createQuery</code> übergeben wird, gibt es noch die sogenannte <i>Criteria API</i>, über die eine JQPL-Anfrage imperativ zusammengestellt werden kann. Zum Vergleich wird die letztere der beiden obigen JQPL-Anfragen im nächsten Code-Beispiel mittels der Criteria API formuliert. Für Entwickler, die SQL-Anfragen gewohnt sind, erscheint der Code, der durch die Criteria API entsteht, vergleichsweise unübersichtlich.</p>

<pre><code class="language-java line-numbers">// Above typed query in Criteria API
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(Person.class);
Root a = cq.from(Department.class);
ParameterExpression&lt;String> p = cb.parameter(String.class);
cq.select(a.get("lead")).where(cb.equal(a.get("label"), p));
TypedQuery&lt;Person> q = em.createQuery(cq);
q.setParameter(p, "Sales");
Person leadSales = q.getSingleResult();
</code></pre> 

<h4>Nebenläufige Transaktionen in JPA</h4>

<p>Bei parallelen Anwender-Sessions mit schreibenden Datenbankzugriffen sind Transaktionen, die die <a href="https://de.wikipedia.org/wiki/ACID">ACID-Eigenschaften</a> erhalten, erforderlich, um insbesondere sogenannte <a href="https://de.wikipedia.org/wiki/Verlorenes_Update"><i>Lost Updates</i></a> zu vermeiden. Die folgende Abbildung zeigt ein potentielles Lost Update, da beide Transaktionen parallel zueinander dieselbe Entität verändern möchten.</p>

<!--
<pre><code class="language-java">em1.getTransaction().begin();
Department a = em.find(Department.class, 1);
a.setLabel("A1");
em1.getTransaction().commit();
</code></pre> 
<pre><code class="language-java">em2.getTransaction().begin();
Department a = em.find(Department.class, 1);
a.setLabel("A2");
em2.getTransaction().commit();
</code></pre>
-->

<img src="media/distributed_orm_concurrency.png" style="width:800px">
<label>Lost Update bei nebenläufigen Transaktionen in JPA</label>

<p>Voraussetzung für die Konflikterkennung bei nebenläufigen Transaktionen in JPA ist ein Ganzzahl-Attribut in der betroffenen Entity-Klasse, das mit der Annotation <code>@Version</code> versehen ist. Über dieses Attribut gelingt es dem JPA-Framework unterschiedliche Versionen einer Entität voneinander zu unterscheiden. Bezüglich des Sperrverhaltens ist der Default sogenanntes <i>Optimistic Locking</i>. Hierbei ist die Annahme, dass Konflikte zwischen nebenläufigen Transaktionen selten auftreten. Konflikte werden nicht vorab durch das Setzen expliziter Sperren verhindert, sondern erst zum Zeitpunkt des Commit erkannt. Eine Transaktion wird beim Commit ggf. zurückgerollt, falls sie Entitäten gelesen oder verändert hat, deren Zustand während der Transaktionslaufzeit durch eine andere Transaktion verändert und schneller erfolgreich <i>committed</i> worden ist. Es entsteht also ein Wettlauf zum Commit, der zu kurzen Transaktionen diszipliniert. Das folgende Code-Beispiel für <i>Optimistic Locking</i> zeigt, wie eine Transaktion (EntityManager <code>em1</code>) durch eine andere Transaktion (EntityManager <code>em2</code>) überholt wird und dadurch beim Commit auf eine <code>RollbackException</code> (verursacht durch eine <code>OptimisticLockException</code>) stößt.</p>
 
<p>Alternativ kann beim Zugriff auf eine Entität explizit eine exklusive Sperre gesetzt werden, die bis zur Freigabe der Sperre beim Commit verhindert, dass andere Transaktionen die gesperrte Entität verändern können. Dieses Sperrverhalten wird als <i>Pessimistic Locking</i> bezeichnet. Es schränkt die mögliche Parallelität stark ein. Bei einer extensiven Verwendung von Sperren sollten Grundlagen aus dem Datenbankbereich wie das <a href="https://de.wikipedia.org/wiki/Sperrverfahren#Zwei-Phasen-Sperrprotokoll">2-Phasen-Sperrprotokoll</a> berücksichtigt werden. Das folgende Code-Beispiel für <i>Pessimistic Locking</i> zeigt, wie eine durch eine Sperre verzögerte Transaktion (EntityManager <code>em2</code>) auf eine <code>RollbackException</code> (verursacht durch einen <i>Lock-Wait-Timeout</i>) stoßen kann.</p> 
 
<ul class="nav nav-tabs" id="orm-caching-tabs" role="tablist">
  <li class="nav-item"><a href="#orm-caching-tabs-optimistic" class="nav-link active" data-toggle="tab" role="tab">Optimistic Locking in JPA</a></li>
  <li class="nav-item"><a href="#orm-caching-tabs-pessimistic" class="nav-link" data-toggle="tab" role="tab">Pessimistic Locking in JPA</a></li>
</ul>
<div class="tab-content" id="orm-caching-tabs-content">
  <div class="tab-pane show active" id="orm-caching-tabs-optimistic" role="tabpanel">
	<pre><code class="language-java line-numbers">// create 2 concurrent sessions
EntityManagerFactory emf = Persistence.createEntityManagerFactory("...");
EntityManager em1 = emf.createEntityManager();
EntityManager em2 = emf.createEntityManager();

// create managed object
em1.getTransaction().begin();
Department a = new Department("A");
a = em1.merge(a);
em1.getTransaction().commit();

// provoke concurrency conflict: transaction 1 starts, but is not committed
em1.getTransaction().begin();
Department a1 = em1.find(Department.class, a.getId());
a1.setLabel("A1");

// transaction 2 overtakes transaction 1
em2.getTransaction().begin();
Department a2 = em2.find(Department.class, a.getId());
a2.setLabel("A2");
em2.getTransaction().commit();

// transaction 1 is cancelled, if @Version attribute exists in entity class
try {
	em1.getTransaction().commit();
} catch (RollbackException e) {
	e.printStackTrace();
}</code></pre>
<code>>>> javax.persistence.RollbackException caused by: javax.persistence.OptimisticLockException<br>
<b>The object [model.Department@xxxxxxxx] cannot be updated because it has changed or been deleted since it was last read.</b></code>
</div>
  <div class="tab-pane" id="orm-caching-tabs-pessimistic" role="tabpanel">
	<pre><code class="language-java line-numbers">// create 2 concurrent sessions
EntityManagerFactory emf = Persistence.createEntityManagerFactory("...");
EntityManager em1 = emf.createEntityManager();
EntityManager em2 = emf.createEntityManager();

// create managed object
em1.getTransaction().begin();
Department a = new Department("A");
a = em1.merge(a);
em1.getTransaction().commit();

// provoke concurrency conflict: transaction 1 starts, but is not committed
em1.getTransaction().begin();
Department a1 = em1.find(Department.class, a.getId(), LockModeType.PESSIMISTIC_READ); // lock entity
a1.setLabel("A1");

// transaction 2 overtakes transaction 1
em2.getTransaction().begin();
em2.createNativeQuery("SET innodb_lock_wait_timeout = 3").executeUpdate(); // decrease lock-wait-timeout (default is 50 s)
Department a2 = em2.find(Department.class, a.getId());
a2.setLabel("A2");

// commit of transaction 2 is blocked and cancelled after lock-wait-timeout, as transaction 1 has set a lock
try {
	em2.getTransaction().commit();
} catch (RollbackException e) {
	e.printStackTrace();
}

// finally commit transaction 1
em1.getTransaction().commit();</code></pre>
<code>>>> javax.persistence.RollbackException caused by: java.sql.SQLException<br>
<b>Lock wait timeout exceeded; try restarting transaction.</b></code>
</div>	
</div> 

<h4>Vererbung in JPA</h4>

<p>Zur Abbildung von Vererbungsbeziehungen im relationalen Modell gibt es drei geläufige Ansätze, die in der JPA als <i>Single Table</i> (<code>InheritanceType.SINGLE_TABLE</code>), <i>Joined Subclass</i> (<code>InheritanceType.JOINED</code>) und <i>Table per Class</i> (<code>InheritanceType.TABLE_PER_CLASS</code>) bezeichnet werden. Die Vor- und Nachteile der unterschiedlichen Abbildungen von Vererbungshierarchien sollen im Folgenden erläutert werden.</p>

<ul>
<li><b>Single Table</b>: Bei dieser Vererbungsstrategie entsteht lediglich eine breite Tabelle für die komplette Vererbungshierarchie. Eine Diskriminatorspalte wird benötigt, um zu erfassen, von welchem konkreten Typ ein Datensatz ist. Die Tabelle ist bei einer Vererbungshierarchie mit vielen Unterklassen und vielen Attributen in den Unterklassen ggf. dünn besetzt, d.h. die Datensätze haben in vielen Attributen keine sinnvollen Ausprägungen (viele NULL-Werte). 

<pre><code class="language-java line-numbers">@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Person {
    // ...
}
</code></pre>

<img src="media/distributed_orm_single_table.png" style="width:850px">
<label>Object-Relational Mapping: Vererbungsstrategie Single Table</label>

Vorteile:
<ul>
<li>Zugriffe betreffen nur eine Tabelle und erfordern keine Joins.</li>
<li>Polymorphe Anfragen sind einfach (z.B. Namen aller Personen, egal ob Angestellter oder Freelancer).</li>
</ul>
Nachteile:
<ul>
<li>Bei vielen Klassen in der Vererbungshierarchie wird die Tabelle schnell sehr breit und unübersichtlich.</li>
<li>Bei Anfragen nach einem bestimmten Typ (z.B. Namen aller Freelancer) wird die ganze Tabelle gelesen.</li>
</ul><br>
</li>

<li><b>Joined Subclass</b>: Bei dieser Vererbungsstrategie entsteht eine Tabelle je Klasse einer Vererbungshierarchie – auch für abstrakte Oberklassen entsteht jeweils eine Tabelle. Jede Tabelle enthält nur die zusätzlichen Attribute der Unterklasse. Auf die Attribute der Oberklasse wird über einen Fremdschlüssel verwiesen, der gleichzeitig Primärschlüssel der Tabelle ist. Eine Diskriminatorspalte wird in der Tabelle der allgemeinsten Oberklasse benötigt.

<pre><code class="language-java line-numbers">@Entity @Inheritance(strategy = InheritanceType.JOINED)
public class Person {
    // ...
}
</code></pre>

<img src="media/distributed_orm_joined.png" style="width:900px">
<label>Object-Relational Mapping: Vererbungsstrategie Joined Subclass</label>

Vorteile:
<ul>
<li>Entspricht objektorientiertem Modell und 3. relationaler Normalform &rarr; leicht erweiterbar.</li>
<li>Polymorphe Anfragen sind einfach (z.B. Namen aller Personen, egal ob Angestellter oder Freelancer).</li>
</ul>
Nachteile:
<ul>
<li>Zugriffe betreffen i.d.R. mehrere Tabellen und erfordern Joins (z.B. Name und Gehalt eines Angestellten) &rarr; komplexe SQL-Anfragen und bei sehr vielen Datensätzen u.U. schlechtere Performance.</li>
<li>Bei Anfragen nach einem bestimmten Typ (z.B. Namen aller Freelancer) wird die ganze Tabelle der Oberklasse gelesen.</li>
</ul><br>
</li>

Blanke, Daniela's avatar
Blanke, Daniela committed
514
<li><b>Table per Class</b>: Bei dieser Vererbungsstrategie entsteht eine Tabelle für jede konkrete Klasse einer Vererbungshierarchie. Jede Tabelle enthält nicht nur die Attribute der konkreten Unterklasse, sondern auch sämtliche Attribute ihrer Oberklassen. Dadurch gibt es im relationalen Modell keine Fremdschlüssel. Es sind keine Joins erforderlich.
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599

<pre><code class="language-java line-numbers">@Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Person {
    // ...
}
</code></pre>

<img src="media/distributed_orm_table_per_class.png" style="width:720px">
<label>Object-Relational Mapping: Vererbungsstrategie Table per Class</label>

Vorteile:
<ul>
<li>Zugriffe betreffen nur einzelne Tabellen und erfordern keine Joins &rarr; einfache SQL-Anfragen und gute Performance.</li>
<li>Anfragen nach einem bestimmten Typ (z.B. Namen aller Freelancer) sind einfach.</li>
</ul>
Nachteile:
<ul>
<li>Polymorphe Anfragen sind umständlich und erfordern die Vereinigung mehrerer Tabellen (z.B. Namen aller Personen).</li>
</ul>
</li>
</ul>

<h4>Entitätsklassen in REST-Ressourcen</h4>

<p>Wenn die Objekte der Entitätsklassen direkt über eine mit JAX-RS gebaute REST-API ausgegeben werden (ohne sie explizit in DTO-Klassen zu transformieren), ist darauf zu achten, dass Zyklen bei bidirektionalen Beziehungen unterbrochen werden. Im folgenden Code-Beispiel soll eine Anfrage an den Endpunkt <code>/departments/&lt;id></code> nicht die Personen ausgeben, die einer Abteilung zugeordnet sind. Demzufolge ist das Attribut <code>Set&lt;Person> staff</code> mit der Annotation <code>@JsonIgnore</code> versehen (Zeilen 10-11 in Klasse <code>Department</code>). Auf der anderen Seite dieser Beziehung in der Klasse <code>Person</code> ist das Attribut <code>Department department</code> exemplarisch mit der Annotation <code>@JsonIgnoreProperties({"label", "staff"})</code> versehen (Zeilen 10-11 in Klasse <code>Person</code>). Dadurch wird die Ausgabe unter dem Pfad <code>/persons/&lt;id></code> zwar die Abteilung einer Person beinhalten, aber nur mit den Attributen, die nicht explizit ausgeschlossen werden.</p>

<ul class="nav nav-tabs" id="orm-rest-tabs" role="tablist">
  <li class="nav-item"><a href="#orm-rest-tabs-dept" class="nav-link active" data-toggle="tab" role="tab">Entitätsklasse Department</a></li>
  <li class="nav-item"><a href="#orm-rest-tabs-http1" class="nav-link" data-toggle="tab" role="tab">HTTP-Request <code>/departments/&lt;id></code></a></li>
  <li class="nav-item"><a href="#orm-rest-tabs-person" class="nav-link" data-toggle="tab" role="tab">Entitätsklasse Person</a></li>  
  <li class="nav-item"><a href="#orm-rest-tabs-http2" class="nav-link" data-toggle="tab" role="tab">HTTP-Request <code>/persons/&lt;id></code></a></li>
</ul>
<div class="tab-content" id="orm-rest-tabs-content">
  <div class="tab-pane show active" id="orm-rest-tabs-dept" role="tabpanel">
	<pre><code class="language-java line-numbers">@Entity
public class Department {

    @Id
    int id;

    String label;

    @OneToMany(mappedBy = "department")  // bidirectional relationship
	@JsonIgnore
    Set&lt;Person> staff;
}</code></pre></div>
  <div class="tab-pane" id="orm-rest-tabs-http1" role="tabpanel">
	<pre><code class="language-json line-numbers"><!--​---[HTTP GET request]---
Request URL: http://localhost:8080/departments/1

---[HTTP response 200]---
Content-type: application/json; charset=utf-8
{
    "id": 1,
    "label": "R&D"
} --></code></pre></div>
  <div class="tab-pane" id="orm-rest-tabs-person" role="tabpanel">
	<pre><code class="language-java line-numbers">@Entity
public class Person {

    @Id
    int id;

    String name;

    @ManyToOne @JoinColumn(name = "dept_id")  // bidirectional relationship
	@JsonIgnoreProperties({"label", "staff"})
    Department department;
}</code></pre></div>
  <div class="tab-pane" id="orm-rest-tabs-http2" role="tabpanel">
	<pre><code class="language-json line-numbers"><!--​---[HTTP GET request]---
Request URL: http://localhost:8080/persons/2

---[HTTP response 200]---
Content-type: application/json; charset=utf-8
{
    "id": 2,
    "name": "Gilfoyle",
	"department": {
		"id": 1
	}
} --></code></pre></div>	
</div>

<p>Die gezeigten Code-Beispiele zum objekt-relationalen Mapping mittels JPA finden sich im Verzeichnis <a href="/remote/orm" class="repo-link">/remote/orm</a> des Modul-Repository.</p>