Laufzeitdefinierte Spalten mit asentinel-orm

Asentinel-orm ist ein leichtgewichtiges ORM-Tool, das auf Spring JDBC, insbesondere JdbcTemplate, aufbaut. Somit verfügt es über die meisten Funktionen, die man von einem grundlegenden ORM erwarten würde, wie z. B. SQL-Generierung, Lazy Loading, etc.

Durch die Nutzung des JdbcTemplate ermöglicht es die Teilnahme an von Spring verwalteten Transaktionen und kann leicht in jedes Projekt integriert werden, das bereits JdbcTemplate zur Interaktion mit der Datenbank verwendet.

Seit 2015 wurde asentinel-orm erfolgreich in mehreren Anwendungen eingesetzt und kontinuierlich gemäß den geschäftlichen Anforderungen verbessert. Im Sommer 2024 wurde es offiziell zu einem Open-Source-Projekt, was wir erwarten, dass seine Entwicklung beschleunigen und die Anzahl der Beitragenden erhöhen wird.

In diesem Artikel wird eine Beispielanwendung erstellt, um mehrere wichtige ORM-Funktionen zu erläutern:

  • Einfache Konfiguration
  • Klare Modellierung von Domain-Entitäten mittels benutzerdefinierter Annotationen
  • Einfaches Schreiben und sicheres Ausführen einfacher SQL-Anweisungen
  • Automatische Generierung von SQL-Anweisungen
  • Dynamisches Schema (Entitäten werden um zusätzliche Laufzeitattribute erweitert, persistiert und ohne Codeänderungen gelesen)

Anwendungs

Setup

  • Java 21
  • Spring Boot 3.4.0
  • asentinel-orm 1.70.0
  • H2-Datenbank

Konfiguration

Um mit dem asentinel-orm zu interagieren und seine Funktionen zu nutzen, wird eine Instanz von OrmOperations benötigt.

Wie im JavaDoc angegeben, handelt es sich hierbei um die zentrale Schnittstelle für die Ausführung von ORM-Operationen, und es ist weder beabsichtigt noch erforderlich, dass sie speziell im Client-Code implementiert wird.

Die Beispielanwendung enthält den Konfigurationscode zum Erstellen eines Beans dieses Typs.

Java

 

OrmOperations hat zwei Super-Schnittstellen:

  • SqlBuilderFactory – erstellt SqlBuilder -Instanzen, die weiterhin zur Erstellung von SQL-Abfragen verwendet werden können. SqlBuilder kann Teile der Abfrage automatisch generieren, z.B. die, die die Spalten auswählt. Die Where-Klausel, die Order-By-Klausel, andere Bedingungen und die tatsächlichen Spalten können ebenfalls mit Methoden aus der Klasse SqlBuilder hinzugefügt werden. Im nächsten Teil dieses Abschnitts wird ein Beispiel für eine mit SqlBuilder generierte Abfrage gezeigt.
  • Updater – wird verwendet, um Entitäten in ihre jeweiligen Datenbanktabellen zu speichern. Es kann Einfügungen oder Aktualisierungen durchführen, je nachdem, ob die Entität neu erstellt wurde oder bereits vorhanden ist. Es existiert eine Strategie-Schnittstelle namens NewEntityDetector , die verwendet wird, um festzustellen, ob eine Entität neu ist. Standardmäßig wird der SimpleNewEntityDetector verwendet.

Alle vom ORM generierten Abfragen werden mithilfe einer SqlQueryTemplate Instanz ausgeführt, die zusätzlich eine Spring JdbcOperations/JdbcTemplate benötigt, um zu funktionieren. Letztendlich erreichen alle Abfragen das gute alte JdbcTemplate, über das sie ausgeführt werden, während sie an Spring-Transaktionen teilnehmen, genau wie jede JdbcTemplate direkte Ausführung.

Datenbankspezifische SQL-Konstrukte und Logik werden über Implementierungen des JdbcFlavor-Interfaces bereitgestellt, die in die meisten der oben genannten Beans injiziert werden. In diesem Artikel wird eine H2JdbcFlavor-Implementierung konfiguriert, da eine H2-Datenbank verwendet wird.

