Vielleicht wissen Sie bereits etwas über die Open-Source- Celesta- Bibliothek. Wenn nicht, spielt es keine Rolle, jetzt erzählen wir Ihnen alles. Ein weiteres Jahr verging, Version 7.x wurde veröffentlicht, viele Dinge hatten sich geändert, und es war Zeit, die Änderungen zusammenzufassen und gleichzeitig daran zu erinnern, was Celesta im Allgemeinen ist.
Wenn Sie noch nichts über Celesta gehört haben und beim Lesen dieses Artikels wissen möchten, für welche Geschäftsaufgaben die Anwendung am effektivsten ist, kann ich den ersten Teil des alten Beitrags oder dieses halbstündige Video empfehlen (mit Ausnahme von Wörtern zur Verwendung der Python-Sprache). Aber noch besser, lesen Sie zuerst diesen Artikel. Ich werde mit den Änderungen beginnen, die in Version 7 vorgenommen wurden, und dann ein vollständiges technisches Beispiel für die Verwendung der modernen Version von Celesta durchgehen, um mithilfe von Spring Boot einen kleinen Backend-Dienst für eine Java-Anwendung zu schreiben.
Was hat sich in Version 7.x geändert?
- Wir haben uns geweigert, Jython als eine in Celesta integrierte Sprache zu verwenden. Wenn wir früher über Celesta mit der Tatsache gesprochen haben, dass Geschäftslogik in Python geschrieben ist, kann jetzt ... jede Java-Sprache als Sprache der Geschäftslogik dienen: Java, Groovy, JRuby oder dasselbe Jython. Jetzt ruft Celesta den Geschäftslogikcode nicht mehr auf, aber der Geschäftslogikcode verwendet Celesta und seine Datenzugriffsklassen als häufigste Java-Bibliothek. Ja, die Abwärtskompatibilität wurde dadurch verletzt, aber dies ist der Preis, den wir bereit waren zu zahlen. Leider hat unsere Wette auf Jython verloren. Als wir vor einigen Jahren mit der Verwendung von Jython begannen, war es ein lebhaftes und vielversprechendes Projekt, aber im Laufe der Jahre verlangsamte sich seine Entwicklung, der Rückstand aus der Sprachspezifikation häufte sich und Kompatibilitätsprobleme für die meisten Pip-Bibliotheken wurden nicht gelöst. Der letzte Strohhalm waren die neuen Fehler in den neuesten Sprachversionen, die sich bei der Arbeit an einer Produktionslast zeigten. Wir selbst haben nicht die Ressourcen, um das Jython-Projekt zu unterstützen, und wir haben beschlossen, uns davon zu trennen. Celesta ist nicht mehr von Jython abhängig.
- Datenzugriffsklassen werden jetzt mithilfe des Maven-Plugins in der Java-Sprache (und nicht wie zuvor in Python) Code generiert. Und da wir aus diesem Grund von der dynamischen zur statischen Typisierung übergegangen sind, haben sich mehr Möglichkeiten für das Refactoring ergeben und es wurde einfacher, subjektiv korrekten Code zu schreiben.
- Die Erweiterung für JUnit5 wurde angezeigt, sodass es sehr praktisch wurde, Logiktests zu schreiben, die mit der Datenbank in JUnit5 arbeiten (auf die später noch eingegangen wird).
- Ein separates Projekt ist erschienen - Spring-Boot-Starter-Celesta , das, wie der Name schon sagt, der Celesta-Starter in Spring Boot ist. Die Möglichkeit, Celesta-Anwendungen in einfach zu implementierende Spring Boot-Dienste zu packen, kompensierte den Verlust der Möglichkeit, die Anwendung auf dem Server zu aktualisieren, indem einfach der Ordner mit Python-Skripten geändert wurde.
- Wir haben die gesamte Dokumentation aus dem Wiki in das AsciiDoctor- Format übertragen, sie zusammen mit dem Code in die Versionskontrolle gestellt und verfügen nun über eine aktuelle Dokumentation für jede Celesta-Version. Die neueste Version der Online-Dokumentation finden Sie hier: https://courseorchestra.imtqy.com/celesta/
- Wir wurden oft gefragt, ob es möglich ist, die Datenbankmigration über idempotente DDL getrennt von Celesta zu verwenden. Jetzt gibt es eine solche Möglichkeit mit dem 2bass- Tool.
Was ist Celesta und was kann sie tun?
Kurz gesagt, Celesta ist:
- eine Zwischenschicht zwischen der relationalen Datenbank und dem Geschäftslogikcode, basierend auf dem Datenbank-First- Design-Ansatz,
- Migrationsmechanismus der Datenbankstruktur,
- Framework zum Testen von Code, der mit Daten arbeitet.
Wir unterstützen vier Arten relationaler Datenbanken: PostgreSQL, MS SQL Server, Oracle und H2.
Hauptmerkmale von Celesta:
- Ein Prinzip, das dem Grundprinzip von Java sehr ähnlich ist: "Einmal schreiben, auf jedem unterstützten RDBMS ausführen." Der Geschäftslogikcode weiß nicht, auf welchem Datenbanktyp er ausgeführt wird. Sie können den Geschäftslogikcode schreiben und in MS SQL Server ausführen und dann zu PostgreSQL wechseln. Dies geschieht ohne Komplikationen (na ja, fast :)
- Automatische Umstrukturierung in einer Live-Datenbank. Der größte Teil des Lebenszyklus von Celesta-Projekten findet statt, wenn die Arbeitsdatenbank bereits vorhanden ist und mit Daten gefüllt ist, die gespeichert werden müssen. Es ist jedoch auch erforderlich, ihre Struktur ständig zu ändern. Eines der Hauptmerkmale von Celesta ist die Möglichkeit, die Datenbankstruktur automatisch an Ihr Datenmodell anzupassen.
- Testen. Es wird viel Wert darauf gelegt, dass der Code für Celesta testbar ist, damit wir automatisch Methoden testen können, mit denen Daten in der Datenbank geändert werden können. Dies geschieht einfach, schnell und elegant, ohne externe Tools wie DbUnit und Container zu verwenden.
Warum brauchen Sie Unabhängigkeit von der Art des DBMS?
Die Unabhängigkeit des Geschäftslogikcodes vom DBMS-Typ war nicht der erste Punkt, den wir festgelegt haben: Der für Celesta geschriebene Code weiß überhaupt nicht, auf welchem DBMS er ausgeführt wird. Warum?
Erstens aufgrund der Tatsache, dass die Wahl eines DBMS-Typs kein technologisches, sondern ein politisches Problem ist. Wenn wir zu einem neuen Geschäftskunden kommen, stellen wir häufig fest, dass er bereits einen bevorzugten DBMS-Typ hat, in den Mittel investiert werden, und der Kunde möchte andere Lösungen für die vorhandene Infrastruktur sehen. Die technologische Landschaft ändert sich: PostgreSQL findet sich zunehmend in Regierungsbehörden und privaten Unternehmen, obwohl sich MS SQL Server in unserer Praxis vor einigen Jahren durchgesetzt hat. Celesta unterstützt die gängigsten DBMS, und wir sind nicht besorgt über diese Änderungen.
Zweitens möchte ich den bereits erstellten Code zur Lösung von Standardproblemen von einem Projekt in ein anderes übertragen, um eine wiederverwendbare Bibliothek zu erstellen. Dinge wie hierarchische Verzeichnisse oder E-Mail-Benachrichtigungsverteilungsmodule sind von Natur aus Standard. Warum müssen wir mehrere Versionen für Kunden mit unterschiedlichen Beziehungen unterstützen?
Drittens, nicht zuletzt die Möglichkeit, Komponententests ohne Verwendung von DbUnit und Containern mithilfe einer speicherinternen H2-Datenbank durchzuführen. In diesem Modus startet die H2-Basis sofort. Celesta erstellt sehr schnell ein Datenschema, nach dem Sie die erforderlichen Tests durchführen und die Datenbank „vergessen“ können. Da der Geschäftslogikcode wirklich nicht weiß, auf welcher Basis er ausgeführt wird, funktioniert er entsprechend fehlerfrei unter PostgreSQL, wenn er auf H2 fehlerfrei funktioniert. Die Aufgabe der Entwickler des Celesta-Systems selbst besteht natürlich darin, alle Tests mit echten DBMS durchzuführen, um sicherzustellen, dass unsere Plattform ihre API für verschiedene Beziehungen gleichermaßen ausführt. Und wir machen es. Der Entwickler der Geschäftslogik wird jedoch nicht mehr benötigt.
CelestaSQL
Wie wird Cross-Basalism erreicht? Natürlich auf Kosten der Arbeit mit Daten nur über eine spezielle API, die die Logik von allen Datenbankspezifikationen isoliert. Celesta generiert Java-Klassen für den Zugriff auf Daten einerseits und SQL-Code und einige Hilfsobjekte in der Datenbank andererseits.
Celesta bietet keine objektrelationale Zuordnung in ihrer reinsten Form, da wir beim Entwerfen eines Datenmodells nicht aus Klassen, sondern aus der Datenbankstruktur stammen. Das heißt, wir erstellen zuerst ein ER-Modell von Tabellen, und dann generiert Celesta basierend auf diesem Modell selbst Cursorklassen für den Zugriff auf Daten.
Sie können dieselbe Arbeit für alle unterstützten DBMS nur für die Funktionalität ausführen, die in jedem von ihnen ungefähr gleich implementiert ist. Wenn wir die Menge der Funktionsfähigkeiten jeder von uns unterstützten Basis in Form von „Eulerkreisen“ bedingt darstellen, erhalten wir das folgende Bild:

Wenn wir eine vollständige Unabhängigkeit von der Art der Datenbank bieten, sollte die Funktionalität, die wir für Programmierer von Geschäftslogik öffnen, innerhalb des Schnittpunkts aller Basen liegen. Auf den ersten Blick scheint dies eine erhebliche Einschränkung zu sein. Ja, für einige spezifische Funktionen können wir beispielsweise SQL Server nicht verwenden. Relationale Datenbanken unterstützen jedoch ausnahmslos Tabellen, Fremdschlüssel, Ansichten, Sequenzen und SQL-Abfragen mit JOIN und GROUP BY. Dementsprechend können wir Entwicklern diese Möglichkeiten geben. Wir stellen Entwicklern "depersonalisiertes SQL" zur Verfügung, das wir "CelestaSQL" nennen, und generieren dabei SQL-Abfragen für Dialekte der entsprechenden Datenbanken.
Die CelestaSQL-Sprache enthält DDL zum Definieren von Datenbankobjekten und SELECT-Abfragen für Ansichten und Filter, enthält jedoch keine DML-Befehle: Cursor werden zum Ändern der Daten verwendet, die noch zu diskutieren sind.
Jede Datenbank hat ihre eigenen Datentypen. CelestaSQL hat auch eigene Typen. Zum Zeitpunkt des Schreibens gibt es neun davon, und diese Tabelle vergleicht sie mit realen Typen in verschiedenen Datenbanken und Java-Datentypen.
Es mag den Anschein haben, dass neun Typen nicht ausreichen (im Vergleich zu dem, was PostgreSQL beispielsweise unterstützt ), aber in Wirklichkeit sind dies die Typen, die ausreichen, um Finanz-, Handels- und Logistikinformationen zu speichern: Zeichenfolgen, Ganzzahlen, Bruchzahlen , Daten, Boolesche Werte und Blobs reichen immer aus, um solche Daten darzustellen.
Die CelestaSQL-Sprache selbst wird in der Dokumentation mit einer Vielzahl von Syntaxdiagrammen beschrieben.
Änderung der Datenbankstruktur. Idempotente DDL
Ein weiteres wichtiges Merkmal von Celesta ist der Ansatz zur Migration der Struktur der Arbeitsdatenbank während der Projektentwicklung. Zu diesem Zweck wird der in Celesta integrierte Ansatz mit idempotenter DDL verwendet.
Kurz gesagt, wenn wir in CelestaSQL den folgenden Text schreiben:
CREATE TABLE OrderLine( order_id VARCHAR(30) NOT NULL, line_no INT NOT NULL, item_id VARCHAR(30) NOT NULL, item_name VARCHAR(100), qty INT NOT NULL DEFAULT 0, cost REAL NOT NULL DEFAULT 0.0, CONSTRAINT Idx_OrderLine PRIMARY KEY (order_id, line_no) );
- Dieser Text wird von Celesta nicht als "Tabelle erstellen, aber wenn bereits eine Tabelle vorhanden ist, geben Sie einen Fehler aus" interpretiert, sondern als "Tabelle in die gewünschte Struktur bringen". Das heißt: „Wenn es keine Tabelle gibt, erstellen Sie sie, wenn es eine Tabelle gibt, sehen Sie, welche Felder darin enthalten sind, mit welchen Typen, welchen Indizes, welchen Fremdschlüsseln, welchen Standardwerten usw. und ob etwas geändert werden muss diesen Tisch, um es auf die richtige Art zu bringen. "
Mit diesem Ansatz implementieren wir die Möglichkeit, Skripte zu refaktorieren und zu versionieren, um die Struktur der Datenbank zu bestimmen:
- wir sehen im Skript das aktuelle "gewünschte Bild" der Struktur,
- Was, von wem und warum hat sich die Struktur im Laufe der Zeit geändert? Wir können das Versionskontrollsystem durchsehen.
- Bei ALTER-Befehlen generiert Celesta diese automatisch und führt sie bei Bedarf „unter der Haube“ aus.
Natürlich hat dieser Ansatz seine Grenzen. Celesta unternimmt alle Anstrengungen, um sicherzustellen, dass die automatische Migration problemlos und nahtlos verläuft. Dies ist jedoch nicht in allen Fällen möglich. Die Motivation, Möglichkeiten und Grenzen dieses Ansatzes wurden in diesem Beitrag beschrieben (die englische Version ist ebenfalls verfügbar).
Um den Prozess des Überprüfens / Aktualisierens der Datenbankstruktur zu beschleunigen, wendet Celesta die Speicherung von DDL-Skriptprüfsummen in der Datenbank an (bis die Prüfsumme geändert wird, startet der Prozess des Überprüfens und Aktualisierens der Datenbankstruktur nicht). Damit der Aktualisierungsprozess ohne Probleme in Bezug auf die Reihenfolge der Änderung von voneinander abhängigen Objekten fortgesetzt werden kann, wird die topologische Sortierung der Abhängigkeiten zwischen Schemata nach Fremdschlüsseln angewendet. Der automatische Migrationsprozess wird in der Dokumentation ausführlicher beschrieben.
Erstellen eines Celesta-Projekts und eines Datenmodells
Das Demo-Projekt, das wir betrachten werden, ist auf dem Github verfügbar . Mal sehen, wie Sie Celesta beim Schreiben einer Spring Boot-Anwendung verwenden können. Hier sind die Maven-Abhängigkeiten, die Sie benötigen:
org.springframework.boot:spring-boot-starter-web
und ru.curs:spring-boot-starter-celesta
(weitere Informationen finden ru.curs:spring-boot-starter-celesta
Dokumentation).- Wenn Sie Spring Boot nicht verwenden, können Sie die
ru.curs:celesta-system-services
direkt verbinden. - Für die Codegenerierung von Datenzugriffsklassen basierend auf Celesta-SQL-Skripten wird
ru.curs:celesta-maven-plugin
benötigt ru.curs:celesta-maven-plugin
- Der Quellcode für ein Demo-Beispiel oder eine Dokumentation beschreibt, wie die Verbindung hergestellt wird. - Um die Möglichkeit zu nutzen, JUnit5-Komponententests für Methoden zu schreiben, die Daten ändern, müssen Sie
ru.curs:celesta-unit
im ru.curs:celesta-unit
verbinden.
Erstellen Sie nun ein Datenmodell und kompilieren Sie Datenzugriffsklassen.
Angenommen, wir führen ein Projekt für ein E-Commerce-Unternehmen durch, das kürzlich mit einem anderen Unternehmen fusioniert wurde. Jeder hat seine eigene Datenbank. Sie sammeln Bestellungen, aber bis sie ihre Datenbanken zusammengeführt haben, benötigen sie einen einzigen Einstiegspunkt, um Bestellungen von außerhalb zu sammeln.
Die Implementierung dieses „Einstiegspunkts“ sollte recht traditionell sein: ein HTTP-Dienst mit CRUD-Operationen, die Daten in einer relationalen Datenbank speichern.
Aufgrund der Tatsache, dass Celesta den Designansatz "Datenbank zuerst" implementiert, müssen wir zuerst eine Tabellenstruktur erstellen, in der Bestellungen gespeichert werden. Wie Sie wissen, ist eine Bestellung eine zusammengesetzte Entität: Sie besteht aus einer Kopfzeile, in der Informationen über den Kunden, das Bestelldatum und andere Attribute der Bestellung sowie viele Zeilen (Warenartikel) gespeichert sind.
Also, für den Job: erstellen
src/main/celestasql
- Standardmäßig ist dies der Pfad zu den CelestaSQL-Projektskripten- Es enthält Unterordner, die die Ordnerstruktur von Java-Paketen wiederholen (in unserem Fall
ru/curs/demo
). - Erstellen Sie im
.sql
eine .sql
Datei mit folgendem Inhalt:
CREATE SCHEMA demo VERSION '1.0'; CREATE TABLE OrderHeader( id VARCHAR(30) NOT NULL, date DATETIME, customer_id VARCHAR(30), customer_name VARCHAR(50), manager_id VARCHAR(30), CONSTRAINT Pk_OrderHeader PRIMARY KEY (id) ); CREATE TABLE OrderLine( order_id VARCHAR(30) NOT NULL, line_no INT NOT NULL, item_id VARCHAR(30) NOT NULL, item_name VARCHAR(100), qty INT NOT NULL DEFAULT 0, cost REAL NOT NULL DEFAULT 0.0, CONSTRAINT Idx_OrderLine PRIMARY KEY (order_id, line_no) ); ALTER TABLE OrderLine ADD CONSTRAINT fk_OrderLine FOREIGN KEY (order_id) REFERENCES OrderHeader(id); CREATE VIEW OrderedQty AS SELECT item_id, sum(qty) AS qty FROM OrderLine GROUP BY item_id;
Hier haben wir zwei Tabellen beschrieben, die durch einen Fremdschlüssel verbunden sind, und eine Ansicht, die eine zusammenfassende Menge für die in allen Bestellungen vorhandenen Waren zurückgibt. Wie Sie sehen, unterscheidet sich dies nicht von regulärem SQL, mit Ausnahme des Befehls CREATE SCHEMA
, in dem wir die demo
Schema-Version deklariert haben (Informationen darüber, wie sich die Versionsnummer auf die automatische Migration auswirkt, finden Sie in der Dokumentation ). Es gibt aber auch Funktionen. Beispielsweise können alle Namen der von uns verwendeten Tabellen und Felder nur so sein, dass sie in der Java-Sprache in gültige Klassen- und Variablennamen umgewandelt werden können. Leerzeichen, Sonderzeichen sind daher ausgeschlossen. Sie können auch feststellen, dass die Kommentare, die wir über die Namen von Tabellen und einigen Feldern setzen, nicht wie üblich mit / * begonnen haben, sondern mit / **, wie JavaDoc-Kommentare beginnen - und das ist kein Zufall! Ein Kommentar, der für eine Entität definiert ist, die mit / ** beginnt, ist zur Laufzeit in der Eigenschaft .getCelestaDoc()
dieser Entität verfügbar. Dies ist nützlich, wenn wir Datenbankelementen zusätzliche Metainformationen bereitstellen möchten: z. B. von Menschen lesbare Feldnamen, Informationen zur Darstellung von Feldern in der Benutzeroberfläche usw.
Das CelestaSQL-Skript erfüllt zwei gleich wichtige Aufgaben: Erstens für die Bereitstellung / Änderung der Struktur einer relationalen Datenbank und zweitens für die Codegenerierung von Datenzugriffsklassen.
Wir können jetzt Datenzugriffsklassen generieren. Führen Sie einfach den Befehl mvn generate-sources
. Wenn Sie in IDEA arbeiten, klicken Sie in der Maven-Systemsteuerung auf die Schaltfläche "Quellen generieren und Ordner aktualisieren". Im zweiten Fall „ target/generated-sources/celesta
IDEA target/generated-sources/celesta
in target/generated-sources/celesta
erstellten Ordner auf und stellt seinen Inhalt für den Import in die Projektquellcodes zur Verfügung. Das Ergebnis der Codegenerierung sieht folgendermaßen aus: Eine Klasse für jedes Objekt in der Datenbank:
Die Verbindung zur Datenbank wird in den Anwendungseinstellungen angegeben, in unserem Fall in der Datei src/main/resources/application.yml
. Bei Verwendung von Spring-Boot-Starter-Celesta informiert IDEA Sie über die Codeoptionen, die bei der Code-Vervollständigung verfügbar sind.
Wenn wir uns zu Demonstrationszwecken nicht mit dem „echten“ RDBMS beschäftigen möchten, können wir Celesta mithilfe der folgenden Konfiguration dazu bringen, mit der integrierten H2-Datenbank im In-Memory-Modus zu arbeiten:
celesta: h2: inMemory: true
Um eine "echte" Datenbank zu verbinden, ändern Sie die Konfiguration in etwas wie
celesta: jdbc: url: jdbc:postgresql://127.0.0.1:5432/celesta username: <your_username> password: <your_password>
(In diesem Fall müssen Sie Ihrer Anwendung über die Maven-Abhängigkeit auch einen PostgreSQL-JDBC-Treiber hinzufügen.)
Wenn Sie eine Celesta-Anwendung mit einer Verbindung zu einem Datenbankserver starten, können Sie feststellen, dass die erforderlichen Tabellen, Ansichten, Indizes usw. für eine leere Datenbank erstellt und für eine nicht leere Datenbank auf die in der DDL angegebenen Strukturen aktualisiert werden.
Erstellen von Datenmanipulationsmethoden
Sobald Sie herausgefunden haben, wie eine Datenbankstruktur erstellt wird, können Sie mit dem Schreiben von Geschäftslogik beginnen.
Um die Anforderungen für die Verteilung von Zugriffsrechten und Protokollierungsaktionen implementieren zu können, werden alle Vorgänge für Daten in Celesta im Auftrag eines Benutzers ausgeführt. Es gibt keine „anonymen“ Vorgänge. Daher wird jeder Celesta-Code im Kontext des in der CallContext- Klasse beschriebenen Aufrufs ausgeführt .
- Vor dem Starten eines Vorgangs, mit dem Daten in der Datenbank
CallContext
, wird CallContext
aktiviert. - Zum Zeitpunkt der Aktivierung wird eine Verbindung zur Datenbank aus dem Verbindungspool entnommen und die Transaktion beginnt.
- Nach Abschluss des
CallContext
führt CallContext
entweder commit()
wenn der Vorgang erfolgreich war, oder rollback()
wenn während der Ausführung eine nicht behandelte Ausnahme aufgetreten ist. CallContext
geschlossen und die Datenbankverbindung wird an den Pool zurückgegeben.
Wenn wir Spring-Boot-Starter-Celesta verwenden, werden diese Aktionen automatisch für alle von @CelestaTransaction
kommentierten Methoden @CelestaTransaction
.
Angenommen, wir möchten einen Handler schreiben, der das Dokument in der Datenbank speichert. Der Code auf Controller-Ebene könnte folgendermaßen aussehen:
@RestController @RequestMapping("/api") public class DocumentController { private final DocumentService srv; public DocumentController(DocumentService srv) { this.srv = srv; } @PutMapping("/save") public void saveOrder(@RequestBody OrderDto order) { CallContext ctx = new CallContext("user1");
In der Regel kennen wir auf der Ebene der Controller-Methode (d. H. Wenn die Authentifizierung bereits bestanden wurde) die Benutzer-ID und können sie beim Erstellen des CallContext
. Das Binden eines Benutzers an einen Kontext bestimmt die Berechtigungen für den Zugriff auf Tabellen und bietet auch die Möglichkeit, in seinem Namen vorgenommene Änderungen zu protokollieren. In diesem Fall müssen für die Funktionsfähigkeit des mit der Datenbank interagierenden Codes die Rechte für den Benutzer "Benutzer1" in den Systemtabellen angegeben werden . Wenn Sie das Celesta-Zugriffsverteilungssystem nicht verwenden und dem Sitzungskontext alle Rechte für Tabellen erteilen möchten, können Sie ein SystemCallContext
Objekt erstellen.
Die Methode zum Speichern der Rechnung auf Serviceebene kann folgendermaßen aussehen:
@Service public class DocumentService { @CelestaTransaction public void postOrder(CallContext context, OrderDto doc) { try (OrderHeaderCursor header = new OrderHeaderCursor(context); OrderLineCursor line = new OrderLineCursor(context)) { header.setId(doc.getId()); header.setDate(Date.from(doc.getDate().atStartOfDay(ZoneId.systemDefault()).toInstant())); header.setCustomer_id(doc.getCustomerId()); header.setCustomer_name(doc.getCustomerName()); header.insert(); int lineNo = 0; for (OrderLineDto docLine : doc.getLines()) { lineNo++; line.setLine_no(lineNo); line.setOrder_id(doc.getId()); line.setItem_id(docLine.getItemId()); line.setQty(docLine.getQty()); line.insert(); } } }
Beachten Sie die Annotation @CelestaTransaction
. Dank dessen führt das Proxy-Objekt DocumentService
alle diese CallContext ctx
mit dem CallContext ctx
beschriebenen Parameter CallContext ctx
. Das heißt, zu Beginn der Methodenausführung ist sie bereits an die Datenbankverbindung gebunden, und die Transaktion kann gestartet werden. Wir können uns darauf konzentrieren, Geschäftslogik zu schreiben. In unserem Fall lesen Sie das OrderDto
Objekt und speichern es in der Datenbank.
Dazu verwenden wir die sogenannten Cursor - Klassen, die mit dem celesta-maven-plugin
generiert wurden. Wir haben bereits gesehen, was sie sind. Für jedes der Schemaobjekte wird eine Klasse erstellt - zwei Tabellen und eine Ansicht. Und jetzt können wir diese Klassen verwenden, um auf Datenbankobjekte in unserer Geschäftslogik zuzugreifen.
Um einen Cursor in der Auftragstabelle zu erstellen und den ersten Datensatz auszuwählen, müssen Sie den folgenden Code schreiben:
OrderHeaderCursor header = new OrderHeaderCursor(context); header.tryFirst();
Nach dem Erstellen des Header-Objekts können wir über Getter und Setter auf die Felder des Tabelleneintrags zugreifen:
Beim Erstellen eines Cursors müssen wir den aktiven Aufrufkontext verwenden - dies ist die einzige Möglichkeit, einen Cursor zu erstellen. Der Anrufkontext enthält Informationen über den aktuellen Benutzer und seine Zugriffsrechte.
Mit dem Cursor-Objekt können wir verschiedene Dinge tun: filtern, Datensätze durchgehen und natürlich auch Datensätze einfügen, löschen und aktualisieren. Die gesamte Cursor-API wird in der Dokumentation ausführlich beschrieben.
Zum Beispiel könnte der Code unseres Beispiels wie folgt entwickelt werden:
OrderHeaderCursor header = new OrderHeaderCursor(context); header.setRange("manager_id", "manager1"); header.tryFirst(); header.setCounter(header.getCounter() + 1); header.update();
In diesem Beispiel setzen wir den Filter nach dem Feld manager_id und finden dann den ersten Datensatz mit der Methode tryFirst.
(warum "versuchen")Die Methoden get
, first
, insert
, update
haben zwei Optionen: ohne das try-Präfix (nur get(...)
usw.) und mit dem try-Präfix ( tryGet(...)
, tryFirst()
usw.) . Methoden ohne das Präfix try lösen eine Ausnahme aus, wenn die Datenbank nicht über die entsprechenden Daten zum Ausführen der Aktion verfügt. Beispielsweise löst first () eine Ausnahme aus, wenn keine Datensätze in den auf dem Cursor festgelegten Filter gelangen. Gleichzeitig lösen Methoden mit dem Präfix try keine Ausnahme aus, sondern geben stattdessen einen Booleschen Wert zurück, der den Erfolg oder Misserfolg der entsprechenden Operation signalisiert. Es wird empfohlen, nach Möglichkeit Methoden ohne das Präfix try zu verwenden. Auf diese Weise wird ein "Selbsttest" -Code erstellt, der zeitliche Fehler in den Logik- und / oder Datenbankdaten signalisiert.
Wenn tryFirst
ausgelöst wird, werden die tryFirst
Variablen mit den Daten eines Datensatzes gefüllt, wir können sie lesen und ihnen Werte zuweisen. Wenn die Daten im Cursor vollständig vorbereitet sind, führen wir update()
und speichern den Inhalt des Cursors in der Datenbank.
Welches Problem könnte dieser Code betreffen? Natürlich das Auftauchen des Rennzustands / verlorenes Update! Denn zwischen dem Moment, in dem wir die Daten in der Zeile mit "tryFirst" erhalten haben, und dem Moment, in dem wir versuchen, diese Daten am "Update" -Punkt zu aktualisieren, kann bereits jemand anderes diese Daten in der Datenbank empfangen, ändern und aktualisieren. Nachdem die Daten gelesen wurden, blockiert der Cursor in keiner Weise ihre Verwendung durch andere Benutzer! Zum Schutz vor verlorenen Updates verwendet Celesta das optimistische Sperrprinzip. In jeder Tabelle erstellt Celesta standardmäßig ein recversion
Auf der EIN-Ebene des UPDATE-Triggers wird die Versionsnummer erhöht und überprüft, ob die aktualisierten Daten dieselbe Version wie die Tabelle haben. Wenn ein Problem auftritt, wird eine Ausnahme ausgelöst. Weitere Informationen hierzu finden Sie im Artikel „ Schutz vor verlorenen Updates “.
Erinnern Sie sich erneut daran, dass dem CallContext-Objekt eine Transaktion zugeordnet ist. Wenn die Celesta-Prozedur erfolgreich ist, erfolgt ein Commit. Wenn die Celesta-Methode mit einer nicht behandelten Ausnahme endet, tritt ein Rollback auf. Wenn also in einer komplizierten Prozedur ein Fehler auftritt, wird die gesamte Transaktion in Bezug auf den Aufrufkontext zurückgesetzt, als ob wir nicht begonnen hätten, etwas mit den Daten zu tun, und die Daten werden nicht beschädigt. Wenn Sie aus irgendeinem Grund ein Commit in der Mitte einer großen Prozedur benötigen, kann ein explizites Commit durch Aufrufen von context.commit()
.
Testen von Datenmethoden
Erstellen wir einen OrderDto
der die Richtigkeit der Servicemethode überprüft, mit der OrderDto
in der Datenbank gespeichert wird.
Bei Verwendung von JUnit5 und der im celesta-unit
Modul verfügbaren Erweiterung für JUnit5 ist dies sehr einfach. Die Struktur des Tests ist wie folgt:
@CelestaTest public class DocumentServiceTest { DocumentService srv = new DocumentService(); @Test void documentIsPutToDb(CallContext context) { OrderDto doc =... srv.postOrder(context, doc);
Dank der Annotation @CelestaTest
, einer Erweiterung für JUnit5, können wir den CallContext context
in Testmethoden deklarieren. Dieser Kontext ist bereits aktiviert und an die Datenbank gebunden (In-Memory H2). Daher müssen wir die Serviceklasse nicht in einen Proxy einbinden - wir erstellen sie mit new
und nicht mit Spring. Falls erforderlich, injizieren Sie den Service jedoch mit Federwerkzeugen in den Test, da dies keine Hindernisse darstellt.
Wir erstellen Komponententests unter der Annahme, dass die Datenbank zum Zeitpunkt ihrer Ausführung vollständig leer sein wird, aber mit der Struktur, die wir benötigen, und nach ihrer Ausführung können wir uns keine Sorgen darüber machen, dass wir "Müll" in der Datenbank belassen haben. Diese Tests werden mit einer sehr hohen Geschwindigkeit durchgeführt.
Erstellen wir eine zweite Prozedur, die JSON mit aggregierten Werten zurückgibt, die angeben, wie viele Produkte wir bestellt haben.
Der Test schreibt zwei Aufträge in die Datenbank und überprüft anschließend den von der neuen Methode getAggregateReport
:
@Test void reportReturnsAggregatedQuantities(CallContext context) { srv.postOrder(context, . . .); srv.postOrder(context, . . .); Map<String, Integer> result = srv.getAggregateReport(context); assertEquals(5, result.get("A").intValue()); assertEquals(7, result.get("B").intValue()); }
Um die getAggregateReport
Methode zu implementieren getAggregateReport
wir die OrderedQty-Ansicht, die, wie ich mich erinnere, in der CelestaSQL-Datei folgendermaßen aussieht:
create view OrderedQty as select item_id, sum(qty) as qty from OrderLine group by item_id;
Die Anfrage ist Standard: Wir fassen Bestellpositionen nach Menge und Gruppe nach Produktcode zusammen. Für die Ansicht wurde bereits ein OrderedQtyCursor-Cursor erstellt, den wir verwenden können. Wir deklarieren diesen Cursor, iterieren darüber und sammeln die gewünschte Map<String, Integer>
:
@CelestaTransaction public Map<String, Integer> getAggregateReport(CallContext context) { Map<String, Integer> result = new HashMap<>(); try (OrderedQtyCursor ordered_qty = new OrderedQtyCursor(context)) { for (OrderedQtyCursor line : ordered_qty) { result.put(ordered_qty.getItem_id(), ordered_qty.getQty()); } } return result; }
Materialisierte Celesta-Ansichten
Warum ist die Verwendung einer Ansicht schlecht, um aggregierte Daten abzurufen? Dieser Ansatz ist durchaus praktikabel, bringt jedoch in Wirklichkeit eine Zeitbombe in unser gesamtes System: Schließlich wird eine Ansicht, bei der es sich um eine SQL-Abfrage handelt, immer langsamer ausgeführt, wenn sich Daten im System ansammeln. Er muss immer mehr Zeilen zusammenfassen und gruppieren. Wie vermeide ich das?
Celesta versucht, alle Standardaufgaben zu implementieren, mit denen Business-Logic-Programmierer auf Plattformebene ständig konfrontiert sind.
MS SQL Server hat das Konzept materialisierter (indizierter) Ansichten, die als Tabellen gespeichert und schnell aktualisiert werden, wenn sich die Daten in den Quelltabellen ändern. Wenn wir in einem „sauberen“ MS SQL Server arbeiten würden, wäre in unserem Fall das Ersetzen der Ansicht durch eine indizierte Ansicht genau das, was wir benötigen: Das Abrufen des aggregierten Berichts würde sich nicht verlangsamen, wenn sich die Daten ansammeln, und die Arbeit zum Aktualisieren des aggregierten Berichts würde im Moment ausgeführt Einfügen von Daten in die Tabelle der Auftragspositionen und würde auch mit zunehmender Anzahl von Zeilen nicht viel zunehmen.
Was können wir tun, wenn wir über Celesta mit PostgreSQL arbeiten? Definieren Sie die Ansicht neu, indem Sie das materialisierte Wort hinzufügen:
create materialized view OrderedQty as select item_id, sum(qty) as qty from OrderLine group by item_id;
Lassen Sie uns das System starten und sehen, was mit der Datenbank passiert ist.
Wir werden feststellen, dass die OrderedQty
verschwunden ist und OrderedQty
die OrderedQty
Tabelle OrderedQty
wurde. Da die OrderLine-Tabelle mit Daten gefüllt ist, werden die Informationen in der OrderedQty-Tabelle außerdem "magisch" aktualisiert, als wäre OrderedQty eine Ansicht.
Es gibt hier keine Magie, wenn wir uns die Trigger ansehen, die auf der OrderLine
Tabelle OrderLine
. Nachdem Celesta die Aufgabe erhalten hatte, eine „materialisierte Ansicht“ zu erstellen, analysierte sie die Abfrage und erstellte Trigger in der OrderLine
Tabelle, die OrderLine
aktualisieren. Durch Einfügen eines einzelnen Schlüsselworts - materialized
- in die CelestaSQL-Datei haben wir das Problem der Leistungsverschlechterung gelöst, und der Geschäftslogikcode musste nicht einmal geändert werden!
, , , . «» Celesta , , JOIN-, GROUP BY. , , , , . . .
Fazit
Celesta. — .