Введение в Reladomo

Введение в Reladomo

1. обзор

Reladomo (formerly known as Mithra) is an object-relational mapping (ORM) framework for Java, разработанный вGoldman Sachs, в настоящее время выпущен как проект с открытым исходным кодом. Каркас предоставляет функции, которые обычно необходимы в ORM, а также некоторые дополнительные.

Давайте посмотрим на некоторые ключевые особенностиReladomo:

  • он может генерировать классы Java, а также сценарии DDL

  • он управляется метаданными, записанными в файлах XML

  • сгенерированный код является расширяемым

  • язык запросов является объектно-ориентированным и строго типизированным

  • Фреймворк поддерживает Sharding (одна и та же схема, разные наборы данных).

  • поддержка тестирования также включена

  • он предоставляет полезные функции, такие как эффективное кэширование и транзакции

В следующих разделах мы увидим настройку и несколько основных примеров использования.

2. Maven Настройка

Чтобы начать использовать ORM, нам нужно добавить зависимостьreladomo в наш файлpom.xml:


    com.goldmansachs.reladomo
    reladomo
    16.5.1

Мы будем использовать базу данныхH2 для наших примеров, поэтому давайте также добавим зависимостьh2:


    com.h2database
    h2
    1.4.196

In addition to this, we need to setup plugins that will generate classes and SQL files, и загрузить их во время выполнения.

Для генерации файлов мы можем использовать задачи, которые выполняются с использованиемmaven-antrun-plugin. Во-первых, давайте посмотрим, как мы можем определить задачу для генерации классов 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. Мы подробнее рассмотрим, что содержит этот файл, в следующем разделе.

Задачи также имеют два свойства, которые определяют местоположение сгенерированных файлов:

  • generatedDir - содержит классы, которые нельзя изменять или версировать

  • nonGeneratedDir - сгенерированные классы конкретных объектов, которые могут быть дополнительно настроены и версированы

Таблицы базы данных, соответствующие объектам Java, могут быть созданы вручную или автоматически с помощью сценариев DDL, сгенерированных второй задачейAnt:


    

Эта задача используетMithraDbDefinitionGenerator на основе того же файлаReladomoClassList.xml, упомянутого ранее. Скрипты SQL будут помещены в каталогgenerated-db/sql.

Чтобы завершить определение этого плагина, мы также должны добавить две зависимости, используемые для создания:


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

Наконец, используяbuild-helper-maven-plugin, мы можем добавить сгенерированные файлы в путь к классам:


    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/
                    
                
            
        
    

Добавление сценариев DDL не является обязательным. В нашем примере мы будем использовать базу данных в памяти, поэтому мы хотим выполнить сценарии для создания таблиц.

3. Конфигурация XML

Метаданные для платформыReladomo могут быть определены в нескольких файлах XML.

3.1. XML-файлы объектов

Каждая сущность, которую мы хотим создать, должна быть определена в ее XML-файле.

Давайте создадим простой пример с двумя объектами: отделами и сотрудниками. Вот визуальное представление нашей модели предметной области:

image

Давайте определим первый файлDepartment.xml:


    com.example.reladomo
    Department
    departments

    
    
    
         Employee.departmentId = this.id
    

Мы видим вышеthe entity is defined inside a root element called MithraObject. Затем мы указали пакет, класс и имя соответствующей таблицы базы данных.

Каждое свойство типа определяется с помощью элементаAttribute, для которого мы можем указать имя, тип Java и имя столбца.

We can describe the relationships between objects using the Relationship tag. В нашем примере мы определили связьone-to-many между объектамиDepartment иEmployee на основе выражения:

Employee.departmentId = this.id

АтрибутreverseRelationshipName можно использовать, чтобы сделать связь двунаправленной, не определяя ее дважды.

АтрибутrelatedIsDependent позволяет нам каскадировать операции.

Затем давайте создадим файлEmployee.xml аналогичным образом:


    com.example.reladomo
    Employee
    employees

    
    
    

3.2. ReladomoClassList.xml файл

Reladomo нужно сообщить об объектах, которые он должен генерировать.

В разделеMaven мы определили файлReladomoClassList.xml как источник для задач генерации, так что пора создать файл:


    
    

Это простой файл, содержащий список сущностей, для которых будут генерироваться классы на основе конфигурации XML.

4. Сгенерированные классы

Теперь у нас есть все элементы, необходимые для запуска генерации кода путем создания приложенияMaven с помощью командыmvn clean install.

Конкретные классы будут сгенерированы в папкеsrc/main/java указанного пакета:

image

