«1. Обзор
Reladomo (ранее известный как Mithra) — это инфраструктура объектно-реляционного отображения (ORM) для Java, разработанная в Goldman Sachs, в настоящее время выпущенная как проект с открытым исходным кодом. Фреймворк предоставляет функции, обычно необходимые для ORM, а также некоторые дополнительные.
Давайте рассмотрим некоторые ключевые особенности Reladomo:
-
он может генерировать классы Java, а также сценарии DDL он управляется метаданными, записанными в XML-файлах сгенерированный код является расширяемым язык запросов является объектно-ориентированным и строго типизированным фреймворк обеспечивает поддержку сегментирования (одна и та же схема, разные наборы данных), также включена поддержка тестирования, он предоставляет полезные функции, такие как эффективное кэширование и транзакции
В следующих разделах мы увидим настройку и некоторые основные примеры использования.
2. Настройка Maven
Чтобы начать использовать ORM, нам нужно добавить зависимость reladomo в наш файл pom.xml:
<dependency>
<groupId>com.goldmansachs.reladomo</groupId>
<artifactId>reladomo</artifactId>
<version>16.5.1</version>
</dependency>
Мы будем использовать базу данных H2 для наших примеров, так что давайте также добавим зависимость h2:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
</dependency>
В дополнение к этому нам нужно настроить плагины, которые будут генерировать классы и файлы SQL и загружать их во время выполнения.
Для генерации файлов мы можем использовать задачи, которые выполняются с помощью плагина maven-antrun-plugin. Во-первых, давайте посмотрим, как мы можем определить задачу для создания классов Java:
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>generateMithra</id>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<property name="plugin_classpath"
refid="maven.plugin.classpath"/>
<taskdef name="gen-reladomo"
classpath="plugin_classpath"
classname="com.gs.fw.common.mithra.generator.MithraGenerator"/>
<gen-reladomo
xml="${project.basedir}/src/main/resources/reladomo/ReladomoClassList.xml"
generateGscListMethod="true"
generatedDir="${project.build.directory}/generated-sources/reladomo"
nonGeneratedDir="${project.basedir}/src/main/java"/>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
Задача gen-reladomo использует предоставленный MithraGenerator для создания файлов Java на основе конфигурации в файле ReladomoClassList.xml. Мы более подробно рассмотрим, что содержит этот файл, в следующем разделе.
Задачи также имеют два свойства, которые определяют расположение сгенерированных файлов:
-
generateDir — содержит классы, которые не должны изменяться или версионироваться nonGeneratedDir — сгенерированные конкретные классы объектов, которые можно дополнительно настраивать и версионные
Таблицы базы данных, соответствующие объектам Java, могут быть созданы вручную или автоматически с помощью сценариев DDL, созданных второй задачей Ant:
<taskdef
name="gen-ddl"
classname = "com.gs.fw.common.mithra.generator.dbgenerator.MithraDbDefinitionGenerator"
loaderRef="reladomoGenerator">
<classpath refid="maven.plugin.classpath"/>
</taskdef>
<gen-ddl
xml="${project.basedir}/src/main/resources/reladomo/ReladomoClassList.xml"
generatedDir="${project.build.directory}/generated-db/sql"
databaseType="postgres"/>
Эта задача использует MithraDbDefinitionGenerator на основе того же ReladomoClassList.xml файл, упомянутый ранее. Сценарии SQL будут размещены в каталоге generate-db/sql.
Чтобы завершить определение этого плагина, мы также должны добавить две зависимости, используемые для создания:
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
//...
</executions>
<dependencies>
<dependency>
<groupId>com.goldmansachs.reladomo</groupId>
<artifactId>reladomogen</artifactId>
<version>16.5.1</version>
</dependency>
<dependency>
<groupId>com.goldmansachs.reladomo</groupId>
<artifactId>reladomo-gen-util</artifactId>
<version>16.5.1</version>
</dependency>
</dependencies>
</plugin>
Наконец, используя build-helper-maven-plugin, мы можем добавить сгенерированные файлы в путь к классам. :
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/reladomo</source>
</sources>
</configuration>
</execution>
<execution>
<id>add-resource</id>
<phase>generate-resources</phase>
<goals>
<goal>add-resource</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>${project.build.directory}/generated-db/</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
Добавление сценариев DDL необязательно. В нашем примере мы будем использовать базу данных в памяти, поэтому мы хотим выполнить сценарии для создания таблиц.
3. Конфигурация XML
Метаданные для платформы Reladomo могут быть определены в нескольких файлах XML.
3.1. XML-файлы объектов
Каждая сущность, которую мы хотим создать, должна быть определена в ее XML-файле.
Давайте создадим простой пример с двумя сущностями: отделами и сотрудниками. Вот визуальное представление нашей модели предметной области:
Давайте определим первый файл Department.xml:
<MithraObject objectType="transactional">
<PackageName>com.baeldung.reladomo</PackageName>
<ClassName>Department</ClassName>
<DefaultTable>departments</DefaultTable>
<Attribute name="id" javaType="long"
columnName="department_id" primaryKey="true"/>
<Attribute name="name" javaType="String"
columnName="name" maxLength="50" truncate="true"/>
<Relationship name="employees" relatedObject="Employee"
cardinality="one-to-many"
reverseRelationshipName="department"
relatedIsDependent="true">
Employee.departmentId = this.id
</Relationship>
</MithraObject>
Как видно выше, сущность определена внутри корневого элемента с именем MithraObject. Затем мы указали пакет, класс и имя соответствующей таблицы базы данных.
Каждое свойство типа определяется с помощью элемента Attribute, для которого мы можем указать имя, тип Java и имя столбца.
Мы можем описать отношения между объектами, используя тег Relationship. В нашем примере мы определили отношение «один ко многим» между объектами «Отдел» и «Сотрудник» на основе выражения:
Employee.departmentId = this.id
Атрибут reverseRelationshipName можно использовать для того, чтобы сделать отношение двунаправленным, не определяя его дважды.
Атрибут relatedIsDependent позволяет выполнять каскадные операции.
Далее аналогичным образом создадим файл Employee.xml:
<MithraObject objectType="transactional">
<PackageName>com.baeldung.reladomo</PackageName>
<ClassName>Employee</ClassName>
<DefaultTable>employees</DefaultTable>
<Attribute name="id" javaType="long"
columnName="employee_id" primaryKey="true"/>
<Attribute name="name" javaType="String"
columnName="name" maxLength="50" truncate="true"/>
<Attribute name="departmentId" javaType="long"
columnName="department_id"/>
</MithraObject>
3.2. Файл ReladomoClassList.xml
Reladomo нужно сообщить об объектах, которые он должен генерировать.
В разделе Maven мы определили файл ReladomoClassList.xml как источник для задач генерации, поэтому пришло время создать файл:
<Mithra>
<MithraObjectResource name="Department"/>
<MithraObjectResource name="Employee"/>
</Mithra>
«
«Это простой файл, содержащий список сущностей, для которых будут созданы классы на основе конфигурации XML.
4. Сгенерированные классы
Теперь у нас есть все элементы, необходимые для начала генерации кода путем сборки приложения Maven с помощью команды mvn clean install.
Конкретные классы будут сгенерированы в папке src/main/java в указанном пакете:
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.
}
}
Это простые классы, в которые мы можем добавить наш собственный код. Например, класс Department содержит только конструктор, который не следует удалять:
public Department(long id, String name){
super();
this.setId(id);
this.setName(name);
}
Если мы хотим добавить к этому классу пользовательский конструктор, он также должен вызвать родительский конструктор:
Эти классы основаны на абстрактных и служебных классах в папке сгенерированных источников/реладомо:
-
Основные типы классов в этой папке:
классы DepartmentAbstract и EmployeeAbstract, содержащие методы для работы с сущностями. определены DepartmentListAbstract и EmployeeListAbstract, которые содержат методы для работы со списками отделов и сотрудников. 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 реализует шаблон singleton и основан на 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<Path> 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:
<MithraRuntime>
<ConnectionManager
className="com.baeldung.reladomo.ReladomoConnectionManager ">
<MithraObjectConfiguration
className="com.baeldung.reladomo.Department" cacheType="partial"/>
<MithraObjectConfiguration
className="com.baeldung.reladomo.Employee " cacheType="partial"/>
</ConnectionManager>
</MithraRuntime>
Далее мы можем создать основной класс, в котором сначала вызовем метод createTables(), а затем воспользуемся классом MithraManager для загрузки конфигурации и инициализации 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.
Если мы хотим написать тесты для нашего приложения, один из способов сделать это — просто написать тот же код в тестовом классе.
«Однако для лучшей поддержки тестирования Reladomo также предоставляет класс MithraTestResource. Это позволяет нам использовать другую конфигурацию и базу данных в памяти только для тестов.
Во-первых, нам нужно добавить дополнительную зависимость reladomo-test-util вместе с зависимостью junit:
<dependency>
<groupId>com.goldmansachs.reladomo</groupId>
<artifactId>reladomo-test-util</artifactId>
<version>16.5.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
Затем мы должны создать файл ReladomoTestConfig.xml, который использует класс ConnectionManagerForTests:
<MithraRuntime>
<ConnectionManager
className="com.gs.fw.common.mithra.test.ConnectionManagerForTests">
<Property name="resourceName" value="testDb"/>
<MithraObjectConfiguration
className="com.baeldung.reladomo.Department" cacheType="partial"/>
<MithraObjectConfiguration
className="com.baeldung.reladomo.Employee " cacheType="partial"/>
</ConnectionManager>
</MithraRuntime>
Этот диспетчер соединений настраивает базу данных H2 в памяти, используемую только для тестов.
Удобная функция класса MithraTestResource заключается в том, что мы можем предоставить текстовые файлы с тестовыми данными в следующем формате:
class com.baeldung.reladomo.Department
id, name
1, "Marketing"
class com.baeldung.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, а также настройку и примеры частого использования.
Исходный код примеров можно найти на GitHub.