Persistence Layer
Aus Salespoint
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.
Es sei gesagt, dass es sich bei dem Persistence Layer um eine komplexe Angelegenheit handelt und seine Funktion hier nur umrissen werden kann.
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 numm im Fehlerfall zurück.
Listen
Der PersistenceManager stellt weiterhin Funktionen zur Listenverwaltung zur Verfügung die von persistenten Liste und Maps benutzt werden
ClassFieldMapper
Der ClassFieldMapper filtert die Daten von Objekten und gibt weitere Informationen zu Klassen. Er ist standardmässig 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 wiederherstell-bar. Die konkreten Implementationen für die verschiedenen Datenbanken übernehmen die Vereinheitlichung der Datenbank spezifischen Operationen und Typ Konvertierungen.
DatabaseConnectionTemplates
Dise 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 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 indentifizierbar 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 Attrubut "m_sName" als eindeutig gekennzeichnet. Dies geschieht durch die Annotation PersistenceProperty. Der vegebene 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 zu überschreiben durch eine erneute Annotation. Es ist daher für alle Unterklassen von CatalogItem in der Regel nicht notwendig einen anderen Schlüssel zu vergeben. StockItems hingegen können meist nicht eindeutig durch ihren Namen indentifiziert 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 Feld 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 Shlü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 ausserdem unbedingt, das Referenzen in irgendeiner Form Kreise bilden, dass heisst: Dass sie irgendwann wieder auf das Ausgangsobjekt zurück verweisen.
Felder com Typ Array können nicht gespeichert werden. Verwenden sie dazu die Klassen PeristentMap bzw. PersistentList. Ein Beispiel dafür entstammt der Klass 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 wie Wiederherstellung gilt es auch einiges zu beachten. Das Objekt muss instanziert werden. Dafür muss entweder ein Konstruktor deklariert werden 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 Äussere Klasse ebenfalls neu instanziert wird, was in der Regel nicht erwünscht is. 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 instanziert wurde, werden die gespeicherten Felder (aussgenommen sind Felder die an den Konstruktor übergeben wurden) im Objekt gesetzt. Um nach der Instanzierung 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.