Einreichungsmechanismus - Spezielle kubanische Magie

Published on October 09, 2018

Einreichungsmechanismus - Spezielle kubanische Magie

Ursprünglicher Autor: Mario David
  • Übersetzung

CPDV


Views oder Views ist eines der Konzepte der CUBA-Plattform, das in der Welt der Webframeworks nicht das gängigste ist. Um dies zu verstehen, müssen Sie sich dumme Fehler ersparen, wenn die Anwendung aufgrund unvollständig geladener Daten plötzlich nicht mehr funktioniert. Mal sehen, was die Präsentation ist (Wortspiel) und warum es eigentlich praktisch ist.


Das Problem der entladenen Daten


Nehmen Sie das Thema leichter und betrachten Sie das Problem in ihrem Beispiel. Angenommen, wir haben eine Customer- Entität , die sich auf die CustomerType- Entität in Bezug auf die Many-to-One- Entität bezieht. Mit anderen Worten, der Käufer hat einen Verweis auf einen Typ, der dies beschreibt: z. B. eine Cash Cow, einen Barkman usw. ZUSAMMENFASSUNG Customer hat ein Attribut Name , der den Typnamen speichert.


Und wahrscheinlich haben alle Neulinge (oder sogar fortgeschrittene Benutzer) in CUBA früher oder später den folgenden Fehler erhalten:


IllegalStateException: Cannot get unfetched attribute [type] from detached object com.rtcab.cev.entity.Customer-e703700d-c977-bd8e-1a40-74afd88915af [detached].


Fehler Das nicht abgerufene Attribut kann in der CUBA-Benutzeroberfläche nicht abgerufen werden


Gib es zu, du hast es auch mit deinen eigenen Augen gesehen? Ich bin in hundert verschiedenen Situationen. In diesem Artikel werden wir die Ursache dieses Problems untersuchen, warum es überhaupt existiert und wie man es löst.
Zunächst eine kleine Einführung in das Konzept der Ansichten.


Was ist die Präsentation?


Eine Ansicht in CUBA besteht im Wesentlichen aus einer Reihe von Spalten in einer Datenbank, die zusammen in eine einzelne Abfrage geladen werden müssen.


Angenommen, Sie möchten eine Benutzeroberfläche mit einer Kundentabelle erstellen, in der die erste Spalte den Namen des Käufers und die zweite den Typnamen aus dem Attribut customerType enthält (siehe Abbildung oben). Es ist logisch anzunehmen, dass in diesem Datenmodell zwei separate Tabellen in der Datenbank vorhanden sind, eine für die Entität Customer und eine für den CustomerType . Die Anforderung SELECT * from CEV_CUSTOMERgibt nur Daten aus einer Tabelle (Attribut nameusw.) an uns zurück . Um Daten aus anderen Tabellen abzurufen, verwenden wir natürlich JOINs.


Bei der Verwendung klassischer SQL-Abfragen mit JOIN wird die Hierarchie der Zuordnungen (Referenzattribute) von einem Diagramm zu einer flachen Liste erweitert.
Anmerkung des Übersetzers: Mit anderen Worten, die Beziehungen zwischen den Tabellen werden gelöscht, und das Ergebnis wird in einem einzelnen Datenarray dargestellt, das den Join der Tabellen darstellt.


Im Fall von CUBA wird ORM verwendet, das keine Informationen über die Beziehungen zwischen Entitäten verliert und das Ergebnis von Abfragen als vollständige Grafik der angeforderten Daten darstellt. In diesem Fall wird JPQL, das Objektanalogon von SQL, als Abfragesprache verwendet.


Die Daten müssen jedoch noch irgendwie aus der Datenbank entladen und in ein Entitätsdiagramm umgewandelt werden. Zu diesem Zweck gibt es im objektrelationalen Zuordnungsmechanismus (JPA) zwei Hauptansätze für Abfragen an die Basis.


Lazy loading vs. eifrig holen


Lazy Loading und Greedy Loading sind zwei mögliche Strategien zum Abrufen von Daten aus der Datenbank. Der grundlegende Unterschied zwischen beiden ist der Zeitpunkt, zu dem Daten aus verknüpften Tabellen geladen werden. Ein kleines Beispiel zum besseren Verständnis:


Erinnern Sie sich an die Szene aus dem Buch "Der Hobbit oder hin und zurück", in der eine Gruppe von Zwergen in Begleitung von Gandalf und Bilbo versucht, Beorns Haus nach einem Bett zu fragen? Gandalf befahl den Zwergen, streng nacheinander zu erscheinen, und erst, nachdem er Beorn sorgfältig zugestimmt hatte und sie nacheinander vorstellte, um den Besitzer nicht durch die Notwendigkeit zu schockieren, 15 Gäste gleichzeitig unterzubringen.


Gandalf und die Zwerge im Haus Beorn


Also, Gandalf und die Zwerge in Beorns Haus ... Vielleicht ist dies nicht das erste, was mir bei dem Gedanken an faule und gierige Ladungen einfällt, aber es gibt definitiv eine Ähnlichkeit. Gandalf handelte hier mit Bedacht, da er sich der Grenzen bewusst war. Man kann sagen, dass er sich bewusst für das langsame Laden der Zwerge entschieden hat, da ihm klar war, dass das Herunterladen aller Daten für diese Datenbank sofort zu schwierig sein würde. Nach dem 8. Zwerg wechselte Gandalf jedoch zum gierigen Laden und lud eine Packung verbleibender Zwerge, weil er bemerkte, dass zu viele Verweise auf die Datenbank sie nicht weniger irritierten.


Die Moral ist, dass sowohl faule als auch gierige Lasten ihre Vor- und Nachteile haben. Was in jeder Situation angewendet werden soll, entscheiden Sie.


N + 1 Abfrageproblem


Das Problem, N + 1 anzufordern, tritt häufig auf, wenn Sie gedankenlos und überall faul laden. Schauen wir uns zur Veranschaulichung einen Teil des Grails-Codes an. Dies bedeutet nicht, dass alles, was in Grails geladen wird, faul ist (Sie können die Download-Methode auch selbst auswählen). In Grails gibt eine Abfrage an die Datenbank standardmäßig Instanzen von Entitäten mit allen Attributen aus ihrer Tabelle zurück. Im Wesentlichen zufrieden SELECT * FROM Pet.


Wenn Sie die Beziehung zwischen Entitäten vertiefen möchten, müssen Sie dies nachträglich tun. Hier ist ein Beispiel:


function getPetOwnerNamesForPets(String nameOfPet) {
  def pets = Pet.findAll(sort:"name") {
       name == nameOfPet
  }
  def ownerNames = []
  pets.each {
    ownerNames << it.owner.name
  }
  return ownerNames.join(", ")
}

Durchlaufen der Graph hier führt eine einzige Zeile: it.owner.name. Eigentümer ist eine Beziehung, die nicht in die ursprüngliche Abfrage ( Pet.findAll) geladen wurde . Jedes Mal, wenn diese Zeile aufgerufen wird, wird GORM so etwas tun SELECT * FROM Person WHERE id=’…’. Sauberes Wasser faul laden.


Wenn Sie die Gesamtzahl der SQL-Abfragen zählen, erhalten Sie N (einen Host für jeden Aufruf it.owner) + 1 (für den ersten Pet.findAll). Wenn Sie das Diagramm verwandter Entitäten vertiefen möchten, wird Ihre Datenbank wahrscheinlich schnell die Grenze ihrer Funktionen finden.


Als Entwickler werden Sie dies kaum bemerken, da Sie sich aus Ihrer Sicht nur um den Objektgraphen drehen. Diese versteckte Verschachtelung in einer kurzen Zeile schmerzt die Datenbank und macht das langsame Laden manchmal gefährlich.


Bei der Entwicklung der hobbischen Analogie könnte sich das N + 1-Problem folgendermaßen manifestieren: Stellen Sie sich vor, Gandalf könne sich die Namen der Zwerge nicht merken. Wenn er die Zwerge einzeln vorstellt, ist er gezwungen, sich zu seiner Gruppe zurückzuziehen und den Zwerge nach seinem Namen zu fragen. Mit diesen Informationen kehrt er zu Beorn zurück und stellt Thorin vor. Dann wiederholt er dieses Manöver für Bifur, Bofur, Fili, Kili, Dory, Nori, Ori, Oina, Gloin, Balin, Dvalin und Bombur.


Das Problem von N + 1 bei den Zwergen


