Langzeitgedächtnis

Freiland-Haltung

Bei einer graphischen Desktop-Anwendung sind die Transaktionsgrenzen anders, als bei Anwendungsdiensten eines Anwendungsservers. Während bei letzterem davon ausgegangen werden muss, dass der Dienst Teil einer größeren Transaktion ist, ist es bei einer Desktopanwendung so, dass diese den äußersten Rahmen einer Transaktion darstellt.

Somit macht es wenig Sinn, einen EntityManager als Basis für eine Transaktion zu nehmen.

Bei SR-JRC können viele EntityManager an einer Transaktion beteiligt sein. Genauso kann auch ein Anwendungsdienst eines entfernten Servers Teil der Transaktion sein. Aus diesem Grund spielt der EntityManager für die Anwendung keine Rolle und tritt dort auch nicht in Erscheinung.

Trotzdem muss ein Entwickler wissen, dass der EntityManager die Basis für eine bestimmte Art der Daten-Speicherung ist.

Beispiel

TransactionFactory taFactory = ApplicationServiceProvider.getService(TransactionFactory.class);
Transaction ta = taFactory.createTransaction();

ta.add(new TOSave(getAppConfig()));
ta.execute();
Im ersten Beispiel speichert ein Anwendungsmodul seine Konfiguration als Preferences, im zweiten Beispiel wird eine Entität in einer SQL-Datenbank gespeichert.
TransactionFactory taFactory = ApplicationServiceProvider.getService(TransactionFactory.class);
Transaction ta = taFactory.createTransaction();

ta.add(new TOSave(any));
ta.execute();
Wie man sehen kann, ist es für die Anwendung kein Unterschied, wo oder in welchem Format gespeichert wird. Es ist auch nicht von Belang, wer die eigentliche Speicherung durchführt.

Frage der Zuständigkeit

Die Zuständigkeit eines EntityManagers wird aus dem Anwendungskontext und der Entitätsklasse ermittelt. Die Konfiguration des EntityManagers erfolgt so:

<bean id="repository" class="de.schwarzrot.data.access.Repository">
    <property name="exporter" ref="entityExporter" />
    <property name="importer" ref="entityImporter" />
    <property name="managers">
        <map>
            <entry key="de.schwarzrot.app.config.support.AbstractConfigBase"
             value-ref="prefsEntityManager" />
            <entry key="de.schwarzrot.data.VdrEntity"
             value-ref="vdrEntityManager" />
            <entry key="de.schwarzrot.data.Entity"
             value-ref="jdbcEntityManager" />
            <entry key="java.lang.Object"
             value-ref="baseEntityManager" />
        </map>
    </property>
</bean>
Auf diese Weise kann zu jedem beliebigen Zeitpunkt eine neue Entität ins Spiel gebracht werden und ohne die Einstellungen anpassen zu müssen, ist die Zuständigkeit geklärt:
  • alle Instanzen vom Typ AbstractConfigBase, sowie alle ihre Kinder werden vom PreferencesEntityManager verarztet
  • alle Instanzen vom Typ VdrEntity, sowie alle ihre Kinder werden vom VdrEntityManager betreut
  • alle Instanzen vom Type Entity, sowie alle ihre Kinder werden vom JDBCEntityManager behandelt
  • für alle anderen Klassen ist der BaseEntityManager zuständig
Sowohl AbstractConfigBase, als auch VdrEntity sind Nachfahren von Entity - und alle sind natürlich auch ein java.lang.Object - es gilt also bei der Ermittlung der Zuständigkeit Hierarchien zu beachten. Das bietet SR-JRC frei Haus.

Arten-Vielfalt

In VdrAssistant gibt es 3 Arten von EntityManager, einen für Java Preferences, einen für SQL-Datenbanken und einen dritten für Spezialfälle (der JDBCEntityManager verwendet noch eine Reihe von Helferlein, um unterschiedliche Datenbank-Dialekte behandeln zu können. Aber das ist ein anderes Thema).

Ein solcher Spezialfall ist der Fall, wenn ein proprietäres System parallel zur SQL-Datenbank gepflegt werden muss. Dann können alle Lesezugriffe auf die Datenbank erfolgen, Schreibzugriffe müssen aber sowohl in der Datenbank, als auch im proprietären System erfolgen. Im konkreten Anwendungsfall ist die Schnittstelle zum proprietären System ein proprietäres TCP/IP Protokoll und der EntityManager ist ein Wrapper, der die meisten Anfragen an einen JDBCEntityManager weiter leitet und nur beim Schreibzugriff selbst aktiv wird.

