Persistenz Muster Muster rund um den Datenbank Zugrif
Persistenz Muster
Muster rund um den Datenbank Zugrif
Einleitung Persistenz Muster beschreiben verschiedene
Lösungen von Problemen rund um das Speichern und Lesen (Persistieren) von Objekten.o Sie zeigen, wie der Datenzugrif von der
Business-Logik getrennt werden kann.o Sie behandeln Lösungen zu Konflikten beim
Schreiben.o Sie zeigen Lösungen zum Beschleunigen des
Daten-Zugrifs.o Sie behandeln Lösungen zum Vermindern der
Anzahl Zugrife auf die DB.
Beispiele: DAO, Lazy Load, Optimistic Locking, …
DAO Pattern
Data Access Object
Szenario/Problem/Kontext Die meisten Anwendungen benötigen persistente
Daten, d.h. Daten, welche nach Beenden der Applikation erhalten bleiben.
Bei Änderung der Persistenz-Schicht soll die Applikation möglichst unverändert bleiben.
Es gibt Anwendungen, welche auf Daten zugreifen müssen welche sich auf verschiedenen Systemen befinden.
Gewisse Daten werden durch externe Systeme zur Verfügung gestellt: Business-to-Business Integration-Systeme, Kreditkarte Büro-Services, …
Anwendung/Vorteil Ein Data Access Object (DAO) dient als
Verbindung zwischen der Applikation und der Datenquelle
Das DAO dient dazu, alle Zugrife auf die Datenquelle zu abstrahieren und zu kapseln
Die Datenquelle kann beliebig sein: eine relationale Datenbank, ein externer Dienstleister, ein Repository, ein Business-Service,…
Das DAO Objekt
Data Access Objekt als Verbindungsstelle zwischen den Business Objekten und der Data Source (Datenbank)
Das DAO Objekt Business Objekte (Fachklassen) benötigen Zugrif
auf die Datenbank (Data Source). Das DAO Objekt bietet diesen Zugrif als Dienst
(Methode) an. Für den Datenaustausch dient ein Transfer Objekt
(oder ein Fachklassen-Objekt)
Lösung/Implementation
Lesen
Schreiben
Quelle: http://www.oracle.com/technetwork/java/dataaccessobject-138824.html
Lösung/Implementation
Das BusinessObject repräsentiert die Client Daten (Fachklassen). Es benötigt die Daten-Quelle um (indirekt) Informationen zu lesen und zu speichern.
Das DAO abstrahiert den Zugriff auf die Daten-Quelle und enthält Operationen zum Lesen und Schreiben von Daten.
DataSource repräsentiert den Daten Zugriff. Dies kann eine DB Connection, ein File, ein anderes System, … sein. Das DAO regelt alle technischen Aspekte für die konkrete technische DataSource.
(Generisches) Transfer Objekte dient zum Transportieren von Daten von und zu der Daten-Quelle.
BeispielStudenten-Daten aus DB lesen (Fachklasse als Transfer Objekt)
Jeder Fachklasse wird ein DAO-Objekt zugeordnet, welches den Datenzugriff für diese Fachklasse kapselt. Das ermöglicht eine direkte Verbindung zwischen Fachklassen-Objekt und DAO-Objekt
Implementation
Definition Data Source und der Query-Strings
Initialisierung
Implementation
Lesen und Schreiben der Daten
Implementation
Die Fach-Klasse Student ist unabhängig von der Datenquelle:
. . . }
Implementation
Der Client kann mit Hilfe des entsprechenden DAO Objekts einen konkreten Studenten aus der Datenbank lesen:
Verwendung
Das DAO Pattern wird sinnvollerweise angewandt wenn
eine Entkoppelung zwischen Fachklassen und Daten-Quelle sinnvoll erscheint saubere Fachklassen durch Wegkapseln der
technischen Aspekte des Datenzugrifs in das DAO
einfaches Testing (Mocking) verschiedene Daten-Quellen vorhanden sind eine einfache Migration der Daten-Quelle
möglich sein soll
Vorteile
Das DAO versteckt die Implementierungs-Details der Persistenz
Transparenter Zugrif
Wenn die zugrunde liegende Datenquelle ihre Implementierung ändert, bleibt die DAO Schnittstelle erhalten, verlangt also von ihren Kunden keinerlei Anpassungen
einfache Migration
DB-Zugrifs-Code (wie z.B. SQL-Anweisungen) ist in das DAO ausgelagert
Verbesserte Lesbarkeit der Business Objekte
Nachteil
Die DAOs legen eine Indirektion zwischen die Business Objekte und die Datenquelle
Mehr Klassen/Objekte nötig
Viele (neue) Beziehungen über Paket-Grenzen
Eventuell müssen Daten umkopiert werden (beim Benutzen von generischen Transfer-Objekten)
(Connection Objekt in allen DAO Objekten → kann durch Verwendung eines Connection Pools umgangen werden)
Implementation mit DAO Factory
Siehe Factory Pattern
. . . }
Implementation
Die DAOFactory verwaltet die Connection und übergibt sie den DAO Objekten:
Der Client muss das StudentDAO nicht mehr selber instanzieren:
Vorteil / Varianten Entkoppelung der Business-Schicht von den
einzelnen DAO Klassen durch Benutzen einer DAOFactory - sinnvoll, falls es viele DAO-Klassen gibt, z.B.
eine DAO-Klasse für Datenbankzugrif, eine für Filesystemzugrif
Entkoppelung von der DAOFactory durch eine AbstractFactory, falls verschiedene Datenbanken unterstützt werden sollen. Erzeugungsmuster
Praxisbeispiel: Spring Data JPA, Repository-Pattern
Die Fachklasse@Entitypublic class Customer {
@Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; private String firstName; private String lastName;
protected Customer() {}
public Customer(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; }
public Long getId() {return id;
}
public String getFirstName() {return firstName;
}
public String getLastName() {return lastName;
}}
Das Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);}
Der Client@SpringBootApplicationpublic class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {SpringApplication.run(Application.class);
}
@Beanpublic CommandLineRunner demo(CustomerRepository repository) {
return (args) -> {// save a couple of customersrepository.save(new Customer("Jack", "Bauer"));repository.save(new Customer("Chloe", "O'Brian"));
// fetch all customerslog.info("Customers found with findAll():");log.info("-------------------------------");for (Customer customer : repository.findAll()) {
log.info(customer.toString());}log.info("");
// fetch customers by last namelog.info("Customer found with findByLastName('Bauer'):");log.info("--------------------------------------------");for (Customer bauer : repository.findByLastName("Bauer")) {
log.info(bauer.toString());}log.info("");
};}
}
Verweis auf andere Muster
Verwandte Muster sind Transfer Object/Value Objekt
Ein DAO verwendet Transfer Objekte, um Daten von und zu den Business Objekten zu transportieren. Entkoppelung von der Daten-Schicht.
Factory Method/Abstract FactoryUm die Business Objekte von ihren DAOs zu entkoppeln können Factory-Methoden angewandt werden.Falls zusätzliche Flexibilität zur Erzeugung der DAOs nötig ist, kann das Abstract Factory-Muster eingesetzt werden Entkoppelung von der konkreten DB Implementation
Lazy Loading
Verzögertes Laden
Szenario/Problem/Kontext Innerhalb der Anwendung müssen umfangreiche
Objekte aus der Datenbank bereitgestellt werden.
Die Datenbeschafung kostet viel Zeit, da die Objekte sehr gross sind oder die Informationen in verschiedene DB Tabellen verteilt sind.
Für die Datenbeschafung sind viele DB Zugrife nötig.
Der Client benötigt für den nächsten Schritt (die nächsten Schritte) nur einen (kleinen) Teil der Inhalte.
Szenario/Problem/Kontext Beispiel: Cargo Cruise
Die Details einer Buchung, einer Reise, eines Schifes, … werden erst dann benötigt, wenn der Kundenberater auf die Detail-Ansicht klickt (also ev. gar nicht).
Grundsätzliche Frage: Wie viele Daten sollen sinnvollerweise für den nächsten Arbeitsschritt aus der DB geholt werden?
Optimierung von Zeit und Memory
Lösung
Initialisierung
Nachladen von Daten
Implementation
public class Person{ private int id; private String name; private Address homeAddress;
public Person(int id) { // minimal init this.id = id; }
Implementationpublic String Name{
get{ //Nachladen falls nötig
if (name == null)name = DataSource.getPersonName(id);
return name; }}
public Address HomeAddress{
get{ //Nachladen falls nötig
if (homeAddress == null) homeAddress = new Address( DataSource.getStreet(id), DataSource.getCity(id));
return homeAddress; }}
Praxisbeispiel: Java Persistence API
Vorteile Vom Objekt werden nur diejenigen Daten
geladen, welche zu Beginn benötigt werden. Der DB-Zugrif ist einfach und schnell (kurze
Ladezeit). Im Memory liegen nur benötigte Daten.
Nachteile Durch das Nachladen sind mehr DB Zugrife nötig
und benötigen zusätzliche Zeit. Code wird komplexer.
Verweis auf andere Muster Lazy Initialisierung: Minimale Initialisierung,
Nachladen bei jedem get-Zugrif, der auf null verweist.
Ghost: Das Objekt ist anfangs leer, beim ersten Datenzugrif (get) wird ein vordefinierter Teil nachgeladen. Weiteres Nachladen sobald nötig.
Virtual Proxy: Ein Stellvertreterobjekt ist für das Nachladen der Daten zuständig.
Optimisic Lock
Optimistisches Sperren
Szenario/Problem/Kontext Innerhalb der Anwendung müssen viele Objekte
aus der Datenbank gelesen werden.
Viele Mitbenutzer wollen gleichzeitig (im gleichen Daten-Bereich) von der DB Daten benutzen.
Der Client benötigt für den nächsten Schritt viele Informationen, nur wenig wird verändert und später wieder in die Datenbank geschrieben.
Ein normales (pessimistisches) Lock blockiert grosse Teile der Datenbank und alle anderen Benutzer müssen warten.
Szenario/Problem/Kontext Beispiel: Cargo Cruise
Zu den Informationen einer Buchung gehören auch eher «statische» Daten wie die der Reise, des Schifes, der Reederei, des Kunden…
Diese Daten werden selten oder gar nicht verändert und könnten daher gleichzeitig von mehreren Benutzern gelesen werden.
Lösung
Die Datensätze erhalten eine Versionsnummer als zusätzliches Feld.
Alle Lesezugrife werden zugelassen, es gibt keine Sperrung (Lock).
Erst beim Schreibzugrif wird geprüft, ob die Versionsnummer des Datensatzes in der DB noch mit der lokalen Kopie übereinstimmt.
Falls dies der Fall ist, wird der veränderte Datensatz geschrieben und die Versionsnummer erhöht.
Andernfalls wird eine Fehlermeldung (und der neue Datensatz) ausgegeben.
Sequenzdiagramm Beispiel
Quelle: https://martinfowler.com/eaaCatalog/optimisticOfflineLock.html
Implementation
Beispiel : Schreiben des neuen Zustands einer Buchung
update booking set state = ‘reserved’, version = localVersion + 1where id = ‘localId’ and version = localVersion;
Praxisbeispiel: Optimistic Locking bei Java Persistence API
Vorteile Alle Lesezugrife sind uneingeschränkt möglich. Datensätze die nur gelesen wurden müssen nicht
wieder freigegeben werden.
Nachteile Wenn ein update fehlschlägt, muss der Benutzer
ausgehend vom neuen Datensatz seine Eingaben (möglicherweise) nochmals neu eingeben.
Verwendung Durch ein Cache und Merge kann ev. der Fehler
vermieden/gemildert werden.
Das Pattern kann nur eingesetzt werden, wenn Konflikte (sehr) selten sind.
Für die Bereiche, bei denen konkurrierende Schreibbefehle zu erwarten sind, ist pessimistisches Locking zu empfehlen.
Verweis auf andere Muster
Pessimistic Lock Die Datensätze (welche in diesem
Arbeitsschritt möglicherweise verändert werden könnten) werden bereits beim Lesen gegen weiteren Zugrif gesperrt.
Die Datensätze werden erst wieder freigegeben, wenn die Business-Transaktion abgeschlossen ist.
Vorteile Pessimistic Lock
Es gibt keine Schreib-Konflikte. Jeder gelesene Datensatz kann garantiert geschrieben werden.
Nachteile Pessimistic Lock Gleichzeitiger Zugrif auf gleichen Datensatz ist
blockiert → Wartezeit. Gelesene Datensätze müssen explizit wieder
(korrekt) freigegeben werden.