Persistence Layer

Aus Salespoint

(Unterschied zwischen Versionen)
Wechseln zu: Navigation, Suche
(PersistentMap)
(PersistentMap)
Zeile 125: Zeile 125:
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.
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.
-
Seit Version 1.1.0 des Frameworks ist es möglich PersistentLists selbst zu speichern. Sollte eine eigene lesbare ID nicht erforderlich sein, so ist es möglich den Konstruktor ohne Angabe von ID und oder Host aufzurufen und an die Liste wird eine automatisch generierte ID vergeben. Selbiges gilt für die PersistentMap, Catalogs und Stocks.
+
Seit Version 1.1.0 des Frameworks ist es möglich PersistentMaps selbst zu speichern. Sollte eine eigene lesbare ID nicht erforderlich sein, so ist es möglich den Konstruktor ohne Angabe von ID und oder Host aufzurufen und an die Map wird eine automatisch generierte ID vergeben. Selbiges gilt für die PersistentList, Catalogs und Stocks.
===PersistentList===
===PersistentList===

Version vom 21:49, 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.

Persistence Layer

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. Weiterhin Enumerationen (Kann mittels Konstanten umgangen werden), Arrays(ausser das byte Array) und Collections nicht speicherbar. Anstatt Arrays und Collections werden die Datentypen PersistentList und PersistentMap angeboten.

Annotationen

Um die Art und Weise der Peristenz für gewisse Klassen besser kontrollieren zu können, besteht die Möglichkeit Annotationen in der Klassendefinition zu verwenden. Dazu werden die zur Verfügung stehenden Annotationen im Folgenden näher erläutert:

  • PersistenceProperty: Diese Annotation ist gültig für Attribuute einer Klasse.
    • follow (default: true): Hiermit wird gesteuert, ob das annotierte Feld gespeichert werden soll (true) oder nicht (false)
    • isUnique (default: false): Definiert das annotierte Feld als ID-Feld. D.h., dass jede Instanz dieser Klasse eindeutig durch den Wert dieses Feldes identifiziert werden kann. Diese Annotation wird vererbt und kann in abgeleiteten Klassen überschrieben werden. Jede Klasse kan nur ein ID-Feld besitzen.
    • autoAssign (default: false): Nur zulässig fpr ID-Felder. Gibt an, ob diesem Feld beim speichern, automatisch ein eindeutiger Wert zugewiesen werden soll. Der Datentyp diese Felder sollt vom Type int oder long sein.
    • isLongString (default: false): Nicht zulässig für ID-Felder. Datentyp des Feldes muss vom Typ String sein. Erlaubt die Speicherung sehr langer Zeichketten (> 1024 Zeichen)
  • RecoveryProperty: Diese Annotation ist für Klassendefinitionen zulässig und steuert den Wiederherstellungsprozess (Recovery)
    • initialize (default: true): Gibt an, ob die Attribute des Objektes nach dessen Instanzierung automatisch mit den gespeicherten Werten besetzt werden sollen
  • RecoveryConstructor: Diese Annotation ist nur für Konstruktoren zulässig. Sie darf nur einmal in einer Klassendefinition verwendet werden und gibt den Konstruktor an, der für die Recovery genutzt werden sollen. Da jedes Objekt durch einen der definierten Konstruktoren instanziiert werden muss, ist es notwendig einen Konstruktor entsprechend zu annotieren. Ist dies nicht geschehen, so wird der Konstruktor ausgewählt, der keine Parameter benötigt. Ist dieser nicht definiert, kann das Objekt nicht instanziiert werden.
    • parameters: Sofern der Konstruktor Parameter benötigt, müssen entsprechende Werte bei der Instanziierung an den Konstruktor gegeben werden. Dafär können nur Werte von Feldern der Klasse benutzt werden, die gespeichert wurden. Die Namen dieser Felder müssen als String Array angegeben werden (z.B. {"Feld1", "Feld2", ...})

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. Seit Version 1.1.0 des Frameworks ist es möglich PersistentMaps selbst zu speichern. Sollte eine eigene lesbare ID nicht erforderlich sein, so ist es möglich den Konstruktor ohne Angabe von ID und oder Host aufzurufen und an die Map wird eine automatisch generierte ID vergeben. Selbiges gilt für die PersistentList, Catalogs und Stocks.

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.

Persönliche Werkzeuge