Die Anwendung selbst muss von dem Prozedere nichts wissen. Für sie wird eine VdrEntity wie jede andere Entity behandelt.

Airbag notwendig?

Transaktionen kann man in 2 Kategorien einteilen: lesende und schreibende. Schreibende Transaktionen sind bereits im Abschnitt "Beispiel" aufgeführt. Bei den lesenden Transaktionen gibt es viele Spielarten. Wichtig ist, dem TransaktionsManager mitzuteilen, dass dies eine lesende Transaktion ist. Dazu gibt es die Methode setRollbackOnly.

Hier ein Beispiel, wie der Job-Verarbeiter die zu verarbeitende Aufnahme nachliest:

Transaction ta = taFactory.createTransaction();
TORead<Recording> tor = new TORead<Recording>(Recording.class);

tor.addCondition(new EqualConditionElement("id", job.getSubject()));
tor.setReadRelated(true);
ta.add(tor);
ta.setRollbackOnly();
ta.execute();

if (tor.getResult() != null && tor.getResult().size() > 0) {
    job.setSubject(tor.getResult().get(0));
}
   
Erwähnenswert sind bei diesem Beispiel ferner die Erstellung der Bedingung, sowie der Auftrag, alle Referenzen der Entität aufzulösen.

Einer der Vorzüge von SR-JRC ist sicherlich der Umstand, dass man aus der API heraus bestimmen kann, ob Referenzen aufgelöst werden sollen, oder nicht.

Bei der Bedingung ist es so, dass job.getSubject() bereits eine Entität liefert. Die Aufnahme-Entität hat ein Attribut namens "id" und dieses muss mit der übergebenen Entität übereinstimmen. Klar - eine Entität ist keine ID - also muss jemand helfen. Dafür gibt es die Übersetzungshelferlein, die eine Entität in eine ID übersetzen können - denn schließlich hat jede Entität die Zusicherung, eine ID zu haben.

Nach Ausführung der Transaktion wird geprüft, ob die Leseoperation erfolgreich war und eine Aufnahme gefunden wurde. Falls dem so ist, wird das Subjekt des Jobs ersetzt.

Spielarten

In SR-JRC gibt es folgende Transaktions-Operationen:


TOCount
ermittelt die Anzahl der Datensätze, die den Kriterien entsprechen

TORead
liest Datensätze (auch teilweise), die den Kriterien entsprechen. Optional können Relationen aufgelöst, d.h. mit gelesen werden.

TORemove
löscht Datensätze, die den Kriterien entsprechen

TOSave
speichert die übergebene(n) Instanz(en)

TOSetProperty
speichert einzelne Felder der übergebenen Instanz(en)

TOContextOperation
bietet die Möglichkeit, innerhalb einer Transaktion beteiligte Instanzen zu manipulieren

Er sagt, sie sagt ...

Viele Werte werden in einer Java-Anwendung anders verwendet, bzw. interpretiert, als in einer Datenbank oder in einer Datei. Aus diesem Grund verwenden EntityManager Übersetzungshilfen, wenn sie lesen oder schreiben. Solche Übersetzungshilfen sind vom Typ de.schwarzrot.data.support.Converter. Die wichtigsten werden bereits vom Framework ins Spiel gebracht. Die ConverterFactory ist ein veröffentlichter Anwendungsdienst und so können auch in der Anwendung die Übersetzungshilfen verwendet werden.

Natürlich kann eine Anwendung auch eigene Übersetzungshelferlein anmelden oder vorhandene ersetzen.

Die Schnittstelle der Übersetzungshelfer ist wie folgt definiert:

public interface Converter {
    public Object fromPersistence(Class type, Object value);

    public Class getValueType();

    public Object toPersistence(Object value, int physicalSize);
}
   
Bei fromPersistence wird per "type" angegeben, welche Klasse das Attribut in der Java-Welt haben soll. Diese Information ist im umgekehrten Falle nicht erforderlich. Bei toPersistence kann es notwendig sein, den Wert aus der Java-Welt zu beschneiden. Sonst könnte es zu Unstimmigkeiten mit der Datenbank kommen.