Introdução ao Reladomo

Introdução ao Reladomo

1. Visão geral

Reladomo (formerly known as Mithra) is an object-relational mapping (ORM) framework for Java, desenvolvido emGoldman Sachs, atualmente lançado como um projeto de código aberto. A estrutura fornece os recursos normalmente necessários de um ORM, bem como alguns adicionais.

Vamos ver alguns dos principais recursos deReladomo:

  • ele pode gerar classes Java e scripts DDL

  • é conduzido por metadados gravados em arquivos XML

  • o código gerado é extensível

  • a linguagem de consulta é orientada a objeto e fortemente tipada

  • a estrutura fornece suporte para sharding (mesmo esquema, conjuntos de dados diferentes)

  • o suporte para testes também está incluído

  • fornece recursos úteis como cache e transações de alto desempenho

Nas seções a seguir, veremos a configuração e alguns exemplos básicos de uso.

2. Configuração deMaven

Para começar a usar o ORM, precisamos adicionar a dependênciareladomo ao nosso arquivopom.xml:


    com.goldmansachs.reladomo
    reladomo
    16.5.1

Usaremos um banco de dadosH2 para nossos exemplos, então vamos adicionar também a dependênciah2:


    com.h2database
    h2
    1.4.196

In addition to this, we need to setup plugins that will generate classes and SQL files, e carregue-os durante a execução.

Para geração de arquivo, podemos usar tarefas que são executadas usandomaven-antrun-plugin. Primeiro, vamos ver como podemos definir a tarefa para gerar 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. Daremos uma olhada mais de perto no que este arquivo contém em uma seção posterior.

As tarefas também possuem duas propriedades que definem o local dos arquivos gerados:

  • generatedDir - contém as classes que não devem ser modificadas ou versionadas

  • nonGeneratedDir - as classes de objetos concretos geradas que podem ser posteriormente personalizadas e com controle de versão

As tabelas de banco de dados correspondentes aos objetos Java podem ser criadas manualmente ou automaticamente usando os scripts DDL gerados por uma segunda tarefaAnt:


    

Esta tarefa usaMithraDbDefinitionGenerator com base no mesmo arquivoReladomoClassList.xml mencionado antes. Os scripts SQL serão colocados no diretóriogenerated-db/sql.

Para concluir a definição desse plug-in, também precisamos adicionar duas dependências usadas para a criação:


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

Finalmente, usandobuild-helper-maven-plugin, podemos adicionar os arquivos gerados ao classpath:


    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/
                    
                
            
        
    

A adição de scripts DDL é opcional. Em nosso exemplo, usaremos um banco de dados na memória, portanto, queremos executar os scripts para criar as tabelas.

3. Configuração XML

Os metadados para a estruturaReladomo podem ser definidos em vários arquivos XML.

3.1. Arquivos XML de objeto

Cada entidade que queremos criar precisa ser definida em seu arquivo XML.

Vamos criar um exemplo simples com duas entidades: departamentos e funcionários. Aqui está uma representação visual do nosso modelo de domínio:

image

Vamos definir o primeiro arquivoDepartment.xml:


    com.example.reladomo
    Department
    departments

    
    
    
         Employee.departmentId = this.id
    

Podemos ver acimathe entity is defined inside a root element called MithraObject. Em seguida, especificamos o pacote, a classe e o nome da tabela de banco de dados correspondente.

Cada propriedade do tipo é definida usando um elementoAttribute, para o qual podemos indicar o nome, o tipo de Java e o nome da coluna.

We can describe the relationships between objects using the Relationship tag. Em nosso exemplo, definimos uma relaçãoone-to-many entre os objetosDepartmenteEmployee, com base na expressão:

Employee.departmentId = this.id

O atributoreverseRelationshipName pode ser usado para tornar o relacionamento bidirecional sem defini-lo duas vezes.

O atributorelatedIsDependent nos permite cascatear as operações.

A seguir, vamos criar o arquivoEmployee.xml de maneira semelhante:


    com.example.reladomo
    Employee
    employees

    
    
    

3.2. ReladomoClassList.xml Arquivo

Reladomo precisa ser informado sobre os objetos que deve gerar.

Na seçãoMaven, definimos o arquivoReladomoClassList.xml como uma fonte para as tarefas de geração, então é hora de criar o arquivo:


    
    

Este é um arquivo simples que contém uma lista de entidades para as quais as classes serão geradas com base na configuração XML.

4. Classes Geradas

Agora temos todos os elementos de que precisamos para iniciar a geração do código, construindo o aplicativoMaven usando o comandomvn clean install.

As classes concretas serão geradas na pastasrc/main/java no pacote especificado:

image