Это простые классы, в которые мы можем добавить наш собственный код. Например, классDepartment содержит только конструктор, который не следует удалять:

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

Если мы хотим добавить пользовательский конструктор в этот класс, он должен также вызвать родительский конструктор:

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

Эти классы основаны на абстрактных и служебных классах в папкеgenerated-sources/reladomo:

image

Основные типы классов в этой папке:

  • DepartmentAbstract иEmployeeAbstract классы - которые содержат методы для работы с определенными объектами

  • DepartmentListAbstract иEmployeeListAbstract - содержит методы для работы со списками отделов и сотрудников

  • DepartmentFinder иEmployeeFinder - они предоставляют методы для запроса сущностей

  • другие служебные классы

Создавая эти классы, мы уже создали большую часть кода, необходимого для выполнения операций CRUD над нашими сущностями.

5. Приложение Reladomo

Для выполнения операций с базой данных нам нужен класс диспетчера соединений, который позволяет нам получать соединения с базой данных.

5.1. Диспетчер подключений

При работе с одной базой данных мы можем реализовать интерфейсSourcelessConnectionManager:

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

Наш классReladomoConnectionManager реализует одноэлементный шаблон и основан наXAConnectionManager, который является служебным классом для диспетчера транзакционных соединений.

Давайте подробнее рассмотрим методcreateConnectionManager():

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

В этом методе мы установили свойства, необходимые для создания подключения к базе данныхH2 в памяти.

Также нам нужно реализовать несколько методов из интерфейсаSourcelessConnectionManager:

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

Наконец, давайте добавим собственный метод для выполнения сгенерированных сценариев DDL, которые создают таблицы нашей базы данных:

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

Это, конечно, не обязательно для производственного приложения, где ваши таблицы не будут воссозданы для каждого выполнения.

5.2. ИнициализацияReladomo

В процессе инициализацииReladomo используется файл конфигурации, в котором указывается класс диспетчера соединений и используемые типы объектов. Давайте определим файлReladomoRuntimeConfig.xml:


    
    
    
    

Затем мы можем создать основной класс, в котором сначала вызываем методcreateTables(), а затем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. Выполнение операций CRUD

Давайте теперь воспользуемся сгенерированнымиReladomo классами для выполнения нескольких операций с нашими сущностями.

Сначала создадим два объектаDepartment иEmployee, а затем сохраним их с помощью методаcascadeInsert():

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

Каждый объект также можно сохранить отдельно, вызвав методinsert(). В нашем примере можно использоватьcascadeInsert(), потому что мы добавили атрибутrelatedIsDependent=true в определение отношения.

Для запроса объектов мы можем использовать сгенерированные классыFinder:

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

Полученные таким образом объекты являются «живыми» объектами, что означает, что любое изменение в них с использованием сеттеров немедленно отражается в базе данных:

empFound.setName("Steven");

Чтобы избежать такого поведения, мы можем получить отдельные объекты:

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

Для удаления объектов мы можем использовать методdelete():

empFound.delete();

5.4. Управление транзакциями

Если мы хотим, чтобы набор операций выполнялся или не был одним целым, мы можем заключить их в транзакцию:

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 Тестовая поддержка

В разделах выше мы написали наши примеры в основном классе Java.

Если мы хотим написать тесты для нашего приложения, один из способов сделать это - просто написать один и тот же код в тестовом классе.

Однакоfor better test support, Reladomo also provides the MithraTestResource class. Это позволяет нам использовать другую конфигурацию и базу данных в памяти только для тестов.

Во-первых, нам нужно добавить дополнительную зависимостьreladomo-test-util вместе с зависимостьюjunit:


    com.goldmansachs.reladomo
    reladomo-test-util
    16.5.1


    junit
    junit
    4.12

Затем нам нужно создать файлReladomoTestConfig.xml, который использует классConnectionManagerForTests:


    
        
        
        
    
 

Этот диспетчер соединений настраивает базу данныхH2 в памяти, используемую только для тестов.

A convenient feature of the MithraTestResource class is that we can provide text files with test data в следующем формате:

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

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

Давайте создадим тестовый классJUnit и настроим наш экземплярMithraTestResource в методе@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();
    }
}

Затем мы можем написать простой метод@Test, который проверяет, были ли загружены наши тестовые данные:

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

После выполнения тестов тестовую базу данных необходимо очистить:

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

7. Заключение

В этой статье мы рассмотрели основные возможности ORM-фреймворкаReladomo, а также познакомились с настройкой и примерами общего использования.

Исходный код примеров можно найтиover on GitHub.