Einführung in Reladomo

Einführung in Reladomo

1. Überblick

Reladomo (formerly known as Mithra) is an object-relational mapping (ORM) framework for Java, entwickelt beiGoldman Sachs, derzeit als Open-Source-Projekt veröffentlicht. Das Framework bietet die Funktionen, die üblicherweise von einem ORM benötigt werden, sowie einige zusätzliche Funktionen.

Sehen wir uns einige der wichtigsten Funktionen vonReladomo an:

  • Es können sowohl Java-Klassen als auch DDL-Skripte generiert werden

  • Es wird von Metadaten gesteuert, die in XML-Dateien geschrieben wurden

  • Der generierte Code ist erweiterbar

  • Die Abfragesprache ist objektorientiert und stark typisiert

  • Das Framework bietet Unterstützung für das Sharding (dasselbe Schema, verschiedene Datasets)

  • Die Unterstützung für das Testen ist ebenfalls enthalten

  • Es bietet nützliche Funktionen wie Performant Caching und Transaktionen

In den folgenden Abschnitten sehen wir das Setup und einige grundlegende Anwendungsbeispiele.

2. Maven Setup

Um das ORM verwenden zu können, müssen wir die Abhängigkeit vonreladomozu unserer Datei vonpom.xmlhinzufügen:


    com.goldmansachs.reladomo
    reladomo
    16.5.1

Wir werden für unsere Beispiele eineH2-Datenbank verwenden. Fügen wir also auch dieh2-abhängigkeit hinzu:


    com.h2database
    h2
    1.4.196

In addition to this, we need to setup plugins that will generate classes and SQL files und laden Sie sie während der Ausführung.

Für die Dateierzeugung können wir Aufgaben verwenden, die mitmaven-antrun-pluginausgeführt werden. Lassen Sie uns zunächst sehen, wie wir die Aufgabe zum Generieren von Java-Klassen definieren können:


    maven-antrun-plugin
    
        
            generateMithra
            generate-sources
            
                run
            
            
                
                    
                    
                    
                
            
        
    

The gen-reladomo task uses the provided MithraGenerator to create Java files based on the configuration in ReladomoClassList.xml file. Wir werden uns in einem späteren Abschnitt genauer ansehen, was diese Datei enthält.

Die Aufgaben haben auch zwei Eigenschaften, die den Speicherort der generierten Dateien definieren:

  • generatedDir - enthält die Klassen, die nicht geändert oder versioniert werden sollten

  • nonGeneratedDir - die generierten konkreten Objektklassen, die weiter angepasst und versioniert werden können

Die Datenbanktabellen, die den Java-Objekten entsprechen, können entweder manuell oder automatisch mithilfe der DDL-Skripte erstellt werden, die von einer zweitenAnt-Aufgabe generiert wurden:


    

Diese Aufgabe verwendetMithraDbDefinitionGenerator basierend auf derselbenReladomoClassList.xml-Datei, die zuvor erwähnt wurde. Die SQL-Skripte werden im Verzeichnisgenerated-db/sqlabgelegt.

Um die Definition für dieses Plugin zu vervollständigen, müssen zwei Abhängigkeiten hinzugefügt werden, die für die Erstellung verwendet werden:


    maven-antrun-plugin
    
    //...
    
    
        
            com.goldmansachs.reladomo
            reladomogen
            16.5.1
        
        
            com.goldmansachs.reladomo
            reladomo-gen-util
            16.5.1
        
    

Schließlich können wir mitbuild-helper-maven-plugin die generierten Dateien zum Klassenpfad hinzufügen:


    org.codehaus.mojo
    build-helper-maven-plugin
    
        
            add-source
            generate-sources
            
                add-source
            
            
                
                    ${project.build.directory}/generated-sources/reladomo
                
            
        
        
            add-resource
            generate-resources
            
                add-resource
            
            
                
                    
                        ${project.build.directory}/generated-db/
                    
                
            
        
    

Das Hinzufügen der DDL-Skripte ist optional. In unserem Beispiel verwenden wir eine In-Memory-Datenbank. Daher möchten wir die Skripte ausführen, um die Tabellen zu erstellen.

3. XML-Konfiguration

Die Metadaten für dasReladomo-Framework können in mehreren XML-Dateien definiert werden.