Essas são classes simples nas quais podemos adicionar nosso código personalizado. Por exemplo, a classeDepartment contém apenas um construtor que não deve ser removido:

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

Se quisermos adicionar um construtor personalizado a essa classe, ele também precisará chamar o construtor pai:

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

Essas classes são baseadas nas classes abstratas e utilitárias na pastagenerated-sources/reladomo:

image

Os principais tipos de classes nesta pasta são:

  • ClassesDepartmentAbstracteEmployeeAbstract - que contém métodos para trabalhar com as entidades definidas

  • DepartmentListAbstracteEmployeeListAbstract - que contém métodos para trabalhar com listas de departamentos e funcionários

  • DepartmentFinder eEmployeeFinder - fornecem métodos para consultar entidades

  • outras classes de utilidade

Ao gerar essas classes, uma grande parte do código necessário para realizar operações CRUD em nossas entidades já foi criada para nós.

5. Aplicativo Reladomo

Para executar operações no banco de dados, precisamos de uma classe de gerenciador de conexões que nos permita obter conexões com o banco de dados.

5.1. Gerenciador de conexões

Ao trabalhar com um único banco de dados, podemos implementar a 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();
    }
    //...
}

Nossa classeReladomoConnectionManager implementa o padrão singleton e é baseada emXAConnectionManager que é uma classe de utilitário para um gerenciador de conexão transacional.

Vamos dar uma olhada mais de perto no métodocreateConnectionManager():

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

Neste método, definimos as propriedades necessárias para criar uma conexão com um banco de dados na memóriaH2.

Além disso, precisamos implementar vários métodos da 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;
}

Finalmente, vamos adicionar um método personalizado para executar os scripts DDL gerados que criam nossas tabelas de banco de dados:

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

Obviamente, isso não é necessário para um aplicativo de produção, onde suas tabelas não seriam recriadas para cada execução.

5.2. InicializandoReladomo

O processo de inicializaçãoReladomo usa um arquivo de configuração que especifica a classe do gerenciador de conexões e os tipos de objeto usados. Vamos definir um arquivoReladomoRuntimeConfig.xml:


    
    
    
    

Em seguida, podemos criar uma classe principal onde primeiro chamamos o métodocreateTables() e, em seguida,use 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. Executando Operações CRUD

Vamos agora usar as classes geradas porReladomo para realizar algumas operações em nossas entidades.

Primeiro, vamos criar dois objetosDepartment eEmployee e, em seguida, salvar ambos usando o métodocascadeInsert():

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

Cada objeto também pode ser salvo separadamente chamando o métodoinsert(). Em nosso exemplo, é possível usarcascadeInsert() porque adicionamos o atributorelatedIsDependent=true à nossa definição de relacionamento.

Para consultar objetos, podemos usar as classesFinder geradas:

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

Os objetos obtidos dessa maneira são objetos "ativos", o que significa que qualquer alteração neles usando setters é imediatamente refletida no banco de dados:

empFound.setName("Steven");

Para evitar esse comportamento, podemos obter objetos desanexados:

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

Para remover objetos, podemos usar o métododelete():

empFound.delete();

5.4. Gerenciamento de Transações

Se queremos que um conjunto de operações seja executado ou não como uma unidade, podemos envolvê-los em uma transação:

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

6. Suporte de testeReladomo

Nas seções acima, escrevemos nossos exemplos em uma classe principal Java.

Se queremos escrever testes para o nosso aplicativo, uma maneira de fazer isso é escrever o mesmo código em uma classe de teste simplesmente.

No entanto,for better test support, Reladomo also provides the MithraTestResource class. Isso nos permite usar uma configuração diferente e um banco de dados na memória apenas para os testes.

Primeiro, precisamos adicionar a dependênciareladomo-test-util adicional, junto com a dependênciajunit:


    com.goldmansachs.reladomo
    reladomo-test-util
    16.5.1


    junit
    junit
    4.12

A seguir, temos que criar um arquivoReladomoTestConfig.xml que usa a classeConnectionManagerForTests:


    
        
        
        
    
 

Este gerenciador de conexões configura um banco de dadosH2 em memória usado apenas para testes.

A convenient feature of the MithraTestResource class is that we can provide text files with test data no seguinte formato:

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

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

Vamos criar uma classe de testeJUnit e configurar nossa instânciaMithraTestResource em um método@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();
    }
}

Em seguida, podemos escrever um método@Test simples que verifica se nossos dados de teste foram carregados:

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

Após a execução dos testes, o banco de dados de teste precisa ser limpo:

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

7. Conclusão

Neste artigo, examinamos os principais recursos do framework ORMReladomo, bem como a configuração e exemplos de uso comum.

O código-fonte dos exemplos pode ser encontradoover on GitHub.