最近在探索 Mendix 时,我注意到他们有一个 Platform SDK,允许您通过 API 与 mendix 应用程序模型进行交互。
这给了我一个想法,探索它是否可以用于创建我们的领域模型。具体来说,是基于现有的传统应用程序创建领域模型。
如果进一步推广,这可用于将任何现有应用程序转换为 Mendix 并从那里继续开发。
因此,我创建了一个带有简单 API 和数据库层的小型 Java/Spring Web 应用程序。为了简单起见,它使用嵌入式 H2 数据库。
在这篇文章中,我们将仅转换 JPA 实体。让我们来看看它们:
@Entity @Table(name = "CAT") class Cat { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private int age; private String color; @OneToOne private Human humanPuppet; ... constructor ... ... getters ... } @Entity @Table(name = "HUMAN") public class Human { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; ... constructor ... ... getters ... }
如您所见,它们非常简单:一只有名字、年龄、颜色的猫和它的人类傀儡,因为正如我们所知,猫统治着世界。
它们都有一个自动生成的 ID 字段。猫与人类有一对一的联系,这样它就可以随时称呼它的人类。 (如果它不是 JPA 实体,我会放置一个 meow() 方法,但让我们将其留到将来)。
应用程序功能齐全,但现在我们只对数据层感兴趣。
这可以通过几种不同的方式来完成:
我选择了选项 2,因为它更快,而且我无法轻松找到可以执行选项 1 的库。
接下来,我们需要决定构建后如何公开 json。为了简单起见,我们只需将其写入文件即可。一些替代方法可能是:
现在让我们看看实际的代码:
public class MendixExporter { public static void exportEntitiesTo(String filePath) throws IOException { AnnotatedTypeScanner typeScanner = new AnnotatedTypeScanner(false, Entity.class); Set<Class<?>> entityClasses = typeScanner.findTypes(JavaToMendixApplication.class.getPackageName()); log.info("Entity classes are: {}", entityClasses); List<MendixEntity> mendixEntities = new ArrayList<>(); for (Class<?> entityClass : entityClasses) { List<MendixAttribute> attributes = new ArrayList<>(); for (Field field : entityClass.getDeclaredFields()) { AttributeType attributeType = determineAttributeType(field); AssociationType associationType = determineAssociationType(field, attributeType); String associationEntityType = determineAssociationEntityType(field, attributeType); attributes.add( new MendixAttribute(field.getName(), attributeType, associationType, associationEntityType)); } MendixEntity newEntity = new MendixEntity(entityClass.getSimpleName(), attributes); mendixEntities.add(newEntity); } writeToJsonFile(filePath, mendixEntities); } ... }
我们首先查找应用程序中标有 JPA 的 @Entity 注释的所有类。
然后,对于每堂课,我们:
对于每个字段,我们:
确定属性的类型:
private static final Map<Class<?>, AttributeType> JAVA_TO_MENDIX_TYPE = Map.ofEntries( Map.entry(String.class, AttributeType.STRING), Map.entry(Integer.class, AttributeType.INTEGER), ... ); // we return AttributeType.ENTITY if we cannot map to anything else
本质上,我们只是通过在 JAVA_TO_MENDIX_TYPE 映射中查找 java 类型与我们的自定义枚举值进行匹配。
接下来,我们检查这个属性是否实际上是一个关联(指向另一个@Entity)。如果是这样,我们确定关联的类型:一对一、一对多、多对多:
@Entity @Table(name = "CAT") class Cat { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private int age; private String color; @OneToOne private Human humanPuppet; ... constructor ... ... getters ... } @Entity @Table(name = "HUMAN") public class Human { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; ... constructor ... ... getters ... }
为此,我们只需检查之前映射的属性类型。如果它是 Entity,这仅意味着在之前的步骤中我们无法将其映射到任何原始 java 类型、String 或 Enum。
然后我们还需要决定它是什么类型的关联。检查很简单:如果是 List 类型,则它是一对多,否则是一对一(尚未实现“多对多”)。
然后我们为找到的每个字段创建一个 MendixAttribute 对象。
完成后,我们只需为实体创建一个 MendixEntity 对象并分配属性列表。
MendixEntity 和 MendixAttribute 是我们稍后将用来映射到 json 的类:
public class MendixExporter { public static void exportEntitiesTo(String filePath) throws IOException { AnnotatedTypeScanner typeScanner = new AnnotatedTypeScanner(false, Entity.class); Set<Class<?>> entityClasses = typeScanner.findTypes(JavaToMendixApplication.class.getPackageName()); log.info("Entity classes are: {}", entityClasses); List<MendixEntity> mendixEntities = new ArrayList<>(); for (Class<?> entityClass : entityClasses) { List<MendixAttribute> attributes = new ArrayList<>(); for (Field field : entityClass.getDeclaredFields()) { AttributeType attributeType = determineAttributeType(field); AssociationType associationType = determineAssociationType(field, attributeType); String associationEntityType = determineAssociationEntityType(field, attributeType); attributes.add( new MendixAttribute(field.getName(), attributeType, associationType, associationEntityType)); } MendixEntity newEntity = new MendixEntity(entityClass.getSimpleName(), attributes); mendixEntities.add(newEntity); } writeToJsonFile(filePath, mendixEntities); } ... }
最后,我们保存一个List
有趣的部分来了,我们如何读取上面生成的 json 文件并从中创建 mendix 实体?
Mendix 的 Platform SDK 有一个 Typescript API 可以与之交互。
首先,我们将创建对象来表示我们的实体和属性,以及关联和属性类型的枚举:
private static final Map<Class<?>, AttributeType> JAVA_TO_MENDIX_TYPE = Map.ofEntries( Map.entry(String.class, AttributeType.STRING), Map.entry(Integer.class, AttributeType.INTEGER), ... ); // we return AttributeType.ENTITY if we cannot map to anything else
接下来,我们需要使用 appId 获取我们的应用程序,创建临时工作副本,打开模型,并找到我们感兴趣的域模型:
private static AssociationType determineAssociationType(Field field, AttributeType attributeType) { if (!attributeType.equals(AttributeType.ENTITY)) return null; if (field.getType().equals(List.class)) { return AssociationType.ONE_TO_MANY; } else { return AssociationType.ONE_TO_ONE; } }
SDK 实际上会从 git 中提取我们的 mendix 应用程序并进行处理。
读取 json 文件后,我们将循环实体:
public record MendixEntity( String name, List<MendixAttribute> attributes) { } public record MendixAttribute( String name, AttributeType type, AssociationType associationType, String entityType) { public enum AttributeType { STRING, INTEGER, DECIMAL, AUTO_NUMBER, BOOLEAN, ENUM, ENTITY; } public enum AssociationType { ONE_TO_ONE, ONE_TO_MANY } }
这里我们使用domainmodels.Entity.createIn(domainModel);在我们的域模型中创建一个新实体并为其分配一个名称。我们可以分配更多属性,例如文档、索引,甚至实体在域模型中呈现的位置。
我们在单独的函数中处理属性:
interface ImportedEntity { name: string; generalization: string; attributes: ImportedAttribute[]; } interface ImportedAttribute { name: string; type: ImportedAttributeType; entityType: string; associationType: ImportedAssociationType; } enum ImportedAssociationType { ONE_TO_ONE = "ONE_TO_ONE", ONE_TO_MANY = "ONE_TO_MANY" } enum ImportedAttributeType { INTEGER = "INTEGER", STRING = "STRING", DECIMAL = "DECIMAL", AUTO_NUMBER = "AUTO_NUMBER", BOOLEAN = "BOOLEAN", ENUM = "ENUM", ENTITY = "ENTITY" }
这里我们唯一需要付出一些努力的就是将属性类型映射到有效的 mendix 类型。
接下来我们处理关联。首先,由于在我们的Java实体中关联是通过字段声明的,因此我们需要区分哪些字段是简单属性,哪些字段是关联。为此,我们只需要检查它是实体类型还是原始类型:
const client = new MendixPlatformClient(); const app = await client.getApp(appId); const workingCopy = await app.createTemporaryWorkingCopy("main"); const model = await workingCopy.openModel(); const domainModelInterface = model.allDomainModels().filter(dm => dm.containerAsModule.name === MyFirstModule")[0]; const domainModel = await domainModelInterface.load();
让我们创建关联:
function createMendixEntities(domainModel: domainmodels.DomainModel, entitiesInJson: any) { const importedEntities: ImportedEntity[] = JSON.parse(entitiesInJson); importedEntities.forEach((importedEntity, i) => { const mendixEntity = domainmodels.Entity.createIn(domainModel); mendixEntity.name = importedEntity.name; processAttributes(importedEntity, mendixEntity); }); importedEntities.forEach(importedEntity => { const mendixParentEntity = domainModel.entities.find(e => e.name === importedEntity.name) as domainmodels.Entity; processAssociations(importedEntity, domainModel, mendixParentEntity); }); }
除了名称之外,我们还有 4 个重要的属性需要设置:
子实体。在最后一步中,我们为每个 java 实体创建了 mendix 实体。现在我们只需要根据实体中java字段的类型找到匹配的实体:
function processAttributes(importedEntity: ImportedEntity, mendixEntity: domainmodels.Entity) { importedEntity.attributes.filter(a => a.type !== ImportedAttributeType.ENTITY).forEach(a => { const mendixAttribute = domainmodels.Attribute.createIn(mendixEntity); mendixAttribute.name = capitalize(getAttributeName(a.name, importedEntity)); mendixAttribute.type = assignAttributeType(a.type, mendixAttribute); }); }
关联类型。如果是一对一的,它会映射到一个引用。如果是一对多,则映射到参考集。我们现在将跳过多对多。
协会所有者。一对一和多对多关联都具有相同的所有者类型:两者。对于一对一,所有者类型必须为默认。
Mendix Platform SDK 将在我们的 mendix 应用程序的本地工作副本中创建实体。现在我们只需要告诉它提交更改:
@Entity @Table(name = "CAT") class Cat { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; private int age; private String color; @OneToOne private Human humanPuppet; ... constructor ... ... getters ... } @Entity @Table(name = "HUMAN") public class Human { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; ... constructor ... ... getters ... }
几秒钟后,您可以在 Mendix Studio Pro 中打开应用程序并验证结果:
现在你已经看到了:猫和人的实体,它们之间存在一对一的关联。
如果您想亲自尝试或查看完整代码,请访问此存储库。
Mendix Platform SDK 是一项强大的功能,允许以编程方式与 mendix 应用程序进行交互。他们列出了一些示例用例,例如导入/导出代码、分析应用程序复杂性。
如果您有兴趣,请看一下。
对于本文,您可以在此处找到完整代码。
以上是将 JPA 实体转换为 Mendix的详细内容。更多信息请关注PHP中文网其他相关文章!