3.1. Objekt-XML-Dateien

Jede Entität, die wir erstellen möchten, muss in ihrer XML-Datei definiert werden.

Lassen Sie uns ein einfaches Beispiel mit zwei Entitäten erstellen: Abteilungen und Mitarbeiter. Hier ist eine visuelle Darstellung unseres Domain-Modells:

image

Definieren wir die ersteDepartment.xml-Datei:


    com.example.reladomo
    Department
    departments

    
    
    
         Employee.departmentId = this.id
    

Wir können überthe entity is defined inside a root element called MithraObject sehen. Anschließend haben wir das Paket, die Klasse und den Namen der entsprechenden Datenbanktabelle angegeben.

Jede Eigenschaft des Typs wird mit einemAttribute-Element definiert, für das wir den Namen, den Java-Typ und den Spaltennamen angeben können.

We can describe the relationships between objects using the Relationship tag. In unserem Beispiel haben wir eineone-to-many-Beziehung zwischenDepartment undEmployee Objekten basierend auf dem Ausdruck definiert:

Employee.departmentId = this.id

Das AttributreverseRelationshipName kann verwendet werden, um die Beziehung bidirektional zu gestalten, ohne sie zweimal zu definieren.

Das AttributrelatedIsDependent ermöglicht es uns, Operationen zu kaskadieren.

Als Nächstes erstellen wir dieEmployee.xml-Datei auf ähnliche Weise:


    com.example.reladomo
    Employee
    employees

    
    
    

3.2. ReladomoClassList.xml Datei

Reladomo muss über die Objekte informiert werden, die generiert werden sollen.

Im AbschnittMaven haben wir die DateiReladomoClassList.xml als Quelle für die Generierungsaufgaben definiert. Daher ist es an der Zeit, die Datei zu erstellen:


    
    

Dies ist eine einfache Datei, die eine Liste von Entitäten enthält, für die Klassen basierend auf der XML-Konfiguration generiert werden.

4. Generierte Klassen

Jetzt haben wir alle Elemente, die wir zum Starten der Codegenerierung benötigen, indem wir die AnwendungMavenmit dem Befehlmvn clean install erstellen.

Die konkreten Klassen werden im Ordnersrc/main/java im angegebenen Paket generiert:

image

Dies sind einfache Klassen, in denen wir unseren benutzerdefinierten Code hinzufügen können. Beispielsweise enthält die KlasseDepartmentnur einen Konstruktor, der nicht entfernt werden sollte:

public class Department extends DepartmentAbstract {
    public Department() {
        super();
        // You must not modify this constructor. Mithra calls this internally.
        // You can call this constructor. You can also add new constructors.
    }
}

Wenn wir dieser Klasse einen benutzerdefinierten Konstruktor hinzufügen möchten, muss auch der übergeordnete Konstruktor aufgerufen werden:

public Department(long id, String name){
    super();
    this.setId(id);
    this.setName(name);
}

Diese Klassen basieren auf den Abstract- und Utility-Klassen im Ordnergenerated-sources/reladomo:

image

Die Hauptklassen in diesem Ordner sind:

  • KlassenDepartmentAbstract undEmployeeAbstract - enthält Methoden zum Arbeiten mit den definierten Entitäten

  • DepartmentListAbstract undEmployeeListAbstract - enthält Methoden zum Arbeiten mit Listen von Abteilungen und Mitarbeitern

  • DepartmentFinder undEmployeeFinder - diese bieten Methoden zum Abfragen von Entitäten

  • andere Gebrauchsklassen

Durch das Generieren dieser Klassen wird bereits ein großer Teil des Codes, der zum Ausführen von CRUD-Operationen für unsere Entitäten erforderlich ist, für uns erstellt.

5. Reladomo-Anwendung

Um Vorgänge in der Datenbank ausführen zu können, benötigen wir eine Verbindungsmanagerklasse, mit der wir Datenbankverbindungen erhalten können.

5.1. Verbindungsmanager

Wenn wir mit einer einzelnen Datenbank arbeiten, können wir dieSourcelessConnectionManager-Schnittstelle implementieren:

public class ReladomoConnectionManager implements SourcelessConnectionManager {

    private static ReladomoConnectionManager instance;
    private XAConnectionManager xaConnectionManager;