Es ist nicht schwer vorstellbar, dass Beorn ein solches Szenario kaum gefallen hätte: Welcher Empfänger würde so lange auf die angeforderten Informationen warten wollen? Aus diesem Grund sollten Sie diesen Ansatz nicht sinnlos anwenden und sich blind auf die Standardeinstellungen Ihres Persistenz-Mappers verlassen.


Lösen des Problems von N + 1-Abfragen mithilfe von CUBA-Ansichten


In CUBA werden Sie höchstwahrscheinlich nie auf das Problem von N + 1-Abfragen stoßen, da entschieden wurde, überhaupt keinen versteckten Lazy Boot auf der Plattform zu verwenden. Stattdessen führte CUBA das Konzept der Repräsentation ein. Ansichten sind eine Beschreibung der Attribute, die ausgewählt und zusammen mit Entitätsinstanzen geladen werden sollen. So etwas wie


SELECT pet.name, person.name FROM Pet pet JOIN Person person ON pet.owner == person.id

Einerseits beschreibt die Ansicht die Spalten, die aus der Haupttabelle ( Pet ) geladen werden müssen (anstatt alle Attribute über * zu laden), andererseits beschreibt sie auch die Spalten, die aus den c-JOIN-Tabellen geladen werden sollen.


Sie können sich eine CUBA-Ansicht als eine SQL-Ansicht für einen OR-Mapper vorstellen: Das Funktionsprinzip ist ungefähr dasselbe.


Auf der CUBA-Plattform können Sie keine Abfrage über den DataManager aufrufen, ohne die Ansicht zu verwenden. Die Dokumentation enthält ein Beispiel:


@Inject
private DataManager dataManager;
private Book loadBookById(UUID bookId) {
    LoadContext<Book> loadContext = LoadContext.create(Book.class)
            .setId(bookId).setView("book.edit");
    return dataManager.load(loadContext);
}

Hier möchten wir das Buch anhand seiner ID herunterladen. Die Methode setView("book.edit")beim Erstellen des Ladekontexts gibt an, mit welcher Ansicht das Buch aus der Datenbank geladen werden soll. Wenn Sie keine Ansicht übergeben, verwendet der Datenmanager eine der drei Standardansichten, über die jede Entität verfügt: die _local-Ansicht . Attribute, die nicht auf andere Tabellen verweisen, werden hier als lokal bezeichnet, alles ist einfach.


Lösen des Problems mit IllegalStateException über Views


Nachdem wir das Konzept der Repräsentationen ein wenig verstanden haben, kehren wir zum ersten Beispiel vom Anfang des Artikels zurück und versuchen zu verhindern, dass die Ausnahme ausgelöst wird.


Die Meldung IllegalStateException: Das nicht abgerufene Attribut [] kann nicht von einem getrennten Objekt abgerufen werden. Dies bedeutet nur, dass Sie versuchen, ein Attribut anzuzeigen, das nicht in der Ansicht enthalten ist, mit der die Entität geladen wird.


Wie Sie sehen, habe ich im Deskriptor des Suchbildschirms die _local-Ansicht verwendet , und das ist das Problem:


<dsContext>
    <groupDatasource id="customersDs"
                     class="com.rtcab.cev.entity.Customer"
                     view="_local">
        <query>
            <![CDATA[select e from cev$Customer e]]>
        </query>
    </groupDatasource>
</dsContext>

Um den Fehler zu beheben, müssen Sie zuerst den Kundentyp in die Ansicht aufnehmen. Da wir die Standardansicht _local nicht ändern können, können wir unsere eigene erstellen. In Studio kann dies beispielsweise folgendermaßen erfolgen (Rechtsklick auf Objekte> Ansicht erstellen):


Erstellen einer Ansicht in Studio


oder direkt im views.xml- Deskriptor unserer Anwendung:


<view class="com.rtcab.cev.entity.Customer"
      extends="_local"
      name="customer-view">
    <property name="type"
              view="_minimal"/>
</view>

Danach ändern wir den Link zur Ansicht im Suchbildschirm wie folgt:


<groupDatasource id="customersDs"
   class="com.rtcab.cev.entity.Customer"
   view="customer-view">
    <query>
        <![CDATA[select e from cev$Customer e]]>
    </query>
</groupDatasource>

