1. Overview
1.概述
Reladomo (formerly known as Mithra) is an object-relational mapping (ORM) framework for Java, developed at Goldman Sachs, currently released as an open-source project. The framework provides the features commonly needed from an ORM as well as some additional ones.
Reladomo(原名Mithra)是Java的对象关系映射(ORM)框架,由Goldman Sachs开发,目前作为一个开源项目发布。该框架提供了ORM通常需要的功能,以及一些额外的功能。
Let’s see some of the key features of Reladomo:
让我们看看Reladomo的一些主要特点。
- it can generate Java classes as well as DDL scripts
- it is driven by metadata written in XML files
- the generated code is extensible
- the query language is object-oriented and strongly typed
- the framework provides support for sharding (same schema, different datasets)
- the support for testing is also included
- it provides useful features like performant caching and transactions
In the following sections, we’ll see the setup and some basic examples of use.
在下面的章节中,我们将看到设置和一些基本的使用例子。
2. Maven Setup
2.Maven设置
To start using the ORM, we need to add the reladomo dependency to our pom.xml file:
为了开始使用ORM,我们需要将reladomo依赖添加到我们的pom.xml文件中。
<dependency>
<groupId>com.goldmansachs.reladomo</groupId>
<artifactId>reladomo</artifactId>
<version>16.5.1</version>
</dependency>
We will use an H2 database for our examples, so let’s also add the h2 dependency:
我们将在我们的例子中使用H2数据库,所以让我们也添加h2依赖。
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
</dependency>
In addition to this, we need to setup plugins that will generate classes and SQL files, and load them during execution.
除此之外,我们还需要设置插件,生成类和SQL文件,并在执行过程中加载它们。
For file generation, we can use tasks that are executed using the maven-antrun-plugin. First, let’s see how we can define the task for generating Java classes:
对于文件生成,我们可以使用使用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>
The gen-reladomo task uses the provided MithraGenerator to create Java files based on the configuration in ReladomoClassList.xml file. We will take a closer look at what this file contains in a later section.
gen-reladomo任务使用提供的MithraGenerator,根据ReladomoClassList.xml文件中的配置创建Java文件。我们将在后面的章节中仔细研究该文件包含的内容。
The tasks also have two properties that define the location of the generated files:
这些任务也有两个属性,定义了生成文件的位置。
- generatedDir – contains the classes which shouldn’t be modified or versioned
- nonGeneratedDir – the generated concrete object classes which can be further customized and versioned
The database tables corresponding to the Java objects can either be created manually or automatically by using the DDL scripts generated by a second Ant task:
与Java对象相对应的数据库表可以手动创建,也可以通过使用第二个Ant任务生成的DDL脚本自动创建。
<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"/>
This task uses the MithraDbDefinitionGenerator based on the same ReladomoClassList.xml file mentioned before. The SQL scripts will be placed in the generated-db/sql directory.
这个任务使用MithraDbDefinitionGenerator,基于之前提到的同一个ReladomoClassList.xml文件。SQL脚本将被放置在generated-db/sql目录中。
To complete the definition for this plugin, we also have to add two dependencies used for the creation:
为了完成这个插件的定义,我们还必须添加两个用于创建的依赖项。
<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>
Finally, using the build-helper-maven-plugin, we can add the generated files to the classpath:
最后,使用build-helper-maven-plugin,我们可以将生成的文件加入classpath。
<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>
Adding the DDL scripts is optional. In our example, we will use an in-memory database, so we want to execute the scripts in order to create the tables.
添加DDL脚本是可选的。在我们的例子中,我们将使用一个内存数据库,所以我们要执行脚本以创建表。
3. XML Configuration
3.XML配置
The metadata for the Reladomo framework can be defined in several XML files.
Reladomo框架的元数据可以在几个XML文件中定义。
3.1. Object XML Files
3.1. 对象XML文件
Each entity we want to create needs to be defined in its XML file.
我们想要创建的每个实体都需要在其XML文件中进行定义。
Let’s create a simple example with two entities: departments and employees. Here is a visual representation of our domain model:
让我们创建一个有两个实体的简单例子:部门和雇员。下面是我们的领域模型的一个可视化表示。
Let’s define the first Department.xml file:
我们来定义第一个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>
We can see above the entity is defined inside a root element called MithraObject. Then, we have specified the package, class, and name of the corresponding database table.
我们可以看到上面实体被定义在一个叫做MithraObject的根元素里面。然后,我们指定了相应数据库表的包、类和名称。
Each property of the type is defined using an Attribute element, for which we can state the name, Java type, and column name.
类型的每个属性都是用Attribute元素定义的,我们可以为其说明名称、Java类型和列名。
We can describe the relationships between objects using the Relationship tag. In our example, we have defined a one-to-many relationship between Department and Employee objects, based on the expression:
我们可以使用Relationship标签来描述对象之间的关系。在我们的例子中,我们在Department和Employee对象之间定义了一个一对多关系,基于表达式。
Employee.departmentId = this.id
The reverseRelationshipName attribute can be used to make the relationship bi-directional without defining it twice.
reverseRelationshipName属性可以用来使关系成为双向的,而不需要定义两次。。
The relatedIsDependent attribute allows us to cascade operations.
relatedIsDependent属性允许我们进行级联操作。
Next, let’s create the Employee.xml file in a similar fashion:
接下来,让我们以类似方式创建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 File
3.2. ReladomoClassList.xml 文件
Reladomo needs to be told about the objects it should generate.
Reladomo需要被告知它应该生成的对象。
In the Maven section, we defined the ReladomoClassList.xml file as a source for the generation tasks, so it’s time to create the file:
在Maven部分,我们定义了ReladomoClassList.xml文件作为生成任务的源,所以现在是时候创建该文件。
<Mithra>
<MithraObjectResource name="Department"/>
<MithraObjectResource name="Employee"/>
</Mithra>
This is a simple file containing a list of entities for which classes will be generated based on the XML configuration.
这是一个简单的文件,包含一个实体列表,将根据XML配置为其生成类。
4. Generated Classes
4.生成的类
Now we have all the elements we need to start the code generation by building the Maven application using the command mvn clean install.
现在我们有了所有需要的元素,可以使用mvn clean install命令构建Maven应用程序,开始生成代码。
The concrete classes will be generated in the src/main/java folder in the specified package:
具体的类将被生成在 src/main/java文件夹中的指定包。
These are simple classes where we can add our custom code. For example, the Department class only contains a constructor which should not be removed:
这些是简单的类,我们可以在其中添加我们的自定义代码。例如,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.
}
}
If we want to add a custom constructor to this class, it needs to call the parent constructor as well:
如果我们想给这个类添加一个自定义的构造函数,它也需要调用父级构造函数。
public Department(long id, String name){
super();
this.setId(id);
this.setName(name);
}
These classes are based on the abstract and utility classes in the generated-sources/reladomo folder:
这些类是基于generated-sources/reladomo文件夹中的抽象和实用类。
The main types of classes in this folder are:
这个文件夹中的主要班级类型是。
- DepartmentAbstract and EmployeeAbstract classes – which contains methods for working with the entities defined
- DepartmentListAbstract and EmployeeListAbstract – that contains methods for working with lists of departments and employees
- DepartmentFinder and EmployeeFinder – these provide methods for querying entities
- other utility classes
By generating these classes, a large part of the code necessary to perform CRUD operations on our entities is already created for us.
通过生成这些类,对我们的实体进行CRUD操作所需的很大一部分代码已经为我们创建。
5. Reladomo Application
5.Reladomo应用
To perform operations on the database, we need a connection manager class that allows us to obtain database connections.
为了对数据库进行操作,我们需要一个连接管理器类,使我们能够获得数据库连接。
5.1. Connection Manager
5.1.连接管理器
When working with a single database, we can implement the SourcelessConnectionManager interface:
在处理单一数据库时,我们可以实现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();
}
//...
}
Our ReladomoConnectionManager class implements the singleton pattern and is based on an XAConnectionManager which is a utility class for a transactional connection manager.
我们的ReladomoConnectionManager类实现了singleton模式,并基于XAConnectionManager,这是一个交易型连接管理器的实用类。
Let’s take a closer look at the createConnectionManager() method:
让我们仔细看看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;
}
In this method, we’ve set the properties necessary to create a connection to an H2 in-memory database.
在这个方法中,我们已经设置了必要的属性,以创建一个与H2内存数据库的连接。
Also, we need to implement several methods from the SourcelessConnectionManager interface:
此外,我们还需要实现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;
}
Finally, let’s add a custom method to execute the generated DDL scripts that create our database tables:
最后,让我们添加一个自定义方法来执行生成的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();
}
});
}
}
This is, of course, not necessary for a production application, where your tables would not be recreated for every execution.
当然,这对生产应用来说是不必要的,因为你的表不会在每次执行时都被重新创建。
5.2. Initializing Reladomo
5.2.初始化Reladomo
The Reladomo initialization process uses a configuration file that specifies the connection manager class and the object types used. Let’s define a ReladomoRuntimeConfig.xml file:
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>
Next, we can create a main class where we first call the createTables() method, then use the MithraManager class to load the configuration and initialize Reladomo:
接下来,我们可以创建一个主类,首先调用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. Performing CRUD Operations
5.3.执行CRUD操作
Let’s now use the Reladomo-generated classes to perform a few operations on our entities.
现在让我们使用Reladomo生成的类来对我们的实体执行一些操作。
First, let’s create two Department and Employee objects, then save both using the cascadeInsert() method:
首先,让我们创建两个Department和Employee对象,然后使用cascadeInsert()方法保存两个对象。
Department department = new Department(1, "IT");
Employee employee = new Employee(1, "John");
department.getEmployees().add(employee);
department.cascadeInsert();
Each object can also be saved separately by calling the insert() method. In our example, it’s possible to use cascadeInsert() because we’ve added the relatedIsDependent=true attribute to our relationship definition.
每个对象也可以通过调用 insert()方法单独保存。在我们的例子中,可以使用cascadeInsert(),因为我们在关系定义中添加了relatedIsDependent=true属性。
To query objects, we can use the generated Finder classes:
为了查询对象,我们可以使用生成的Finder类:。
Department depFound = DepartmentFinder
.findByPrimaryKey(1);
Employee empFound = EmployeeFinder
.findOne(EmployeeFinder.name().eq("John"));
The objects obtained in this manner are “live” objects, meaning any change to them using setters is immediately reflected in the database:
以这种方式获得的对象是 “活 “的对象,这意味着使用设置器对它们的任何改变都会立即反映在数据库中。
empFound.setName("Steven");
To avoid this behavior, we can obtain detached objects:
为了避免这种行为,我们可以获得分离的对象。
Department depDetached = DepartmentFinder
.findByPrimaryKey(1).getDetachedCopy();
To remove objects, we can use the delete() method:
要删除对象,我们可以使用delete()方法。
empFound.delete();
5.4. Transaction Management
5.4.事务管理
If we want a set of operations to be executed or not as one unit, we can wrap them in a 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. Reladomo Test Support
6.Reladomo测试支持
In the sections above, we wrote our examples in a Java main class.
在上面的章节中,我们在一个Java主类中写了我们的例子。
If we want to write tests for our application, one way to do this is to write the same code in a test class simply.
如果我们想为我们的应用程序编写测试,一种方法是简单地在测试类中编写相同的代码。
However, for better test support, Reladomo also provides the MithraTestResource class. This allows us to use a different configuration and in-memory database just for the tests.
然而,为了更好地支持测试,Reladomo还提供了MithraTestResource类。这使得我们可以只为测试使用不同的配置和内存数据库。
First, we need to add the additional reladomo-test-util dependency, along with the junit dependency:
首先,我们需要添加额外的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>
Next, we have to create a ReladomoTestConfig.xml file that uses the ConnectionManagerForTests class:
接下来,我们必须创建一个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>
This connection manager configures an in-memory H2 database used only for tests.
这个连接管理器配置了一个仅用于测试的内存H2数据库。
A convenient feature of the MithraTestResource class is that we can provide text files with test data in the following format:
MithraTestResource类的一个方便特点是,我们可以提供以下格式的测试数据文本文件。
class com.baeldung.reladomo.Department
id, name
1, "Marketing"
class com.baeldung.reladomo.Employee
id, name
1, "Paul"
Let’s create a JUnit test class and setup our MithraTestResource instance in a @Before method:
让我们创建一个JUnit测试类并在@Before方法中设置我们的MithraTestResource实例。
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();
}
}
Then we can write a simple @Test method that verifies our test data was loaded:
然后我们可以写一个简单的@Test方法,验证我们的测试数据是否被加载。
@Test
public void whenGetTestData_thenOk() {
Employee employee = EmployeeFinder.findByPrimaryKey(1);
assertEquals(employee.getName(), "Paul");
}
After the tests have run, the test database needs to be cleared:
在测试运行后,测试数据库需要被清空。
@After
public void tearDown() throws Exception {
this.mithraTestResource.tearDown();
}
7. Conclusion
7.结论
In this article, we went through the main features of Reladomo ORM framework, as well as setup and examples of common use.
在这篇文章中,我们经历了ReladomoORM框架的主要功能,以及设置和常用的例子。
The source code for the examples can be found over on GitHub.
这些例子的源代码可以在GitHub上找到。