Persistence Layer
Aus Salespoint
(→Datentypen) |
(→Datentypen) |
||
Zeile 86: | Zeile 86: | ||
|align="center"|blob | |align="center"|blob | ||
|} | |} | ||
+ | |||
+ | Die Java Class-Library besteht allerdings aus einer vielzahl weiterer Klassen, die sich nur ggf. speichern lassen. Das größte Problem zeigt sich dort in Wiederherstellung der Objekte. Ein JFrame z.B. enthält Referenzen auf einen Systemabhängigen Grafikkontext, als auch sonstige Betriebssystemschnittstellen. Es ist daher dringend davon abzuraten, diese Klassen persistent zu speichern. | ||
====Annotationen==== | ====Annotationen==== |
Version vom 21:23, 9. Okt. 2010
Ein elementarer Punkt der Datenhaltung ist die Speicherung von transienten Daten auf einem persistenten Datenträger. Dies geschieht meist durch die Anbindung der Applikation an eine Datenbank. Die Aufgabe des Persistence Layer's besteht daher auf Folgendem:
- Sicherung der Objektdaten in der Datenbank
- Wiederherstellung von Objekten aus der Datenbank
- Gewährleistung der Referentiellen Integrität
- Verwaltung von Datenbankverbindungen
Inhaltsverzeichnis |
Komponenten
Die folgende Abbildung zeigt den generellen Aufbau des Persistence Layer's in SalesPoint ab version 4.0, das die bisherige Serialisierung ablöst.
PersistenceManager
Der PersistenceManager ist die zentrale Komponente des PersistenceLayers. Er ist als Singleton implementiert und kann daher mit
PersistenceManager.getInstance();
angesprochen werden. Er Verwaltet die Datenbankverbindungen und sorgt für die Speicherung und Wiederherstellung von Objekten.
persist(Object obj)
Eine zentrale Funktion ist die Methode persist(Object obj). Sie speichert die wichtigen Daten des übergebenen Objektes und gibt einen eindeutigen Wert zurück bzw. null wenn das Objekt nicht gespeichert werden konnte. Rein theoretisch kann jedes Objekt übergeben werden... praktisch jedoch sollten diese Objekt gewisse Eigenschaften haben (siehe ...)
recover(Class class, Object ident)
recover(...) ist das Gegenstück zu persist(...). Es stellt gepeicherte Objekt wiederher. Dazu wird einerseits die Klasse des Objektes und seine eindeutiger Schlüssel benötigt. Die Methode gibt entweder das wiederhergestellte Objekt oder null im Fehlerfall zurück.
Listen
Der PersistenceManager stellt weiterhin Funktionen zur Listenverwaltung zur Verfügung die von persistenten Listen und Maps benutzt werden
Datentypen
Da die Daten letztendlich in eine Datenbank geschrieben werden müssen, nimmt der ausgewählte Datenbanktreiber ein Mapping des Java Datentyps auf den passenden SQL Datentyp vor (Unterscheiden sich trotz Standard). Dieses Mapping kann allerdings nur für bestimmte, einfache Datentypen vorgenommen werden, die in folgendr Tabelle aufgelistet sind:
Java-Typ | Besonderheiten | SQL-Typ (JavaDB) |
---|---|---|
int
java.lang.Integer | integer | |
long
java.lang.Long | bigint | |
java.lang.String | Lange Zeichketten können mittels Annotation auf TEXT gemapped werden. Dies ist allerdings nicht für ID-Felder möglich. | varchar(1024)
text |
boolean
java.lang.Boolean | smallint | |
java.math.BigDecimal | decimal | |
double
java.lang.Double | float | |
float
java.lang.Float | real | |
short
java.lang.Short | smallint | |
java.sql.Timestamp | Objekte, z.B. vom Typ java.util.Date usw. können nicht gespeichert werden. | timestamp |
byte[] | blob |
Die Java Class-Library besteht allerdings aus einer vielzahl weiterer Klassen, die sich nur ggf. speichern lassen. Das größte Problem zeigt sich dort in Wiederherstellung der Objekte. Ein JFrame z.B. enthält Referenzen auf einen Systemabhängigen Grafikkontext, als auch sonstige Betriebssystemschnittstellen. Es ist daher dringend davon abzuraten, diese Klassen persistent zu speichern.
Annotationen
ClassFieldMapper
Der ClassFieldMapper filtert die Daten von Objekten und gibt weitere Informationen zu Klassen. Er ist standardgemäß als DefaultClassFieldMapper implementiert, welcher in der Lage ist Annotationen zu verarbeiten.
ClassNameEncoder
Der ClassNameEncoder kodiert Klassennamen in einen eindeutigen String der als Tabellenname in der Datenbank verwendet werden kann. Der DefaulClassFieldMapper als seine standard Implementierung hashed dazu diesen namen via SHA-1 oder MD5 oder ersetzt einfach einige Zeichen.
DatabaseConnection
Das Interface DatabaseConnection stellt eine Datenbankverbindung mit all seine Verbindungsdaten dar. Es ist selbst speicher- und wiederherstellbar. Die konkreten Implementationen für die verschiedenen Datenbanken übernehmen die Vereinheitlichung der datenbankspezifischen Operationen und Typ Konvertierungen.
DatabaseConnectionTemplates
Diese Templates sind dazu da um neue Datenbankverbindungen zu erzeugen.
PersistentMap
Ist eine Implementation der Map-Schnittstelle für eine persistente Datenbasis. Alles was der Map hinzugefügt wird, ist also aus der Datenbank wiederherstellbar. Die Map verwendet weiterhin einen Cache um Datenbankzugriffe zu vermindern. Jede dieser Maps hat eine eindeutige Id, die aus den im Konstruktor übergebenen Paramtern "host" und "id" zusammengesetzt wird. Maps mit gleicher Id arbeiten somit auf den selben Daten.
PersistentList
Verhält sich ähnlich wie die PeristentMap, nur handelt es sich hierbei um eine Liste
Konsequenzen für die Nutzung des Frameworks
Gespeichert werden sollten CatalogItems, StockItems und User, da diese die zentralen Klassen zur Daten- bzw. Benutzerverwaltung darstellen. Ihre Implementationen: CatalogItemImpl, StockItemImpl und User sind bereits für die Speicherung vorbereitet. Bei der konkreten Ableitung gilt es jedoch einiges zu beachten.
Jedes Objekt muss eindeutig identifizierbar sein Dazu kann entweder ein Attribut der Klasse als eindeutig gekennzeichnet werden oder der Persistence Layer wird selbst einen Schlüssel erzeugen. Folgendes Beispiel zeigt einen Ausschnitt aus der Klasse AbstractNameable, von welcher CatalogItms und StockItems erben:
@PersistenceProperty(isUnique = true)
private String m_sName;
Wie zu sehen ist, wurde das Attribut "m_sName" als eindeutig gekennzeichnet. Dies geschieht durch die Annotation PersistenceProperty. Der vergebene Schlüssel muss nur innerhalb aller Objekte dieser Klasse eindeutig sein. Alle Klassen die jetzt von AbstractNamable erben, haben ebenfalls m_sName als eindeutiges Attribut. Es besteht aber die Möglichkeit dieses durch eine erneute Annotation zu überschreiben. Es ist daher meist nicht notwendig für alle Unterklassen von CatalogItem einen anderen Schlüssel zu vergeben. StockItems hingegen können meist nicht eindeutig durch ihren Namen identifiziert werden. Daher wird für diese ein automatisch generierter Schlüssel vergeben. Folgender Ausschnitt ist aus der Klasse StockItemImpl:
@PersistenceProperty(isUnique = true, autoAssign = true)
private int m_id;
Hier wurde ein Integer Field als eindeutig gekennzeichnet. "autoAssign = true" zeigt hier an, dass ein Schlüssel generiert werden soll. Dieser Schlüssel wird nach dem Speichervorgang in das Feld geschrieben und ist von dort aus abrufbar. Die automatische Schlüsselerzeugung funktioniert nur mit Feldern vom Typ int oder long.
Folgender Code stammt aus der Klasse CatalogItemImpl:
@PersistenceProperty(follow = false)
private CatalogImpl<?> m_ciOwner;
"follow = false" zeigt an, dass der Inhalt dieses Feldes nicht gespeichert werden soll. Dies empfiehlt sich für viele Felder mit komplexen Datentypen, da diese meist nicht für die Speicherung vorbereitet wurden. Vermeiden sie außerdem unbedingt, das Referenzen in irgendeiner Form Kreise bilden, dass heißt: Dass sie irgendwann wieder auf das Ausgangsobjekt zurück verweisen.
Felder vom Typ Array können nicht gespeichert werden. Verwenden sie dazu die Klassen PeristentMap bzw. PersistentList. Ein Beispiel dafür entstammt der Klasse User:
@PersistenceProperty(follow = false)
private Map<String, Capability> m_mpCapabilities = null;
m_mpCapabilities = new PersistentMap<String, Capability>(this, null, String.class, Capability.class);
Sollten alle diese Bedingungen erfüllt sein, kann das Objekt mittels der persist Methode gespeichert werden. Für die Wiederherstellung gibt es auch einiges zu beachten. Das Objekt muss instanziiert werden. Dafür muss entweder ein Konstruktor deklariert, der keine Parameter hat, oder die folgende Methode verwendet werden:
public class Video extends CatalogItemImpl implements Descriptive, Categorizable {
.
.
.
@RecoveryConstructor(parameters = { "m_sName" })
public Video(String name) {
super(name);
}
.
.
.
}
Hier wurde ein Konstruktor explizit als Konstruktor für die Wiederherstellung gekennzeichnet. Dort muss mittels "parameters = {...}" angegeben werden in welcher Reihenfolge, welcher Parameter an den Konstruktor übergeben werden soll. Zur Erinnerung: "m_sName" ist das Feld, welches in AbstractNameable deklariert wurde und somit Bestandteil der Klasse Video ist. Besonderheiten ergeben sich bei Annonymen inneren und Lokalen Klassen: Diese Klassen kriegen implizit eine Referenz auf ihre Äußere Klasse übergeben und dass heisst der PersistentManager muss diese Referenz irgendwoher bekommen. Dies passiert dadurch, dass diese Äußere Klasse ebenfalls neu instanziiert wird, was eigentlich nicht erwünscht ist. Daher ist davon abzuraten diese Klassen für die Speicherung zu verwenden. Wer zu faul ist eine Top Level Klasse zu schreiben, kann dafür statische innere Klassen verwenden, die keine Probleme bereiten sollten.
Nachdem das Objekt instanziiert wurde, werden die gespeicherten Felder (ausgenommen sind Felder die an den Konstruktor übergeben wurden) im Objekt gesetzt. Um nach der Instanziierung eigenen Code auszuführen muss die Klasse das Interface Recoverable implementieren. Folgender Code aus CatalogItemImpl demonstriert dies:
public void recover(Map<String, Object> data, Object recoveryContext, boolean reInit)
{
if (recoveryContext instanceof CatalogImpl)
{
setCatalog((CatalogImpl) recoveryContext);
}
m_chImage = null;
}
Der Parameter data beinhaltet die Werte der einzelnen Felder. Der recoveryContext entspricht in der Regel dem Catalog bzw. Stock in dem sich das Item befindet und reInit gibt an, ob das Objekt zum ersten mal wiederhergestellt wird, oder nur seine Felder aktualisiert wurden.
Beachten sie bitte das zur Speicherung von Zeiten und Daten der Datentyp java.sql.TimeStamp verwendet werden muss.
Persistence Events
Der PersistenceManger produziert verschiedene Events wofür Event Listeners registriert werden können.
DataSourceOnChange
Wird getriggert, kurz bevor die Datenquelle geändert wird, wenn zB die Datenbank zur Laufzeit gewechselt wird. Dieses Event ist insbesondere für Databaskets interessant, da diese ein rollback ausführen sollten.
DataSourceChanged
Wird getriggert sobald sich die Datequelle geändert hat. Dies ist interessant für PersistentMaps und PersistentLists, da diese ihre Caches invalidieren müssen und das Event entsprechend weiter propagiert wird (an Cataloge, Stocks, visuelle Komponenten....)
ExternalModification
SalesPoint unterstützt das verteilte Arbeiten auf der selben Datenquelle. Sollte eine Modifikation auftreten wird dieses Event getriggert. Weiterhin werden ähnliche Maßnahmen getroffen wie bei einem DataSourceChanged Event.