Введение в 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
add-resource
generate-resources
add-resource
${project.build.directory}/generated-db/
Добавление сценариев DDL не является обязательным. В нашем примере мы будем использовать базу данных в памяти, поэтому мы хотим выполнить сценарии для создания таблиц.
3. Конфигурация XML
Метаданные для платформыReladomo могут быть определены в нескольких файлах XML.
3.1. XML-файлы объектов
Каждая сущность, которую мы хотим создать, должна быть определена в ее XML-файле.
Давайте создадим простой пример с двумя объектами: отделами и сотрудниками. Вот визуальное представление нашей модели предметной области:
Давайте определим первый файл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 указанного пакета:
Это простые классы, в которые мы можем добавить наш собственный код. Например, класс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:
Основные типы классов в этой папке:
-
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.