Introduction à Reladomo

Introduction à Reladomo

1. Vue d'ensemble

Reladomo (formerly known as Mithra) is an object-relational mapping (ORM) framework for Java, développé àGoldman Sachs, actuellement publié en tant que projet open-source. Le cadre fournit les fonctionnalités généralement nécessaires d’un ORM, ainsi que des fonctionnalités supplémentaires.

Voyons quelques-unes des principales fonctionnalités deReladomo:

  • il peut générer des classes Java ainsi que des scripts DDL

  • il est conduit par des métadonnées écrites dans des fichiers XML

  • le code généré est extensible

  • le langage de requête est orienté objet et fortement typé

  • la structure prend en charge le partage (même schéma, différents jeux de données)

  • le support pour les tests est également inclus

  • il fournit des fonctionnalités utiles comme la mise en cache et les transactions performantes

Dans les sections suivantes, nous verrons la configuration et quelques exemples de base d'utilisation.

2. Configuration deMaven

Pour commencer à utiliser l'ORM, nous devons ajouter la dépendancereladomo à notre fichierpom.xml:


    com.goldmansachs.reladomo
    reladomo
    16.5.1

Nous utiliserons une base de donnéesH2 pour nos exemples, ajoutons donc également la dépendanceh2:


    com.h2database
    h2
    1.4.196

In addition to this, we need to setup plugins that will generate classes and SQL files et les charger pendant l'exécution.

Pour la génération de fichiers, nous pouvons utiliser des tâches qui sont exécutées à l'aide desmaven-antrun-plugin. Voyons d'abord comment nous pouvons définir la tâche de génération des classes Java:


    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. Nous examinerons de plus près ce que contient ce fichier dans une section ultérieure.

Les tâches ont également deux propriétés qui définissent l'emplacement des fichiers générés:

  • generatedDir - contient les classes qui ne doivent pas être modifiées ou versionnées

  • nonGeneratedDir - les classes d'objets concrètes générées qui peuvent être davantage personnalisées et versionnées

Les tables de la base de données correspondant aux objets Java peuvent être créées manuellement ou automatiquement en utilisant les scripts DDL générés par une deuxième tâcheAnt:


    

Cette tâche utilise lesMithraDbDefinitionGenerator basés sur le même fichierReladomoClassList.xml mentionné précédemment. Les scripts SQL seront placés dans le répertoiregenerated-db/sql.

Pour compléter la définition de ce plugin, nous devons également ajouter deux dépendances utilisées pour la création:


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

Enfin, en utilisant lesbuild-helper-maven-plugin, nous pouvons ajouter les fichiers générés au chemin de classe:


    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/
                    
                
            
        
    

L'ajout des scripts DDL est facultatif. Dans notre exemple, nous utiliserons une base de données en mémoire. Nous souhaitons donc exécuter les scripts pour créer les tables.

3. Configuration XML

Les métadonnées du frameworkReladomo peuvent être définies dans plusieurs fichiers XML.

3.1. Fichiers XML d'objets

Chaque entité que nous voulons créer doit être définie dans son fichier XML.

Créons un exemple simple avec deux entités: les départements et les employés. Voici une représentation visuelle de notre modèle de domaine:

image

Définissons le premier fichierDepartment.xml:


    com.example.reladomo
    Department
    departments

    
    
    
         Employee.departmentId = this.id
    

Nous pouvons voir ci-dessusthe entity is defined inside a root element called MithraObject. Ensuite, nous avons spécifié le package, la classe et le nom de la table de base de données correspondante.

Chaque propriété du type est définie à l'aide d'un élémentAttribute, pour lequel nous pouvons indiquer le nom, le type Java et le nom de la colonne.

We can describe the relationships between objects using the Relationship tag. Dans notre exemple, nous avons défini une relationone-to-many entre les objetsDepartment etEmployee, sur la base de l'expression:

Employee.departmentId = this.id

L'attributreverseRelationshipName peut être utilisé pour rendre la relation bidirectionnelle sans la définir deux fois.

L'attributrelatedIsDependent nous permet de mettre en cascade les opérations.

Ensuite, créons le fichierEmployee.xml de la même manière:


    com.example.reladomo
    Employee
    employees

    
    
    

3.2. FichierReladomoClassList.xml

Reladomo doit être informé des objets qu'il doit générer.

Dans la sectionMaven, nous avons défini le fichierReladomoClassList.xml comme source pour les tâches de génération, il est donc temps de créer le fichier:


    
    

Il s’agit d’un simple fichier contenant une liste d’entités pour lesquelles des classes seront générées en fonction de la configuration XML.

4. Classes générées

Nous avons maintenant tous les éléments dont nous avons besoin pour démarrer la génération de code en construisant l'applicationMaven à l'aide de la commandemvn clean install.

Les classes concrètes seront générées dans le dossiersrc/main/java du package spécifié:

image

Ce sont des classes simples où nous pouvons ajouter notre code personnalisé. Par exemple, la classeDepartment contient uniquement un constructeur qui ne doit pas être supprimé:

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.
    }
}

