1. 概述

Reladomo(前称 Mithra) 是由高盛开发并开源的 Java 对象关系映射(ORM)框架。它不仅提供了 ORM 的常用功能,还包含一些独特的增强特性。

Reladomo 的核心优势包括:

代码生成能力:自动生成 Java 类和 DDL 脚本
元数据驱动:基于 XML 配置文件驱动
可扩展性:生成的代码支持自定义扩展
强类型查询:提供面向对象的强类型查询语言
分片支持:支持同构多数据集分片
内置测试工具:提供专门的测试支持
高性能缓存:内置高效缓存机制和事务管理

接下来我们将通过实际示例,逐步解析 Reladomo 的配置与使用。

2. Maven 配置

首先在 pom.xml 中添加核心依赖:

<dependency>
    <groupId>com.goldmansachs.reladomo</groupId>
    <artifactId>reladomo</artifactId>
    <version>18.1.0</version>
</dependency>

示例使用 H2 内存数据库,需额外添加:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.1.214</version>
</dependency>

⚠️ 关键配置:需配置代码生成插件。通过 maven-antrun-plugin 实现类生成:

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

该任务通过 MithraGenerator 基于配置文件生成代码,关键目录说明:

  • generatedDir:存放不可修改的自动生成类
  • nonGeneratedDir:存放可扩展的具体实体类

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"/>

添加插件依赖:

<plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <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 将生成文件加入 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>

3. XML 配置

3.1. 对象 XML 文件

每个实体需定义独立的 XML 文件。以部门(Department)和员工(Employee)实体为例:

tables

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:属性映射(主键、字段名、类型)
  • Relationship:关系定义
    • cardinality="one-to-many":一对多关系
    • reverseRelationshipName:双向关系简化配置
    • relatedIsDependent="true":启用级联操作

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 文件

需在 ReladomoClassList.xml 中声明所有实体:

<Mithra>
    <MithraObjectResource name="Department"/>
    <MithraObjectResource name="Employee"/>
</Mithra>

该文件作为代码生成任务的输入源,指定需生成类的实体列表。

4. 生成的类结构

执行 mvn clean install 后生成两类代码:

可扩展实体类(位于 src/main/java): classes

示例 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): gen classes

核心类类型:

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

连接池初始化(H2 内存数据库示例):

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

实现接口方法:

@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

创建运行时配置文件 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>

主类初始化流程:

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

            //执行业务操作
        }
        catch (IOException exc){
            exc.printStackTrace();
        }     
    }
}

5.3. CRUD 操作示例

级联插入(需在关系配置中设置 relatedIsDependent="true"):

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

查询操作

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

⚠️ 注意:查询返回的是"实时对象",修改会立即同步到数据库:

empFound.setName("Steven"); // 直接更新数据库

获取非实时副本:

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

删除操作

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. 测试支持

6.1. 测试依赖配置

添加测试专用依赖:

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

6.2. 测试配置文件

创建 ReladomoTestConfig.xml

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

6.3. 测试数据文件

创建测试数据文件 test-data.txt

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

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

6.4. JUnit 测试类

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
    public void whenGetTestData_thenOk() {
        Employee employee = EmployeeFinder.findByPrimaryKey(1);
        assertEquals(employee.getName(), "Paul");
    }

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

优势:自动管理测试数据库生命周期,隔离测试数据。

7. 总结

本文详细解析了 Reladomo 框架的核心特性与使用方法:

  • 代码生成:通过 XML 配置自动生成实体类、查询类和 DDL
  • 强类型查询:避免 SQL 注入,提升类型安全性
  • 缓存优化:内置高效缓存机制
  • 测试支持:提供专用测试工具简化单元测试

完整示例代码可在 GitHub 获取。对于需要高性能、强类型 ORM 的企业级应用,Reladomo 是一个值得考虑的选择。