Damit ist das Problem vollständig gelöst und die Referenzdaten werden auf dem Kundenansichtsbildschirm angezeigt.


_Minimalansicht und das Konzept des Instanznamens


Was im Kontext von Ansichten noch erwähnenswert ist, ist die _Minimalansicht . Eine lokale Ansicht ist sehr klar definiert: Sie enthält alle Attribute einer Entität, die die unmittelbaren Attribute einer bestimmten Tabelle sind (bei denen es sich nicht um Fremdschlüssel handelt).


Die Definition der Mindestrepräsentation ist nicht so offensichtlich, aber auch ziemlich klar.


In CUBA gibt es das Konzept einer Instanz mit Entitätsnamen - Instanzname. Ein Instanzname entspricht toString()einer guten alten Java- Methode . Dies ist eine Zeichenfolgendarstellung einer Entität, die auf der Benutzeroberfläche angezeigt und in den Links verwendet wird. Der Instanzname wird mithilfe der NamePattern- Entitätsanmerkung angegeben .


Verwenden Sie es wie folgt aus : @NamePattern("%s (%s)|name,code"). Wir haben zwei Ergebnisse:


Der Instanzname definiert die Entitätszuordnung in der Benutzeroberfläche.


Zunächst einmal wird der Instanzname festgestellt , dass und in welcher Reihenfolge in der Benutzeroberfläche angezeigt werden, wenn ein Unternehmen an ein anderes Unternehmen bezieht (wie Kunde bezieht sich auf Customer ).


In unserem Fall wird der Kundentyp als Instanzname CustomerType angezeigt , dem Code in Klammern hinzugefügt wurde. Wenn der Instanzname nicht angegeben wird, werden der Name der Entitätsklasse und die ID der bestimmten Instanz angezeigt. Stimmen Sie zu, dass dies für den Benutzer überhaupt nicht wünschenswert ist. Beispiele für beide Fälle finden Sie in den Screenshots „Vorher und Nachher“.


Ein Verweis auf eine Entität, für die kein Instanzname angegeben ist.


Eine Entitätsreferenz mit dem angegebenen Instanznamen.


Der Instanzname definiert die Attribute der Minimalansicht.


Das zweite, was sich auf die NamePattern-Annotation auswirkt: Alle nach der vertikalen Leiste angegebenen Attribute bilden automatisch die _minimal- Ansicht. Auf den ersten Blick scheint dies offensichtlich, da die Daten in irgendeiner Form in der Benutzeroberfläche angezeigt werden müssen, was bedeutet, dass sie zuerst aus der Datenbank geladen werden müssen. Obwohl ich, um ehrlich zu sein, selten darüber nachdenke.


Hierbei ist zu beachten, dass die Mindestrepräsentation im Vergleich zur lokalen Repräsentation Verweise auf andere Entitäten enthalten kann. Für den Käufer aus dem obigen Beispiel habe ich beispielsweise einen Instanznamen festgelegt, der ein lokales Attribut der Entität Customer ( name) und ein Referenzattribut ( type) enthält:


@NamePattern("%s - %s|name,type")

Die minimale Darstellung kann rekursiv verwendet werden: (Kunde [Instanzname] -> Kundentyp [Instanzname])


Anmerkung des Übersetzers: Seit der Veröffentlichung des Artikels ist eine andere Systemansicht erschienen - _baseAnsicht, die alle lokalen Nicht-Systemattribute und Attribute enthält, die in der Annotation @NamePattern angegeben sind (dh tatsächlich _minimal+ _local).


Fazit


Fassen Sie abschließend das wichtigste Thema zusammen. Mit Views können wir in CUBA explizit festlegen, was aus der Datenbank geladen werden soll. Views bestimmen auf gierige Weise, was geladen wird, während die meisten anderen Frameworks stillschweigend ein verzögertes Laden durchführen.


Einreichungen mögen wie ein umständlicher Mechanismus erscheinen, aber auf lange Sicht zahlen sie sich aus.


Hoffentlich kann ich auf einfache Weise erklären, was diese mysteriösen Ansichten wirklich sind. Natürlich gibt es fortgeschrittenere Szenarien für ihre Verwendung sowie Fallstricke bei der Arbeit mit Ideen im Allgemeinen und mit minimalen Ideen im Besonderen, aber ich werde darüber in einem separaten Beitrag schreiben.