Die vollständige Konfiguration des ORM als Teil der Beispielanwendung ist OrmConfig.

Implementierung

Das experimentelle Domänenmodell, das von der Beispielanwendung bereitgestellt wird, ist einfach und besteht aus zwei Entitäten – Automobilhersteller und Automodelle. Es repräsentiert genau das, was ihre Namen bedeuten; die Beziehung zwischen ihnen ist offensichtlich: Ein Automobilhersteller kann mehrere Automodelle besitzen.

Neben seinem Namen wird der Automobilhersteller mit Attributen (Spalten) angereichert, die vom Anwendungsbenutzer zur Laufzeit dynamisch eingegeben werden. Der veranschaulichte Anwendungsfall ist unkompliziert:

  • Der Benutzer wird gebeten, die angestrebten Namen und Typen für die dynamischen Attribute bereitzustellen
  • Einige Automobilhersteller werden mit konkreten Werten für zuvor hinzugefügte dynamische Attribute erstellt, und dann
  • Die Entitäten werden unter Verwendung sowohl der ursprünglichen als auch der zur Laufzeit definierten Attribute wieder geladen

Die ursprünglichen Entitäten werden unter Verwendung der untenstehenden Datenbanktabellen gemappt:

SQL

 

Die entsprechenden Domänenklassen sind mit ORM-spezifischen Annotationen dekoriert, um die Zuordnungen zu den oben genannten Datenbanktabellen zu konfigurieren.

Java

 

Java

 

Einige Überlegungen:

  • @Table – verknüpft (ordnet) die Klasse mit einer Datenbanktabelle
  • @PkColumn – verknüpft die id (eindeutiger Bezeichner) mit dem Primärschlüssel der Tabelle
  • @Column – verknüpft ein Klassenmitglied mit einer Spalte der Tabelle
  • @Child – definiert die Beziehung zu einer anderen Entität
  • @Child-annotierte Mitglieder – konfiguriert, um faul geladen zu werden
  • type Spalte der Tabelle – zu einem enum Feld zugeordnet – CarType

Damit die CarManufacturer-Klasse zur Unterstützung von zur Laufzeit definierten Attributen (die auf zur Laufzeit definierten Tabellenspalten abgebildet werden) geeignet ist, wird eine Unterklasse wie die folgende definiert:

Java

 

Diese Klasse speichert die zur Laufzeit definierten Attribute (Felder) in einer Map. Die Interaktion zwischen den Laufzeitfeldwerten und dem ORM erfolgt über die Implementierung des DynamicColumnEntity Interfaces.

Java

 

  • setValue() – wird verwendet, um den Wert der zur Laufzeit definierten Spalte festzulegen, wenn diese aus der Tabelle gelesen wird.
  • getValue() – wird verwendet, um den Wert einer zur Laufzeit definierten Spalte abzurufen, wenn diese in die Tabelle gespeichert wird

Der DynamicColumn ordnet zur Laufzeit definierte Attribute ihren entsprechenden Spalten auf ähnliche Weise zu, wie die @Column Annotation bekannte Klassenmitglieder zur Kompilierungszeit zuordnet.

Beim Ausführen der Anwendung wird der CfRunner ausgeführt. Der Benutzer wird aufgefordert, Namen und Typen für die gewünschten dynamischen benutzerdefinierten Attribute einzugeben, die die CarManufacturer Entität bereichern (zur Vereinfachung werden nur int und varchar Typen unterstützt).

Für jedes Namens-Typ-Paar wird ein DML-Befehl ausgeführt, damit die neuen Spalten der CarManufacturer Datenbanktabelle hinzugefügt werden können. Die folgende Methode (deklariert in CarService) führt die Operation aus.

Java

 

Jedes Eingabeattribut wird als DefaultDynamicColumn, eine Referenzimplementierung von DynamicColumn aufgezeichnet.

Sobald alle Attribute definiert sind, werden zwei Automobilhersteller zur Datenbank hinzugefügt, da der Benutzer Werte für jedes solche Attribut angibt.

Java

 

Die untenstehende Methode (deklariert in CarService) erstellt tatsächlich die Entität über das ORM.

