Dienstag, 20. März 2012

Persistieren mit JPA: persist(), merge(), remove(), clear(), close() oder doch flush()?

Welche JPA - Methode sollte in welcher Situation genutzte werden?
JPA bietet einige Methoden an, um mit dem PersistenzManager zu arbeiten:
  • persists()
  • merge()
  • remove()
  • flush()
  • close()
Was passiert beim persist() / flush() / close?
Beim persist() wird die transiente Entität in den Persistenkontext aufgenommen, d.h. die Entität hat eine Verbindung zum EntityManager erhalten. Diese Entität erhält aber nicht sofort eine Repräsentation in der Datenbank!
Wird beispielsweise die Transaktion zurückgerollt, aufgrund eine SQL-Fehlers oder mittels eines expliziten
rollbacks, dann wird die transiente Entität oder Änderungen an anderen Entitäten nicht in der Datenbank abgespeichert:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistence");
EntityManager em = emf.createEntityManager();

...
em.getTransaction().rollback();
...

Möchte man nun innerhalb einer Transaktion an bestimmten Stellen die Synchronisation mit der Datenbank auslösen, dient flush().
Was passiert bei detachten Entitäten beim flush-Aufruf? Nichts.
Was passiert mit persistent Entities, die mit transienten Entities verbunden sind? IllegalStateException
Was passiert mit einer persistent Entity, die mit einer detached Entity verbunden ist? Die Beziehung zur detached Entity wird persistiert, wenn die persistent Entity Besitzer dieser Beziehung ist.

Überlicherweise wird bei Beendigung der Transaktion die gemachten Änderungen an den Entitäten und die neu angelegten persistenten Entitäten in der Datenbank vom JPA-Provider gespeichert.

Bei einem close() wird der EntityManager geschlossen und wie bei clear() werden alle Entitäten vom EntiyManager gelöst.
Diese Entitäten gehen in den Zustand detached über und werden so nicht mehr mit der Datenbank synchronisiert. Um genau diese wieder zu ermöglichen gibt es die Methode merge().
Hiermit können detached-Entitäten wieder in den Persistenzkontext überführt werden.
Dabei werden die Attributwerte der zu mergenden Entität in die bestehende persistente Entität hineinkopiert.
Fall zur detached Entität in der Session keine persistente Entität befindet, dann wird
versucht die betreffende Entität zu laden oder sie wird angelegt.

Eine Entität wird mit remove() aus dem Persistenzkontext genommen und bei Transaktionsende aus der Datenbank gelöscht. Wird auf eine gelöscht markierte Entität merge() aufgerufen, so wird eine IllegalArgumentException geworfen.






Sonntag, 18. März 2012

Hibernate 4.1 neue Features laden per @NaturalId

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


Samstag, 17. März 2012

JPA find() vs. getReference()

Wie werden Entitäten im Zustand persistent in JPA geladen/geholt?
Hierzu gibt es 2 Möglichkeiten neben diversen query-Methoden:

  • find()
  • getReference()
Wie werden beide genutzt?
- Find() wird auf dem EntityManager aufgerufen, die Methode erwartet 2 Parameter:
  1. Typ der angeforderten Entität
  2. Identität: Id der gefordeten Entität
Zurückgeliefert wird die gefundene Entität ohne null.
Beispiel:
EntityManagerFactory ef = Persistence.createEntityManagerFactory("myapp") ;
EntityManager em =  ef.createEntityManager();
....
Kunde k = em.find(Kunde.class, 70992);


- GetReference() wird ähnlich genutzt:
Kunde k = em.getReference(Kunde.class, 70922);
Falls die Entität mit der angegebenen Id nicht im Persistenzkontext vorliegt, wird eine EntityNotFoundException() geworfen.

Wodrin besteht der genaue Unterschied?
Bei find() wird die Entität aus dem Cache des Persistence-Context zurückgegeben oder wenn er nicht vorliegt aus der Datenbank geladen und zurückgegeben.
Bei getReference() wird die Entität erst einmal noch nicht geladen, sondern es wird ein Proxy(ein spezielles Objekt, so genanntes Stellvertreterobjekt mit angereicherten Methoden zum Nachladen der eigentlichen Entität) zurückgegeben - Realisierung über LazyLoading. Erst wenn auf konkrete Attribute zugegriffen wird oder andere Persistenzmethoden aufgerufen werden, greift der Proxy ein und lädt die konkrete Entität nach.

Wann sollte man welche Methode benutzen?
Die Nutzung von find() sollte nach Möglichkeit immer Vorrang vor query-Methode haben, da
find() geladene Entitäten aus dem Cache des Persistenz-Kontext zurückgeben kann.
Wenn man weiß, dass die Entität erst später konkret verwendet wird, ist die Nutzung von
getReference() anzuraten.

JPA Lebenszyklus Transient Persistent Detached