    public static synchronized ReladomoConnectionManager getInstance() {
        if (instance == null) {
            instance = new ReladomoConnectionManager();
        }
        return instance;
    }

    private ReladomoConnectionManager() {
        this.createConnectionManager();
    }
    //...
}

UnsereReladomoConnectionManager-Klasse implementiert das Singleton-Muster und basiert auf einemXAConnectionManager, einer Dienstprogrammklasse für einen Transaktionsverbindungsmanager.

Schauen wir uns die MethodecreateConnectionManager()genauer an:

private XAConnectionManager createConnectionManager() {
    xaConnectionManager = new XAConnectionManager();
    xaConnectionManager.setDriverClassName("org.h2.Driver");
    xaConnectionManager.setJdbcConnectionString("jdbc:h2:mem:myDb");
    xaConnectionManager.setJdbcUser("sa");
    xaConnectionManager.setJdbcPassword("");
    xaConnectionManager.setPoolName("My Connection Pool");
    xaConnectionManager.setInitialSize(1);
    xaConnectionManager.setPoolSize(10);
    xaConnectionManager.initialisePool();
    return xaConnectionManager;
}

Bei dieser Methode legen wir die Eigenschaften fest, die zum Herstellen einer Verbindung zu einer speicherinternenH2-Datenbank erforderlich sind.

Außerdem müssen wir verschiedene Methoden über dieSourcelessConnectionManager-Schnittstelle implementieren:

@Override
public Connection getConnection() {
    return xaConnectionManager.getConnection();
}

@Override
public DatabaseType getDatabaseType() {
    return H2DatabaseType.getInstance();
}

@Override
public TimeZone getDatabaseTimeZone() {
    return TimeZone.getDefault();
}

@Override
public String getDatabaseIdentifier() {
    return "myDb";
}

@Override
public BulkLoader createBulkLoader() throws BulkLoaderException {
    return null;
}

Fügen Sie abschließend eine benutzerdefinierte Methode hinzu, um die generierten DDL-Skripts auszuführen, mit denen unsere Datenbanktabellen erstellt werden:

public void createTables() throws Exception {
    Path ddlPath = Paths.get(ClassLoader.getSystemResource("sql").toURI());
    try (
      Connection conn = xaConnectionManager.getConnection();
      Stream list = Files.list(ddlPath)) {

        list.forEach(path -> {
            try {
                RunScript.execute(conn, Files.newBufferedReader(path));
            }
            catch (SQLException | IOException exc){
                exc.printStackTrace();
            }
        });
    }
}

Dies ist natürlich nicht für eine Produktionsanwendung erforderlich, bei der Ihre Tabellen nicht bei jeder Ausführung neu erstellt werden.

5.2. Reladomo wird initialisiert

Der Initialisierungsprozess vonReladomoverwendet eine Konfigurationsdatei, die die Verbindungsmanagerklasse und die verwendeten Objekttypen angibt. Definieren wir eineReladomoRuntimeConfig.xml-Datei:


    
    
    
    

Als nächstes können wir eine Hauptklasse erstellen, in der wir zuerst die MethodecreateTables() und dannuse the MithraManager class to load the configuration and initialize Reladomo aufrufen:

public class ReladomoApplication {
    public static void main(String[] args) {
        try {
            ReladomoConnectionManager.getInstance().createTables();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        MithraManager mithraManager = MithraManagerProvider.getMithraManager();
        mithraManager.setTransactionTimeout(120);

        try (InputStream is = ReladomoApplication.class.getClassLoader()
          .getResourceAsStream("ReladomoRuntimeConfig.xml")) {
            MithraManagerProvider.getMithraManager()
              .readConfiguration(is);

            //execute operations
        }
        catch (IOException exc){
            exc.printStackTrace();
        }
    }
}

5.3. CRUD-Operationen durchführen

Verwenden wir jetzt die vonReladomo generierten Klassen, um einige Operationen an unseren Entitäten auszuführen.

Zuerst erstellen wir zweiDepartment- undEmployee-Objekte und speichern dann beide mit dercascadeInsert()-Methode:

Department department = new Department(1, "IT");
Employee employee = new Employee(1, "John");
department.getEmployees().add(employee);
department.cascadeInsert();

Jedes Objekt kann auch separat gespeichert werden, indem die Methodeinsert()aufgerufen wird. In unserem Beispiel ist es möglich,cascadeInsert() zu verwenden, da wir das AttributrelatedIsDependent=true zu unserer Beziehungsdefinition hinzugefügt haben.

Zum Abfragen von Objekten können wir die generiertenFinder-Klassen verwenden:

Department depFound = DepartmentFinder
  .findByPrimaryKey(1);
Employee empFound = EmployeeFinder
  .findOne(EmployeeFinder.name().eq("John"));

Die Objekte, die auf diese Weise erhalten werden, sind "lebende" Objekte, was bedeutet, dass jede Änderung an ihnen unter Verwendung von Setzern sofort in der Datenbank wiedergegeben wird:

empFound.setName("Steven");

Um dieses Verhalten zu vermeiden, können wir getrennte Objekte erhalten:

Department depDetached = DepartmentFinder
  .findByPrimaryKey(1).getDetachedCopy();

Um Objekte zu entfernen, können wir die Methodedelete()verwenden:

empFound.delete();

5.4. Transaktionsmanagement

Wenn wir möchten, dass eine Gruppe von Operationen als eine Einheit ausgeführt wird oder nicht, können wir sie in eine Transaktion einschließen:

mithraManager.executeTransactionalCommand(tx -> {
    Department dep = new Department(2, "HR");
    Employee emp = new Employee(2, "Jim");
    dep.getEmployees().add(emp);
    dep.cascadeInsert();
    return null;
});

6. Reladomo Testunterstützung

In den obigen Abschnitten haben wir unsere Beispiele in einer Java-Hauptklasse geschrieben.

Wenn wir Tests für unsere Anwendung schreiben möchten, besteht eine Möglichkeit darin, einfach denselben Code in eine Testklasse zu schreiben.

for better test support, Reladomo also provides the MithraTestResource class. Dies ermöglicht es uns, nur für die Tests eine andere Konfigurations- und In-Memory-Datenbank zu verwenden.

Zuerst müssen wir die zusätzliche Abhängigkeit vonreladomo-test-utilzusammen mit der Abhängigkeit vonjunithinzufügen:


    com.goldmansachs.reladomo
    reladomo-test-util
    16.5.1


    junit
    junit
    4.12

Als nächstes müssen wir eineReladomoTestConfig.xml-Datei erstellen, die dieConnectionManagerForTests-Klasse verwendet:


    
        
        
        
    
 

Dieser Verbindungsmanager konfiguriert eine speicherinterneH2-Datenbank, die nur für Tests verwendet wird.

A convenient feature of the MithraTestResource class is that we can provide text files with test data im folgenden Format:

class com.example.reladomo.Department
id, name
1, "Marketing"

class com.example.reladomo.Employee
id, name
1, "Paul"

Erstellen wir eineJUnit-Testklasse und richten unsereMithraTestResource-Instanz in einer@Before-Methode ein:

public class ReladomoTest {
    private MithraTestResource mithraTestResource;

    @Before
    public void setUp() throws Exception {
        this.mithraTestResource
          = new MithraTestResource("reladomo/ReladomoTestConfig.xml");

        ConnectionManagerForTests connectionManager
          = ConnectionManagerForTests.getInstanceForDbName("testDb");
        this.mithraTestResource.createSingleDatabase(connectionManager);
        mithraTestResource.addTestDataToDatabase("reladomo/test-data.txt",
          connectionManager);

        this.mithraTestResource.setUp();
    }
}

Dann können wir eine einfache@Test-Methode schreiben, die überprüft, ob unsere Testdaten geladen wurden:

@Test
public void whenGetTestData_thenOk() {
    Employee employee = EmployeeFinder.findByPrimaryKey(1);
    assertEquals(employee.getName(), "Paul");
}

Nachdem die Tests ausgeführt wurden, muss die Testdatenbank gelöscht werden:

@After
public void tearDown() throws Exception {
    this.mithraTestResource.tearDown();
}

7. Fazit

In diesem Artikel wurden die Hauptfunktionen des ORM-Frameworks vonReladomoowie das Setup und Beispiele für die allgemeine Verwendung erläutert.

Der Quellcode für die Beispiele lautetover on GitHub.