Java

 

Die 2-Parameter-Version der OrmOperations update()-Methode wird aufgerufen, die es ermöglicht, eine UpdateSettings-Instanz zu übergeben und dem ORM bei der Ausführung mitzuteilen, dass es runtime-definierte Werte gibt, die persistiert werden sollen.

Zuletzt werden zwei Automodelle erstellt, die einem der zuvor hinzugefügten Automobilhersteller entsprechen.

Java

 

Die folgende Methode (deklariert in CarService) erstellt tatsächlich die Entitäten über das ORM, diesmal unter Verwendung der OrmOperations-Methode update() zum Persistieren von Entitäten ohne dynamische Attribute. Der Einfachheit halber werden mehrere Entitäten in einem Aufruf erstellt.

Java

 

Als letzter Schritt wird einer der erstellten Hersteller anhand seines Namens über eine vom ORM generierte Abfrage zurückgeladen.

Java

 

Einige Erklärungen zur oben definierten Methode sind es wert, gemacht zu werden.

Die OrmOperations newSqlBuilder() -Methode erstellt eine SqlBuilder-Instanz, und wie der Name schon sagt, kann dies verwendet werden, um SQL-Abfragen zu generieren. Die SqlBuilder select() -Methode generiert den select from table-Teil der Abfrage, während der Rest (where, order by) hinzugefügt werden muss. Der Select-Teil der Abfrage kann angepasst werden, indem EntityDescriptorNodeCallback -Instanzen übergeben werden (Details zu EntityDescriptorNodeCallback könnten Gegenstand eines zukünftigen Artikels sein).

Um dem ORM mitzuteilen, dass der Plan darin besteht, zur Laufzeit definierte Spalten auszuwählen und zuzuordnen, muss ein DynamicColumnsEntityNodeCallback übergeben werden. Zusammen mit ihm wird ein AutoEagerLoader bereitgestellt, damit das ORM versteht, dass die Liste der CarModels , die mit dem Hersteller verbunden sind, frühzeitig geladen werden soll. Dennoch hat dies nichts mit den zur Laufzeit definierten Attributen zu tun, sondern zeigt, wie ein untergeordnetes Mitglied frühzeitig geladen werden kann.

Fazit

Während es wahrscheinlich andere Möglichkeiten gibt, mit zur Laufzeit definierten Spalten zu arbeiten, wenn Daten in relationalen Datenbanken gespeichert sind, hat der in diesem Artikel vorgestellte Ansatz den Vorteil, dass er Standarddatenbanksäulen verwendet, die mit standardmäßigen SQL-Abfragen gelesen/geschrieben werden, die direkt vom ORM generiert werden.

Es war nicht selten, dass wir die Gelegenheit hatten, in „der Gemeinschaft“ über das asentinel-orm zu diskutieren, über die Gründe, warum wir ein solches Tool entwickeln mussten. In der Regel waren die Entwickler auf den ersten Blick zögerlich und zurückhaltend, wenn es um ein maßgeschneidertes ORM ging, und fragten, warum nicht Hibernate oder andere JPA-Implementierungen verwendet werden sollten.

In unserem Fall war der Hauptantrieb die Notwendigkeit, eine schnelle, flexible und einfache Möglichkeit zu haben, mit manchmal recht vielen zur Laufzeit definierten Attributen (Spalten) für Entitäten zu arbeiten, die Teil des Geschäftsdomain sind. Für uns erwies sich dies als der richtige Weg. Die Anwendungen laufen reibungslos in der Produktion, die Kunden sind mit der Geschwindigkeit und der erreichten Leistung zufrieden, und die Entwickler fühlen sich wohl und kreativ mit der intuitiven API.

Da das Projekt jetzt Open Source ist, ist es für jeden Interessierten sehr einfach, einen Blick darauf zu werfen, sich eine objektive Meinung darüber zu bilden und, warum nicht, es zu forken, einen PR zu öffnen und beizutragen.

Ressourcen

  • Das Open-Source-ORM-Projekt ist hier.
  • Der Quellcode der Musteranwendung ist hier.

Source:
https://dzone.com/articles/runtime-defined-columns-with-asentinel-orm