Si nous voulons ajouter un constructeur personnalisé à cette classe, elle doit également appeler le constructeur parent:

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

Ces classes sont basées sur les classes abstraites et utilitaires dans le dossiergenerated-sources/reladomo:

image

Les principaux types de classes dans ce dossier sont:

  • ClassesDepartmentAbstract etEmployeeAbstract - qui contient des méthodes pour travailler avec les entités définies

  • DepartmentListAbstract etEmployeeListAbstract - qui contient des méthodes pour travailler avec des listes de départements et d'employés

  • DepartmentFinder etEmployeeFinder - ils fournissent des méthodes pour interroger les entités

  • autres classes utilitaires

En générant ces classes, une grande partie du code nécessaire à l'exécution des opérations CRUD sur nos entités est déjà créée pour nous.

5. Application Reladomo

Pour effectuer des opérations sur la base de données, nous avons besoin d'une classe de gestionnaire de connexions nous permettant d'obtenir des connexions à la base de données.

5.1. Gestionnaire de connexions

Lorsque vous travaillez avec une seule base de données, nous pouvons implémenter l'interfaceSourcelessConnectionManager:

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();
    }
    //...
}

Notre classeReladomoConnectionManager implémente le modèle singleton et est basée sur unXAConnectionManager qui est une classe utilitaire pour un gestionnaire de connexions transactionnelles.

Examinons de plus près la méthodecreateConnectionManager():

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;
}

Dans cette méthode, nous avons défini les propriétés nécessaires pour créer une connexion à une base de données en mémoireH2.

De plus, nous devons implémenter plusieurs méthodes à partir de l'interfaceSourcelessConnectionManager:

@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;
}

Enfin, ajoutons une méthode personnalisée pour exécuter les scripts DDL générés qui créent nos tables de base de données:

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();
            }
        });
    }
}

Ceci n'est bien entendu pas nécessaire pour une application de production, où vos tables ne seraient pas recréées à chaque exécution.

5.2. Initialisation deReladomo

Le processus d'initialisation deReladomo utilise un fichier de configuration qui spécifie la classe du gestionnaire de connexions et les types d'objets utilisés. Définissons un fichierReladomoRuntimeConfig.xml:


    
    
    
    

Ensuite, nous pouvons créer une classe principale où nous appelons d'abord la méthodecreateTables(), puisuse the MithraManager class to load the configuration and initialize Reladomo:

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. Exécution des opérations CRUD

Utilisons maintenant les classes générées parReladomo pour effectuer quelques opérations sur nos entités.

Commençons par créer deux objetsDepartment etEmployee, puis sauvegardons les deux en utilisant la méthodecascadeInsert():

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

Chaque objet peut également être enregistré séparément en appelant la méthodeinsert(). Dans notre exemple, il est possible d’utilisercascadeInsert() car nous avons ajouté l’attributrelatedIsDependent=true à notre définition de relation.

Pour interroger des objets, nous pouvons utiliser les classesFinder générées:

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

Les objets obtenus de cette manière sont des objets "vivants", ce qui signifie que toute modification de ceux-ci à l'aide de setters est immédiatement reflétée dans la base de données:

empFound.setName("Steven");

Pour éviter ce comportement, nous pouvons obtenir des objets détachés:

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

Pour supprimer des objets, nous pouvons utiliser la méthodedelete():

empFound.delete();

5.4. Gestion des transactions

Si nous voulons qu'un ensemble d'opérations soit exécuté ou non comme une seule unité, nous pouvons les envelopper dans une transaction:

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

6. Prise en charge des testsReladomo

Dans les sections ci-dessus, nous avons écrit nos exemples dans une classe principale Java.

Si nous voulons écrire des tests pour notre application, une solution consiste à écrire simplement le même code dans une classe de tests.

Cependant,for better test support, Reladomo also provides the MithraTestResource class. Cela nous permet d'utiliser une configuration différente et une base de données en mémoire juste pour les tests.

Tout d'abord, nous devons ajouter la dépendance supplémentairereladomo-test-util, ainsi que la dépendancejunit:


    com.goldmansachs.reladomo
    reladomo-test-util
    16.5.1


    junit
    junit
    4.12

Ensuite, nous devons créer un fichierReladomoTestConfig.xml qui utilise la classeConnectionManagerForTests:


    
        
        
        
    
 

Ce gestionnaire de connexions configure une base de donnéesH2 en mémoire utilisée uniquement pour les tests.

A convenient feature of the MithraTestResource class is that we can provide text files with test data au format suivant:

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

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

Créons une classe de testJUnit et configurons notre instanceMithraTestResource dans une méthode@Before:

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();
    }
}

Ensuite, nous pouvons écrire une simple méthode@Test qui vérifie que nos données de test ont été chargées:

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

Une fois les tests exécutés, la base de données de tests doit être effacée:

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

7. Conclusion

Dans cet article, nous avons passé en revue les principales fonctionnalités du framework ORMReladomo, ainsi que la configuration et des exemples d'utilisation courante.

Le code source des exemples peut être trouvéover on GitHub.