Welche Features kommen im neuen Hibernate 4 Release?
Im 4. Release, bzw. genau genommen im 4.1-Release, kann man in Hibernate Entitäten laden mittels
naturalIds.
Was sind naturalIds?
NaturalIds sind Schlüssel mit fachlicher Eindeutigkeit, die in diversen Abfragen / Ladensituationen genutzt werden. Es wird angeraten immer technische Schlüssel zu verwenden, wenn diese Entität
über Attribute mit fachlicher Eindeutigkeit besitzt, können diese mit der hibernate-Annotation
@NaturalId versehen werden. Dazu muss die Property unique sein, da
eine unique-Constraint in der Datenbank auf dieser Spalte angelegt wird.
Wozu der Aufwand @NaturalId - Annotationen zu vergeben?
Angenommen es gibt eine Entität Steuerzahler, dieser hat sicherlich eine property namens
Steuernummer. Die Steuernummer sollte eindeutig sein.
Somit ist die property ein Anwärter für die Definition eines naturalId -Schlüssel.
public class Taxpayer {
@Id
@GeneratedValue
private Long id;
private String firstname;
....
@NaturalId
private String taxnumber;
...
}
Nun ergibt sich der Vorteil bereits in der älteren Hibernate-Versionen mittels Criteria-API das Vorhandensein
eines @NaturalIds in Abfragen zu nutzen:
Session s = ...
Criteria crit = s.createCritera(Taxpayer.class);
Taxpayer t = (Taxpayer) crit.add(Restrictions.naturalId("taxnumber").set("89089083AST8908").uniqueResult();
Neues 4.1 feature:
Mit 4.1 läßt sich ein definiertes @NaturalId in den Entität auf session-Ebene verwenden, d.h. eine Entität kann mittels byNaturalId() geladen werden!
Session s = ...
TayPayer t = (TaxPayer) s.bySessionId(Taxpayer.class).using("taxnumber", "89089083AST8908");
Gibt es Nachteile?
Leider ist @NaturalId ein hibernate specifisches feature.
Man kann leider keine @NaturalIds per JPA definieren.
Es hat auch den Anschein, dass @NaturalId nicht so bald in den kommenden JPA2.1 Standard aufgenommen wird: siehe Early Draft JPA 2.1
Posts mit dem Label Hibernate werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Hibernate werden angezeigt. Alle Posts anzeigen
Sonntag, 18. März 2012
Mittwoch, 14. März 2012
JPA2 Feature Speichern der Reihenfolge von Elementen einer Assoziation
Es gibt nun mit JPA 2 ein neues Feature mit dem man bei einer Assoziation die Reihenfolge der Elemente
sicherstellen kann, d.h. die Reihenfolge der Elemente einer Assoziation, die als Liste geführt wird,
wird in der Datenkbank mitgespeichert.
Gegeben sei eine 1:n - Assoziation.
Die 1-Seite hat die betreffende Collection mit Entitäten der n-Seite.
Diese Collection kann als List definiert sein in der Klasse der Entität.
Möchte man die Reihenfolge der Liste aber immer deterministisch haben, dann muss
die Reihenfolge mitgespeichert werden. Hierzu gibt es eine neue Annotation in JPA 2:
@OrderColumn
@Entity
public class A implements Serializable {
...
@OneToMany
@OrderColumn
private List<B> bs = new ArrayList<B>();
...
}
@Entity
public class B implements Serializable {
...
@Id
@Generated
private Long id;
private String orderNumber;
...
}
In diesem Fall wird in der Entität B ein von der Entität nicht sichtbares Feld angelegt mit dem Namen
BS_ORDER, welches dazu dient, die Reihenfolge der assozierten Entitäten von B zu A festzuhalten.
Somit ergibt sich immer wieder die gleiche Reihenfolge Elemente in der Collection, wenn die Entitäten von A geladen werden z.B. im Modus FetchType.EAGER.
Wann anwenden?
Wenn eine Sortiervorschrift @OrderBy die Zugriffszeit unnötig verschlechtert oder die Sortierung nach fachlichen Attributen / Spalten immer noch eine undefinierte Sortierung hervorbringt.
Das Speichern von Elementen per Key bei einer Assoziation, die als Map realisiert wurde,
war auch mit der vorherigen JPA Version möglich:
Im obrigen Beispiel ist bs nicht als List angegen worden, sondern als Map.
Dann kann mit der Annotation @MapKey der Schlüssel festgelegt werden, mit der man auf
die Collection / Assoziation zugreifen möchte.
Im Default bezeichnet @MapKey immer den primary key der Entität.
Möchte man eine andere Spalte als Schlüssel für die Map definieren, kann man dies mit
@MapKey(name="columnName") festlegen.
So ergibt sich nach obigen Beispiel:
@OneToMany
@MapKey(name="orderNumber")
private Map<String, B> bs = new HashMap<String,B>();
Wozu der keybasierte Zugriff auf Assoziatioen?
Der macht Sinn, wenn man weiß, dass bei dieser Annotation der Zugriff
aus der Fachlogik meistens mit der orderNumber erfolgt und der pk ein technischer Schlüssel ist,
der in anderen Use Cases wie Beispiel Archivierung eher zum Zuge kommt.
Dann lässt sich schön ausdrücken:
String aOrderNumber;
....
B myBEntity = a.getBs().get(aOrderNumber);
sicherstellen kann, d.h. die Reihenfolge der Elemente einer Assoziation, die als Liste geführt wird,
wird in der Datenkbank mitgespeichert.
Gegeben sei eine 1:n - Assoziation.
Die 1-Seite hat die betreffende Collection mit Entitäten der n-Seite.
Diese Collection kann als List definiert sein in der Klasse der Entität.
Möchte man die Reihenfolge der Liste aber immer deterministisch haben, dann muss
die Reihenfolge mitgespeichert werden. Hierzu gibt es eine neue Annotation in JPA 2:
@OrderColumn
@Entity
public class A implements Serializable {
...
@OneToMany
@OrderColumn
private List<B> bs = new ArrayList<B>();
...
}
@Entity
public class B implements Serializable {
...
@Id
@Generated
private Long id;
private String orderNumber;
...
}
In diesem Fall wird in der Entität B ein von der Entität nicht sichtbares Feld angelegt mit dem Namen
BS_ORDER, welches dazu dient, die Reihenfolge der assozierten Entitäten von B zu A festzuhalten.
Somit ergibt sich immer wieder die gleiche Reihenfolge Elemente in der Collection, wenn die Entitäten von A geladen werden z.B. im Modus FetchType.EAGER.
Wann anwenden?
Wenn eine Sortiervorschrift @OrderBy die Zugriffszeit unnötig verschlechtert oder die Sortierung nach fachlichen Attributen / Spalten immer noch eine undefinierte Sortierung hervorbringt.
Das Speichern von Elementen per Key bei einer Assoziation, die als Map realisiert wurde,
war auch mit der vorherigen JPA Version möglich:
Im obrigen Beispiel ist bs nicht als List angegen worden, sondern als Map.
Dann kann mit der Annotation @MapKey der Schlüssel festgelegt werden, mit der man auf
die Collection / Assoziation zugreifen möchte.
Im Default bezeichnet @MapKey immer den primary key der Entität.
Möchte man eine andere Spalte als Schlüssel für die Map definieren, kann man dies mit
@MapKey(name="columnName") festlegen.
So ergibt sich nach obigen Beispiel:
@OneToMany
@MapKey(name="orderNumber")
private Map<String, B> bs = new HashMap<String,B>();
Wozu der keybasierte Zugriff auf Assoziatioen?
Der macht Sinn, wenn man weiß, dass bei dieser Annotation der Zugriff
aus der Fachlogik meistens mit der orderNumber erfolgt und der pk ein technischer Schlüssel ist,
der in anderen Use Cases wie Beispiel Archivierung eher zum Zuge kommt.
Dann lässt sich schön ausdrücken:
String aOrderNumber;
....
B myBEntity = a.getBs().get(aOrderNumber);
Dienstag, 13. März 2012
Behandlung von Value Types in JPA Hibernate
Wie wird mit dem Thema value types in JPA / Hibernate umgegangen?
Zunächst einmal die Definition von value types.
Value types sind keine echten Entitäten. Sie haben keinen Lebenszyklus, dass bedeutet sie werden nicht angelegt, editiert oder gelöscht. Sie gibt es seit Anbeginn des Systems.
Echte Entitäten, also mit @Entity gekennzeichnet, haben sehr wohl einen Lebenszyklus und haben eine entsprechende Repräsentation in der Datenbank.
Nun gibt es diverse Fälle wie man value types nutzen möchte:
public enum EnumXY{
constantValue1, constantValue2, constantValue3;
}
Verwendet werden können die Konstanten in jeder Entität:
@Entity
public class MyEntity implements Serializable {
...
@Enumerated
private EnumXY kennzeichen;
...
}
In der Tabelle wird nun im Default die Position des Wertes(Ordinalzahl) der betreffenden Entität als Integer - Wert gespeichert. Würde also MyEntity das Kennzeichen constantValue3 zugewiesen bekommen, dann stünde der Wert 2 in der Tabelle von MyEntity. Dies kann über @Enumerated(EnumType.ORDINAL) (default) oder @Enumerated(EnumType.STRING) gesteuert werden. Beim letzteren wird der Name der Konstante in der betreffenden Spalte in der Datenbank gespeichert. Hier also constantValue3.
Was ist mit den Fällen, in denen man mehrere Werte eines value types zu einer Entität speichern möchte?
Auch dies ist nun mit JPA 2 möglich:
Mittels @ElementCollection wird deklarativ bestimmt, dass die Collection aus Werten eines value types besteht und sich nicht wie gewöhlich um eine Collection bestehend aus Entitäten handelt.
@ElementCollection
private List<String> basetypeValues = new ArrayList<String>();
Erst einmal CollectionsOfElements ist die Hibernate spezifische Annotation.
In JPA gibt es hierfür @ElementCollection.
Beispiel:
@Entity
public class X implements Serializable {
@Id
@Generated
private Long id;
@ElementCollection
private List<String> remarks = new ArrayList<String>();
...
}
@Test
public void testElementCollection() {
...
X x = new X();
private List<String> remarks = new ArrayList<String>();
remarks.add("remark1");
remarks.add("remark2");
remarks.add("remark3");
x.setRemarks(remarks);
em.persist(x);
...
}
Was passiert in der Datenbank?
Eine "ElementCollection" wird immer in einer separaten Tabelle gespeichert.
Diese kann man noch genauer über @CollectionTable definieren, dabei dient joinColum(s) zur Definition des foreignKey(s):
@CollectionTable(
name = "nameOfCollectionTable",
joinColumns = @JoinColum(name="fk")
)
Resultat:
Table X:
id column1 column2
3 ..... ....
Table Remarks:
id remarks
3 remark1
3 remark2
3 remark3
Zunächst einmal die Definition von value types.
Value types sind keine echten Entitäten. Sie haben keinen Lebenszyklus, dass bedeutet sie werden nicht angelegt, editiert oder gelöscht. Sie gibt es seit Anbeginn des Systems.
Echte Entitäten, also mit @Entity gekennzeichnet, haben sehr wohl einen Lebenszyklus und haben eine entsprechende Repräsentation in der Datenbank.
Nun gibt es diverse Fälle wie man value types nutzen möchte:
- als Konstanten, die bei dem entsprechenden Attribut der Entität in der Datenbank gespeichert wird
- Werte, die lediglich als solche zu einer betreffenden Entität gespeichert werden sollen
public enum EnumXY{
constantValue1, constantValue2, constantValue3;
}
Verwendet werden können die Konstanten in jeder Entität:
@Entity
public class MyEntity implements Serializable {
...
@Enumerated
private EnumXY kennzeichen;
...
}
In der Tabelle wird nun im Default die Position des Wertes(Ordinalzahl) der betreffenden Entität als Integer - Wert gespeichert. Würde also MyEntity das Kennzeichen constantValue3 zugewiesen bekommen, dann stünde der Wert 2 in der Tabelle von MyEntity. Dies kann über @Enumerated(EnumType.ORDINAL) (default) oder @Enumerated(EnumType.STRING) gesteuert werden. Beim letzteren wird der Name der Konstante in der betreffenden Spalte in der Datenbank gespeichert. Hier also constantValue3.
Was ist mit den Fällen, in denen man mehrere Werte eines value types zu einer Entität speichern möchte?
Auch dies ist nun mit JPA 2 möglich:
Mittels @ElementCollection wird deklarativ bestimmt, dass die Collection aus Werten eines value types besteht und sich nicht wie gewöhlich um eine Collection bestehend aus Entitäten handelt.
@ElementCollection
private List<String> basetypeValues = new ArrayList<String>();
Erst einmal CollectionsOfElements ist die Hibernate spezifische Annotation.
In JPA gibt es hierfür @ElementCollection.
Beispiel:
@Entity
public class X implements Serializable {
@Id
@Generated
private Long id;
@ElementCollection
private List<String> remarks = new ArrayList<String>();
...
}
@Test
public void testElementCollection() {
...
X x = new X();
private List<String> remarks = new ArrayList<String>();
remarks.add("remark1");
remarks.add("remark2");
remarks.add("remark3");
x.setRemarks(remarks);
em.persist(x);
...
}
Was passiert in der Datenbank?
Eine "ElementCollection" wird immer in einer separaten Tabelle gespeichert.
Diese kann man noch genauer über @CollectionTable definieren, dabei dient joinColum(s) zur Definition des foreignKey(s):
@CollectionTable(
name = "nameOfCollectionTable",
joinColumns = @JoinColum(name="fk")
)
Resultat:
Table X:
id column1 column2
3 ..... ....
Table Remarks:
id remarks
3 remark1
3 remark2
3 remark3
Montag, 12. März 2012
JPA Vererbung SINGLE_TABLE JOINED TABLE_PER_CLASS
Der Nutzen des Einsatzes eines O/R-Mappers wie z.B. Hibernate unter Verwendnung der JPA-Spezifikation ist ja, dass db-unabhängig entwickelt werden kann und man den Kontext der objektorientieren Welt für Persistenzthemen nicht verlassen muss.
Somit möchte man natürlich Vererbung auch per JPA ausdrücken können.
Hierzu gibt es diverse Vererbungsstrategien:
Default ist SINGLE_TABLE, wenn lediglich die Annotation @Inheritance angegeben wurde.
Es gibt daneben noch 2 andere:
@Inheritance(strategy=InheritanceType.JOINED) und
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS).
Was bedeuten diese Strategien?
Beim Standard(SINGLE_TABLE) werden alle Unterklassen der Vererbungshierarchie auf eine Datenbank-Tabelle gemapped.
Bei JOINED werden alle abstrakten Klassen und konkreten Klassen der Vererbungshierarchie in einer separaten Tabelle persistiert.
Bei TABLE_PER_CLASS wird für jede konkrete Klasse der Vererbungshierarchie eine eigene dedizierte Tabelle angelegt.
Welche Strategie sollte man situationsbedingt auswählen?
Die Antwort auf diese Frage ergibt sich aus vornehmlich 2 Aspekten:
Es gibt Situationen, in der die Vererbungshierarchie wichtige Bewegungsdaten betrifft und im Kern von wichtigen UseCases steht - in dem Fall ist Performance wichtiger als Flexibilität.
SINGLE_TABLE hat den Vorteil des perfomanten Zugriffs, da alles in einer Tabelle gespeichert wird.
Unterscheiden sich die konkreten Unterklassen stark und ist die Anzahl hoch, ergibt sich eine sehr breite Tabelle, so dass sich wiederum ein ungünstiger Tablespace-Bereich ergibt und weniger von der Tabelle von der Datenbank gecacht werden kann. In diesem Fall ist SINGLE_TABLE eine schlechte Wahl.
Nachteil Datenintegrität:
Bei SINGLE_TABLE müssen alle nicht PK-Felder zudem nullable sein, da alle Unterklassen in dieser Tabelle gespeichert werden und einige Typen diverse Spalten nicht verwenden.
Weiß man, dass man in den meisten und wichtigsten Zugriffsfällen konkrete Typen aus dem Persistenzkontext haben möchte, bietet sich TABLE_PER_CLASS an. Bei dieser Strategie sind polymorphe Abfragen sehr unperformant aufgrund von resultierenden UNION-Abfragen und Nutzung polymorpher Beziehung ist nicht möglich, da die abstrakten Typen nicht in der Datenbanktabelle gespeichert sind.
Bei JOINED sind polymorphe Abfragen aufgrund vorliegendem @DescriminatorValue in der Tabelle möglich - realisiert durch outer joins (besser als UNIONS). Konkrete Typenabfrage werden über inner joins realisert - daher der Name.
Realisierung
Es gibt eine abstrakte Klasse. Die Subklassen erben davon und sind @Entity.
Bei SINGLE_TABLE und JOINED muss nach JPA-Standard eine @DiscriminatorValue angegeben werden. Dieser ist vom Typ String und trägt im Default den Namen dtype und dient dazu die konkreten Klassen voneinander zu unterscheiden: In der Datenbank wird in der Tabelle der jeweilige String in die Spalte dtype geschrieben.
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public abstract class BaseClass implements Serializable {
@Id
@GeneratedValue
private Long id;
private String common;
...
}
@Entity
@DiscriminatorValue(value="A")
public class A extends BaseClass {
private String specificA;
...
}
@Entity
@DiscriminatorValue(value="B")
public class B extends BaseClass {
private String specificB;
...
}
Ergibt folgende Tabelle:
dtype|id|common|specificA|specificB
Bei einer Instanz A wird die Spalte specificB immer null sein.
Auf der anderen Seite sind polymorphe Abfragen (select * from BaseClass) oder nach konkrete Subklasse (select * from A ...) hochperformant.
Wäre es TABLE_PER_CLASS sind polymorphe Abfragen nicht möglich.
Bei JOINED werden bei Abfragen nach konkreten Subklassen inner joins generiert,
bei polymorphen Abfragen outer joins.
Somit möchte man natürlich Vererbung auch per JPA ausdrücken können.
Hierzu gibt es diverse Vererbungsstrategien:
Default ist SINGLE_TABLE, wenn lediglich die Annotation @Inheritance angegeben wurde.
Es gibt daneben noch 2 andere:
@Inheritance(strategy=InheritanceType.JOINED) und
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS).
Was bedeuten diese Strategien?
Beim Standard(SINGLE_TABLE) werden alle Unterklassen der Vererbungshierarchie auf eine Datenbank-Tabelle gemapped.
Bei JOINED werden alle abstrakten Klassen und konkreten Klassen der Vererbungshierarchie in einer separaten Tabelle persistiert.
Bei TABLE_PER_CLASS wird für jede konkrete Klasse der Vererbungshierarchie eine eigene dedizierte Tabelle angelegt.
Welche Strategie sollte man situationsbedingt auswählen?
Die Antwort auf diese Frage ergibt sich aus vornehmlich 2 Aspekten:
- Polymorphie
- Performance
Es gibt Situationen, in der die Vererbungshierarchie wichtige Bewegungsdaten betrifft und im Kern von wichtigen UseCases steht - in dem Fall ist Performance wichtiger als Flexibilität.
SINGLE_TABLE hat den Vorteil des perfomanten Zugriffs, da alles in einer Tabelle gespeichert wird.
Unterscheiden sich die konkreten Unterklassen stark und ist die Anzahl hoch, ergibt sich eine sehr breite Tabelle, so dass sich wiederum ein ungünstiger Tablespace-Bereich ergibt und weniger von der Tabelle von der Datenbank gecacht werden kann. In diesem Fall ist SINGLE_TABLE eine schlechte Wahl.
Nachteil Datenintegrität:
Bei SINGLE_TABLE müssen alle nicht PK-Felder zudem nullable sein, da alle Unterklassen in dieser Tabelle gespeichert werden und einige Typen diverse Spalten nicht verwenden.
Weiß man, dass man in den meisten und wichtigsten Zugriffsfällen konkrete Typen aus dem Persistenzkontext haben möchte, bietet sich TABLE_PER_CLASS an. Bei dieser Strategie sind polymorphe Abfragen sehr unperformant aufgrund von resultierenden UNION-Abfragen und Nutzung polymorpher Beziehung ist nicht möglich, da die abstrakten Typen nicht in der Datenbanktabelle gespeichert sind.
Bei JOINED sind polymorphe Abfragen aufgrund vorliegendem @DescriminatorValue in der Tabelle möglich - realisiert durch outer joins (besser als UNIONS). Konkrete Typenabfrage werden über inner joins realisert - daher der Name.
Realisierung
Es gibt eine abstrakte Klasse. Die Subklassen erben davon und sind @Entity.
Bei SINGLE_TABLE und JOINED muss nach JPA-Standard eine @DiscriminatorValue angegeben werden. Dieser ist vom Typ String und trägt im Default den Namen dtype und dient dazu die konkreten Klassen voneinander zu unterscheiden: In der Datenbank wird in der Tabelle der jeweilige String in die Spalte dtype geschrieben.
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public abstract class BaseClass implements Serializable {
@Id
@GeneratedValue
private Long id;
private String common;
...
}
@Entity
@DiscriminatorValue(value="A")
public class A extends BaseClass {
private String specificA;
...
}
@Entity
@DiscriminatorValue(value="B")
public class B extends BaseClass {
private String specificB;
...
}
Ergibt folgende Tabelle:
dtype|id|common|specificA|specificB
Bei einer Instanz A wird die Spalte specificB immer null sein.
Auf der anderen Seite sind polymorphe Abfragen (select * from BaseClass) oder nach konkrete Subklasse (select * from A ...) hochperformant.
Wäre es TABLE_PER_CLASS sind polymorphe Abfragen nicht möglich.
Bei JOINED werden bei Abfragen nach konkreten Subklassen inner joins generiert,
bei polymorphen Abfragen outer joins.
JPA Many-To-Many Beziehungen mappedBy
Eine Many-To-Many-Beziehung wird über die JPA-Annotation @ManyToMany realisiert.
Auf beiden Seiten liegt eine Collection vor mit einer solchen Annotation, wobei eine Seite dabei wieder den Eigentümer/Verwalter der Beziehung innehaben muss - mappedBy:
@Entity
public class A {
@Id
private Long id;
...
@ManyToMany
private List<B> colBs;
}
@Entity
public class B {
@Id
private Long id;
...
@ManyToMany(mappedBy="colBs")
List<A> colAs;
}
Somit ist Entity A Besitzer der n-m-Beziehung.
Eine n-m-Beziehung wird immer in einer separaten dedizierten Tabelle gespeichert:
Der Name ist zusammengesetzt: Entity-Name des Besitzers der Beziehung + _ + Entity-Name der anderen Seite.
In diesem Beispiel: A_B.
Die Foreignkeyspalten für die Herstellung der Beziehung im Default wird nach folgendem Muster erzeugt:
Attributname der referenzierten Seite + _ + PK-Name.
In diesem Beispiel: colbs_id bzw. colas_id
Auf beiden Seiten liegt eine Collection vor mit einer solchen Annotation, wobei eine Seite dabei wieder den Eigentümer/Verwalter der Beziehung innehaben muss - mappedBy:
@Entity
public class A {
@Id
private Long id;
...
@ManyToMany
private List<B> colBs;
}
@Entity
public class B {
@Id
private Long id;
...
@ManyToMany(mappedBy="colBs")
List<A> colAs;
}
Somit ist Entity A Besitzer der n-m-Beziehung.
Eine n-m-Beziehung wird immer in einer separaten dedizierten Tabelle gespeichert:
Der Name ist zusammengesetzt: Entity-Name des Besitzers der Beziehung + _ + Entity-Name der anderen Seite.
In diesem Beispiel: A_B.
Die Foreignkeyspalten für die Herstellung der Beziehung im Default wird nach folgendem Muster erzeugt:
Attributname der referenzierten Seite + _ + PK-Name.
In diesem Beispiel: colbs_id bzw. colas_id
JPA Hibernate one-to-many orphanRemoval
Bei einer 1-n-Beziehung gibt es wieder die Möglichkeit dies uni- oder bidirektional auszudrücken:
Unidirektional:
Auf der 1-Seite liegt eine Collection vor, die nach JPA mit der Annotation @OneToMany versehen wird.
Die Collection besteht aus Entitäten der n-Seite, zwischen denen die Beziehung ja auch ausgedrückt werden soll.
Diverse Parameter können gesetzt werden:
- mappedBy
Der Besitzer der Beziehung. Wird dies bei der 1-Seite aufgeführt, dann ist die n-Seite der Besitzer der Beziehung bzw. Verwalter. Hier wird also die Instanzvariable der n-Seite angegeben. Dadurch wird auf der n-Seite das Fremschlüsselfeld(default) geführt, damit dbseitig die Beziehung hergestellt werden kann.
- cascade
- fetch
Soll die Beziehung bidirektional sein, dann wird auch auf der n-Seite eine Annotation bei der entsprechenden Instanzvariable gesetzt:
@ManyToOne
Hier gibt es interessantere Annotationen/Parameter, die gesetzt werden können.
Doch zunächst der Standard. Wird kein Name via @JoinColumn definiert, so erhält die Fremdschlüsselspalten folgenden Namen: Name der Instanzvariable + _ + Name des PK-Feldes
- @JoinColumn mit den Parametern:
Unidirektional:
Auf der 1-Seite liegt eine Collection vor, die nach JPA mit der Annotation @OneToMany versehen wird.
Die Collection besteht aus Entitäten der n-Seite, zwischen denen die Beziehung ja auch ausgedrückt werden soll.
Diverse Parameter können gesetzt werden:
- mappedBy
Der Besitzer der Beziehung. Wird dies bei der 1-Seite aufgeführt, dann ist die n-Seite der Besitzer der Beziehung bzw. Verwalter. Hier wird also die Instanzvariable der n-Seite angegeben. Dadurch wird auf der n-Seite das Fremschlüsselfeld(default) geführt, damit dbseitig die Beziehung hergestellt werden kann.
- cascade
- fetch
Soll die Beziehung bidirektional sein, dann wird auch auf der n-Seite eine Annotation bei der entsprechenden Instanzvariable gesetzt:
@ManyToOne
Hier gibt es interessantere Annotationen/Parameter, die gesetzt werden können.
Doch zunächst der Standard. Wird kein Name via @JoinColumn definiert, so erhält die Fremdschlüsselspalten folgenden Namen: Name der Instanzvariable + _ + Name des PK-Feldes
- @JoinColumn mit den Parametern:
- referencedColumnName - Name des Fremdschlüsselfeldes
- nullable - Muss die Beziehung gesetzt sein?
- orphanRemoval - bei true wird beim Löschen der Entität auf der 1-Seite alle referenzierten Entitäten der n-Seite gelöscht. Dies macht bspw. bei einer Komposition Sinn. In einigen Fällen kann auch über die Alternative @Embeddable bzw. @Embedded nachgedacht werden. Zur Realisierung der Komposition, d.h. Einzelbestandteile der Komposition werden über die Komposition gemanaged und Einzelteile alleine dürfen designtechnisch nie vorkommen, sollte man dann bei mappedBy die 1-Seite als Besitzer eintragen. Somit wird beim Löschen der Entität auf der 1-Seite vom JPA-Provider bspw. Hibernate auch die n-Seite gelöscht.
- cascade - bspw. kann hier über cascade = CascadeType.PERSIST festgelegt, dass bei einem persist-Aufruf alle referenzierten Entitäten der n-Seite ebenfalls persistiert werden.
- fetch - bzgl. Performance / Ladeverhalten sollte hier genau überlegt werden
Samstag, 10. März 2012
binäre Assoziation JPA
1-zu-1-Beziehung mit JPA
Hierfür dient die JPA-Annotation @OneToOne.Im Standard wird dies über einen ForeignKey datenbankseitig abgebildet.
Unidirektional
Bei einer unidirektionalen @OneToOne-Beziehung enthält die Entität von der aus auf die andere Entität navigiert werden kann, die @OneToOne-Annotation.
mappedBy
Soll die 1-zu-1-Beziehung zwischen zweier Entitäten bidirektional sein,dann muss in beiden Entity die @OneToOne-Annotation gesetzt sein und über das mappedBy-Attribut
wird gesagt, welche Seite die Beziehung verwaltet. Die Beziehung mit der Entität mit dem mappedBy wird von der anderen Entität verwaltet und es wird dabei auf die referenzierte Property verwiesen.
Letztendlich wird gesagt, auf welcher Seite das foreignkey-Spalte vorliegen soll.
Die Entität ohne dem mappedBy-Attribut bekommt dabei in der entsprechenden Tabelle eine ForeignKey-Spalte, um die Beziehung darstellen zu können - der Name leitet sich ab:
Tabellenname der referenzierten Tabelle + _ + Spaltenname des PKs
@Entity
public class A {
@OneToOne
B b;
...
}
@Entity
public class B {
@OneToOne(mappedBy="b")
A a;
Persistieren @OneToOne per JPA
Beim Persistieren einer @OneToOne-Beziehung muss man die Beziehung programmatisch setzen undbeide mittels persist() abspeichern.
cascade=CascadeType.PERSIST
Um zu vermeiden, dass beide Entitäten einer @OneToOne-Beziehung einzeln gespeichert werden müssen, kann man sich das cascade-Attribut bei der @OneToOne-Beziehung zu Nutze machen:@OneToOne(cascade=CascadeType.PERSIST).
Dadurch werden die Beziehung einer Entität mitgespeichert.
Bei einer OneToOne-Beziehung "besitzt" die Entität ohne das mappedBy-Attribut, die Assoziation
und verfügt daher über die ForeignKey-Spalte und die wird gleichermaßen gespeichert, also auch die damti verknüfte Entität. Somit müssen nicht mehr auf beide Entitäten ein persist() aufgerufen werden,
ein persist() auf Entität A genügt, B wird ebenfalls gespeichert.
optional
Bei der Angabe von einer @OneToOne - Beziehung kann über den Parameter optional festgelegt werden, ob die Beziehung gesetzt sein muss oder vorliegen kann:@OneToOne(optional=true)
Donnerstag, 8. März 2012
Caching via Hibernate JPA
JPA Caching
Um eine bestehende Applikation zu optimieren, bietet es sich an, dass man Katalogdaten bzw. Stammdaten cacht. In Abgrenzung zu Bewegungsdaten, die sich auf bestimmte Vorgänge beziehen und nur punktuell benötigt werden, werden Stammdaten in jedem Prozess/Vorgang benötigt unabhängig vom konkreten Geschäftsvorfall.Somit bietet es sich an solche Daten als Kandidaten fürs Cachen vorzusehen.
Sogenannte Schlüsseldaten oder Katalogdaten kennzeichnen sich dadurch aus, dass sie ständig gelesen werden, aber ganz selten verändert werden.
Wie können nun Caching-Strategien mit JPA bzw. Hibernate durchgeführt werden?
- Aktivieren des Hibernate Second Level Caches
- Konfigurieren des Cache-Providers
- Herausfiltern der Caching-Kandidaten und Kennzeichnen der Entities/Assoziationen
1. Aktivieren des Hibernate Second Level Caches
In der persistence.xml bzw. hibernate.cfg.xml wird die Property hibernate.cache.use_second_level_cache auf true gesetzt. Damit wird neben den First-Level-Cache, der alle gemanagten Entitäten der jeweiligen Hibernate Session beinhaltet, ein zweiter Cache in Hibernate aktiviert, der transaktionsübergreifend arbeitet.
Der First-Level-Cache ist an die aktuelle Transaktion gebunden und wird somit geleert, sobald die Transaktion abgeschlossen wurde.
Der Second-Level-Cache ist nicht an der Hibernate-Session gebunden, sondern ist gebunden an dem EntityManager bzw. an der HibernateSessionFactory. Dadurch können Entities zurückgegeben werden, ohne das hierzu eine Rückfrage an die Datenbank erfolgen muss, wenn diese Entities nicht in der aktuellen Transaktion ermittelt wurden, sondern in Transaktionen zuvor gelesen/geschrieben worden sind.
2.Konfigurieren des Cache-Providers
Hibernate kommt mit einer eigenen Implementierung für den Second-Level-Cache.
Dies ist aber nur eine Testimplementierung und ist keinesweges für den produktiven Einsatz gedacht.
Somit muss man den jeweiligen Cacheprovider auswählen und in der Konfiguration setzen:
hibernate.cache.region.factory_class
Es gibt einige Cacheprovider: JBoss Cache, Infinispan, ...
Infinispan wird der zukünftige Cacheprovider im JBoss sein und ist sehr praxiserprobt.
Die Einführung der Regions(Interface region.factory_class) ist neu.
Es gibt die Möglichkeit auch Query-Ergebnisse zu cachen. Die Regeln und Vorgehensweisen für einen solchen Cache unterscheiden sich für einen applikationsweiten Second-Level-Cache, der Entitäten transaktionsübergreifend cacht.
Somit kann der Second-Level-Cache in unterschiedliche Regionen unterteilt werden, damit die unterschiedlichen Cachemechanismen, Eviction-Policys etc. greifen können.
3.Herausfiltern der Caching-Kandidaten und Kennzeichnen der Entities/Assoziationen
Damit eine Entität von Hibernate gecacht wird, muss sie mit einer Annotation versehen werden:
@org.hibernate.annotations.Cache bzw. JPA @javax.persistence.Cacheable
Eine Assoziation muss in der jeweiligen Getter-Methode der betreffenden Entität mit der hibernate-Annotation @org.hibernate.annotations.Cache gekennzeichnet werden.
Folgendes Buch befasst sich im Kapitel 9 bzgl. Caching und auch Query Cache:
Mittwoch, 7. März 2012
Behandlung von IDs / Performance und Optimierung
Wird ein Object/Relation Mapper wie bspw. Hibernate verwendet, so muss jede gemanagte Entity, die bspw. in einer Tabelle persistiert wird, eine ID besitzen.Es muss also etwas geben, dass eine solche Entity eindeutig identifiziert.
In JPA dient die Annotation @Id dazu.
Die Attribute zur Identifizierung können nach folgenden Aspekten ausgewählt werden:
- Nutzung eines fachlichen Schlüssels
- Nutzung/Generierung eines technischen Schlüssels
Für Variante 2 müssen für neue Objekte neue IDs generiert werden.
JPA Generatoren
Unter JPA kommen hier Generatoren zum Einsatz:
@GeneratedValue
Es gibt 3 Generatoren, die über den Annotation-Parameter strategy ausgewählt wird:
- IDENTITY
- SEQUENCE
- TABLE
- AUTO
IDENTITY
Bei IDENTITY kommt eine Autoincrement-Spalte zum Einsatz, wenn die darunterliegende DB dies unterstützt.SEQUENCE
Bei SEQUENCE wird ein eigener DB-seitiger Generator verwendet, der sich um das Hochzählen des Wertes kümmert.TABLE
Bei TABLE wird eine Tabelle hibernate_sequences angelegt, die die Werte des Generators hält.AUTO
Bei AUTO entscheidet hibernate anhand des eingestellten db-Dialekts, welcher Generator am Besten zu verwenden ist.
JPA PERFORMANCE
Performance
Grundsätzlich bedeuten technische Schlüssel eine schlechtere Performance, da beim persist der EntityManager/HibernateSessionFactory die von der DB vergebene ID auslesen muss.
Wehe den Datenbanken, die diese wichtige Operation per API nicht speziell anbieten.
Gibt es vom Treiber keine spezielle Operation bzw. Möglichkeit dies effizient einzulesen,
dann sind schlechtere Insert-Operation vorprogrammiert wie auf anderen Datenbanken.
Die AUTO-strategy kann auf bestimmten Datenbanken Performance kaputtmachen.
So kommt bei der Oracle-Datenbank eine Sequenz zum Einsatz, aber mit leider einer vorkonfigurierten allocationSize von 1.
Dies kann bei AUTO-strategy nicht eingestellt werden und bedeutet somit,
dass die Insert-Performance sehr schlecht ist, da nach jedem Insert über den Treiber die nächste verfügbare ID von der Sequenz ausgelesen werden muss.
So wird im Standard bei manueller Anlage oder bei Nutzung von Datenbanktools auf der DB2 und Oracle Sequences angelegt, die die nächsten 20 IDs cachen, so dass nicht nach jeder Einfügeoperation das Sequenzobjekt hierzu befragt werden muss.
CREATE SEQUENCE XY
START WITH 1
INCREMENT BY 1
NO MAXVALUE
NO CYCLE
CACHE 20;
INSERT INTO ORDERS (ORDERNO, CUSTNO)
VALUES (NEXT VALUE FOR XY, 123456);
Performance @GeneratedValue
Ist besondere Performance gefragt, kann man die Annotation @GeneratedVaule rausnehmen, so dass der JPA-Provider nicht mehr in der Verantwortung ist, eine ID zu ermitteln und bei persist zu setzen.
Dann muss man selber für eine ID-Generierung sorgen, dies kann hinsichtlich schwacher Treiber, deutlich schneller sein.
UUID-Klasse
Im java.util-Package gibt es seit Java 1.5 eine UUID-Klasse mit der man UUIDs generieren kann.
Eventuell sichert man dies noch mit eindeutigen Informationen aus dem jeweiligen Kontext ab, so dass die UUID wirklich eindeutig ist und nie per Zufall doppelt vergeben werden kann.
Dies hätte den Vorteil, dass nach der persist()-Methode ein Nachschlagen des vergeben Wertes für den DB-Treiber wegfiele.
Eine gute weiterführende Lektüre zum Thema ID Behandlung bietet folgendes Buch
(Kapitel 4 Abschnitt Mapping the Primary Key):
Eine gute weiterführende Lektüre zum Thema ID Behandlung bietet folgendes Buch
(Kapitel 4 Abschnitt Mapping the Primary Key):
Abonnieren
Posts (Atom)