Wie sieht ein Lebenszyklus einer JPA entity aus?
Wenn eine Entität definiert wurde, später instaniiert wird, dann beginnt der Lebenszyklus einer
Entität. Der Lebenszyklus eine Entität endet mit einem remove() bzw. einem detach(siehe weiter unten).
Eine Entität kennt nicht den Zustand seines Lebenszyklus, d.h. sie kann hierzu nicht abgefragt werden.

Welche Stadien durchläuft eine JPA in ihrem Leben?
  • transient
Wurde eine Klasse mit @Entity deklariert, dann handelt es sich bei dieser Klasse um eine Entität, d.h. sie wir im Persistenzkontext verwendet und wird vom EntityManager verwaltet.
Wird diese Klasse instaniiert, dann befindet sie sich im transienten Zustand, d.h. es ist ein einfaches Java-Objekt, dass nicht mit der Datenbank synchronisiert wird.

  • persistent
Wird auf dieser Entität nun mittels des EntityManager persist() - aufgerufen, dann wird die Entität in den Persistenzkontext aufgenommen, d.h. die Entität wurde mit einem/dem EntityManager verbunden.
Die Entität wird nun bei jedem Transaktionsende (commit() oder flush()) mit der Datenbank synchronisiert.

  • detached
Wird eine Entität explizit aus dem Persistenzkontext genommen, durch detach() oder der EntityManager geschlossen via close(), dann wird die Verbindung(siehe persistent()) wieder aufgelöst. D.h. die Entität wird nun nicht mehr mit der Datenbank synchronisiert, wenn z.B. die Transaktionen zu Ende ist.

Eine detached - Entität kann mit merge()-Aufruf wieder in den Persistenzkontext aufgenommen werden.
Eine persistente Entität kann mittels remove()-Aufruf in den Zustand transient überführt werden, die Daten der Entität werden in der Datenbank gelöscht.

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);


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:
  • 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
Konstanten können schön mit Java Enums definiert werden:
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:
  • Polymorphie
  • Performance
Jeder möchte hohe Performance - es gibt jedoch Situationen, in denen Flexibilität und Erweiterbarkeit noch vor der Performance eingestuft wird: bspw. neue Features, die Konfigurationen oder untergeordnete UseCases betreffen.
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

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:
  • 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

Grails2

Neue Grails Version2.0
Während bereits an der Grails-Version 3 gearbeitet wird: Grails Upcoming Version,
hier die ausgelieferten Features von Grails2:
  • Alle Views werden über den Generator in HTML5 mit JQuery erstellt
    • Vorteil: clientseitige Validierung bzgl. Syntax möglich, 
    • bessere Usability: Feedback bei der Eingabe etc.
    • Pflichtfelder wie in der Domänenklasse definiert, werden entsprechend gerendert:
      • im dynamic Scaffolding wird in der Domain-Klasse die statischen constraints definiert und die dynamisch erzeugte View dazu stellt diese Felder als Pflichtfelder dar(mit einem * und validiert diese entsprechend):
                                 class domainX {
                                   String name
                                   ...
                                   static constrains = {
                                        name blank:false
                                  }
                                }
                     Auf der View wird nun name als Pflichtfeld dargestellt
    • Serverfehler(HTTP Statuscode 500) werden nun sauber dargestellt: mit Stacktrace, Message und betreffende Groovy-Klasse
  • bessere Performance: statische Ressourcen wie Bilder, CSS, *.js werden nun clientseitg gepackt und gecacht 
  • Neue Unterstützung für nosql-Datenbanken:
    • Diese können mit GORM-Mittel angegangen werden.
  • Persistenz Features:
    • Detached Queries (wiederverwendbare Queries jenseites einer Persistenz-Session)
    • Typsichere, compile-time secure Where-Queries!
    • neue bequeme Persistenzmethoden: findOrCreate() etc.
    • Abstrakte Domänenklassen
  • Grails-Konsole hat sich eine TAB-Vervollständigung, so wie man es von einer Linux - Konsole gewöhnt ist
  • Fehler werden auf der Grails-Konsole formatiert und farblich dargestellt
  • Controller-Features
    • Actions können jetzt auch Methoden sein und müssen nicht mehr Closures sein
    • Für einen Controller könnten automatisch die Unit-Tests angelegt werden:
      • Sogenannte @TestFor-Annotation mit der Klasse als Parameter: 
        • @TestFor(XYController)

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 und
beide 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?


  1. Aktivieren des Hibernate Second Level Caches
  2. Konfigurieren des Cache-Providers
  3. 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:

  1. Nutzung eines fachlichen Schlüssels
  2. Nutzung/Generierung eines technischen Schlüssels
Variante 1 lässt sich nicht immer für eine Entity finden.
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:
  1. IDENTITY
  2. SEQUENCE
  3. TABLE
  4. 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):