使用asentinel-orm运行时定义列

Asentinel-orm是一个基于Spring JDBC构建的轻量级ORM工具,特别是JdbcTemplate。因此,它具有大多数基本ORM应具备的功能,如SQL生成、延迟加载等。

通过利用JdbcTemplate,它允许参与Spring管理的事务,并且可以轻松集成到任何已使用JdbcTemplate与数据库交互的项目中。

自2015年以来,asentinel-orm已成功用于多个应用,并根据业务需求不断改进。在2024年夏天,它正式成为一个开源项目,我们认为这将加速其发展并增加贡献者数量。

在本文中,构建了一个示例应用程序来概述几个ORM的关键特性:

  • 简单配置
  • 通过自定义注解直观领域实体建模
  • 轻松编写和安全执行纯SQL语句
  • 自动生成SQL语句
  • 动态模式(实体附加运行时属性,持久化,无需更改代码即可读取)

应用

设置

  • Java 21
  • Spring Boot 3.4.0
  • asentinel-orm 1.70.0
  • H2数据库

配置

为了与asentinel-orm进行交互并利用其功能,需要一个OrmOperations 的实例。

正如JavaDoc中所述,这 是执行ORM操作的核心接口,既不打算也不需要在客户端代码中具体实现。

示例应用程序包括创建此类型bean的配置代码。

Java

 

OrmOperations 有两个超接口:

  • SqlBuilderFactory – 创建SqlBuilder 实例,这些实例可以进一步用于创建SQL查询。SqlBuilder能够自动生成查询的一部分,例如选择列的部分。通过SqlBuilder类的方法,可以添加where子句、order by子句、其他条件和实际列。在本节的下一部分,将展示一个由SqlBuilder生成的查询示例。
  • Updater – 用于将实体保存到各自的数据库表中。根据实体是新创建的还是已存在的,它可以执行插入或更新。存在一个名为NewEntityDetector 的策略接口,用于确定实体是否为新实体。默认情况下,使用SimpleNewEntityDetector 

所有由 ORM 生成的查询都是通过一个 SqlQueryTemplate 实例执行的,这进一步需要一个 Spring JdbcOperations/JdbcTemplate 来工作。最终,所有查询都通过 JdbcTemplate 到达,执行时参与 Spring 事务,就像任何 JdbcTemplate 的直接执行

数据库特定的 SQL 构造和逻辑通过 JdbcFlavor 接口的实现提供,并进一步注入到上述大多数 bean 中。在本文中,由于使用了 H2 数据库,因此配置了 H2JdbcFlavor 实现。

作为示例应用程序的一部分,ORM 的完整配置是 OrmConfig.

实现

示例应用程序所暴露的实验领域模型是简单明了的,由两个实体组成——汽车制造商和 汽车模型。 它们的名称正好表示了它们的含义,它们之间的关系显而易见:一个汽车制造商可以拥有多个汽车模型。

除了名称外,汽车制造商还丰富了属性(列),这些属性由应用程序用户在运行时动态输入。示例用例是简单明了的:

  • 用户被请求提供动态属性的目标名称和类型
  • 创建了一些汽车制造商,并为之前添加的动态属性提供了具体值,然后
  • 实体通过初始属性和运行时定义的属性加载回去

初始实体使用下面的数据库表进行映射:

SQL

 

对应的领域类使用ORM特定的注释进行装饰,以配置与上述数据库表的映射。

Java

 

Java

 

一些注意事项:

  • @Table – 将类映射(关联)到数据库表
  • @PkColumn – 将id(唯一标识符)映射到表主键
  • @Column – 将类成员映射到表列
  • @Child – 定义与另一个实体的关系
  • @Child注释的成员 – 配置为惰性加载
  • type表列 – 映射到enum字段 – CarType

为了使CarManufacturer类支持运行时定义的属性(映射到运行时定义的表列),定义了如下子类:

Java

 

该类 Map中存储运行时定义的属性(字段)。运行时字段值与ORM之间的交互通过实现DynamicColumnEntity 接口来完成。

Java

 

  • setValue() – 用于在从表中读取时设置运行时定义的列的值
  • getValue() – 用于在保存到表中时检索运行时定义列的值

DynamicColumn 将运行时定义的属性类似地映射到它们对应的列,就像@Column 注解将编译时已知成员映射一样。

运行应用程序时,将执行CfRunner。要求用户输入所需的动态自定义属性的名称和类型,以丰富CarManufacturer 实体(为简单起见,仅支持intvarchar 类型)。

对于每个名称-类型对,将执行DML命令,以便可以将新列添加到CarManufacturer 数据库表中。以下方法(在CarService中声明)执行操作。

Java

 

每个输入属性都记录为DefaultDynamicColumn,一个DynamicColumn 参考实现。

一旦定义了所有属性,将向数据库添加两个汽车制造商,因为用户为每个属性提供值。

Java

 

以下方法(在CarService中声明)实际上通过ORM创建实体。

Java

 

OrmOperations update()方法的两个参数版本被调用,它允许传递一个UpdateSettings实例,并在执行时通知ORM有运行时定义的值将被持久化。

最后,创建了两个汽车模型,对应于之前添加的一个汽车制造商。

Java

 

下面的方法(在CarService中声明)实际上通过ORM创建实体,这次使用OrmOperations的update()方法来持久化没有动态属性的实体。为了方便,多个实体在一次调用中被创建。

Java

 

作为最后一步,通过ORM生成的查询根据名称加载回其中一个创建的制造商。

Java

 

关于上述方法的一些解释值得做。

OrmOperations newSqlBuilder() 方法创建一个SqlBuilder实例,正如其名称所示,这可以用来生成SQL查询。SqlBuilder select() 方法生成查询的从表选择部分,而其余部分(where, order by)必须添加。查询选择部分可以通过传递EntityDescriptorNodeCallback 实例进行自定义(关于EntityDescriptorNodeCallback的详细信息可能是未来文章的主题)。

为了让 ORM 知道计划是选择和映射运行时定义的列,需要传递一个 DynamicColumnsEntityNodeCallback 。同时,提供一个 AutoEagerLoader ,以便 ORM 理解要急切加载与制造商相关的 CarModels 列表。然而,这与运行时定义的属性无关,但它展示了如何急切加载子成员。

结论

虽然在关系数据库中存储数据时,可能还有其他处理运行时定义列的方法,但本文所提出的方法的优势在于使用标准数据库列,这些列通过 ORM 直接生成的标准 SQL 查询进行读写。

我们在“社区”中讨论 asentinel-orm 的机会并不罕见,讨论我们开发这种工具的原因。通常,开发者在面对定制的 ORM 时起初显得犹豫和保留,问为什么不使用 Hibernate 或其他 JPA 实现。

在我们的案例中,主要驱动因素是需要一种快速、灵活且易于处理的方式来处理有时相当多的运行时定义属性(列),这些属性属于业务领域的实体。对我们来说,这被证明是正确的方式。应用程序在生产环境中运行顺利,客户对速度和性能感到满意,开发者在直观的 API 下感到舒适和富有创造力。

项目现在是开源的,任何感兴趣的人都可以很容易地查看,形成客观的看法,并且,为什么不呢,可以进行分支,提交 PR,并做出贡献。

资源

  • 开源 ORM 项目在 这里
  • 示例应用程序的源代码在 这里

Source:
https://dzone.com/articles/runtime-defined-columns-with-asentinel-orm