JPA Hibernate 运行时的动态实体映射和持久化

JPA Hibernate Dynamic entity mapping & persistence at runtime

基本上我们有一个 spring 引导应用程序,它要求用户可以定义 his/her 自己的字段集,并且这些字段应该通过 JPA/Hibernate 在运行时。这些 classes 将通过 bytebuddy 动态生成。

所有这些都应该动态完成,而无需重新启动应用程序。 Hibernate 动态映射不是一个选项,因为我们将完全创建新的 classes 并重新映射它们。

我也考虑过使用 EAV 模型,但这行不通,因为我们需要为每组数据单独 table,因此 JSON 不能混合在同一个 table.

我正在考虑的第一个解决方案是代理 EntityManagetFactory,当我们有一个新的实体要映射时,我将重新创建 EntityManagetFactory 并将新的映射实体添加到它,我也会有hbm2ddl.auto 设置为 "update" 以确保创建新的 tables 方案。

问题是我不知道还有哪些 class 可能需要代理,我相信我必须代理 Hibernate SessionFactory 但我不确定还有多少 classes 需要重新创建和代理,我相信这是一条复杂的道路。

另一种解决方案是通过使用 Hibernate OGM 在 SQL 和 NoSQL 解决方案之间进行混合,但在那种情况下,我将失去与现有 SQL 个实体,我不赞成 运行 第二个 NoSQL DB.

有没有其他我可以探索的解决方案?

编辑:

我会使用 bytebuddy 来动态生成新的 classes 并且它们会有 @Entity 注释,生成的 classes 被写入一个临时 jar 文件(例如 /tmp/myjar.jar )

使用 BeanPostProcessor.postProcessAfterInitialization 我会用代理 class.

替换 LocalContainerEntityManagerFactoryBean

我还使用了 LocalContainerEntityManagerFactoryBean .setPersistenceUnitPostProcessors 添加一个额外的处理器来处理新创建的 jarclasses

所以现在在使用 bytebuddy 创建新的 class 之后,我将手动调用 LocalContainerEntityManagerFactoryBeanProxy.afterProperties 来完成引导 JPA 和休眠层的所有工作,我还设置了 "hibernate.hbm2ddl.auto" 属性 到 "update" 以便创建模式(我知道在生产环境中这样做有风险)

Hibernate 将实体映射到表,元数据是在 bootstrap 期间构建的。因此,当应用程序处于 运行.

时,您无法即时修改它

但是,只要不断添加新表而不修改现有结构,就可以在架构层面解决这个问题:

  1. 您根据需要进行 class 更改。
  2. 您构建项目工件。
  3. 您将新项目工件部署到新服务器。
  4. 您将流量从旧服务器实例切换到负载均衡器的新服务器实例而无需停机。

或者,只需将像 MongoDB 这样的 NoSQL 数据库与 Hibernate OGM 一起使用,因为无论如何您的要求都不太适合关系数据库。

但是,如果您已经在使用 RDBMS,那么 it's simpler to just use JSON 不要仅仅因为这个原因而切换到 NoSQL 数据库。

首先,问题是如何在热部署中添加实体class。我们可以通过一些交换工具(spring-boot-devtools,或 maven 复制资源)来完成。 其次,要构建实体的不同模型,可以使用 JPA Inheritance(https://en.wikibooks.org/wiki/Java_Persistence/Inheritance) 或 JPA 行映射器。

但是,我发现更容易将 JSON 对象作为文本保存在数据库中,并让服务的使用者(前台或其他服务)对其进行解析。

另一种方法是尝试在运行时通过 class 路径加载 class。我将尝试将 Json 对象持久化为文本及其在 ddbb 中的类型。然后在 application.properties 中创建其类型和 class 路径(数据的 class java)的映射,然后执行如下操作:

static{
     Map<String, String> typeClassPathMap = ......// from properties
     File file = new File("c:\class-path\");
    // Convert File to a URL
    URL url = file.toURL();          
    // file:/c:/myclasses/
    URL[] urls = new URL[]{url};
    ClassLoader cl = new URLClassLoader(urls);
}

 Class<?> loadClass(String type){

    Class clazz = urlClassLoader.loadClass(typeClassPathMap.get(type));
 }

它将在运行时读取 class 并且可以通过属性进行配置。之后,对于每种类型的数据,我们在属性文件中定义su类型和class路径。当数据来自ddbb时,我们根据它的类型得到javaclass,并解析为object。当我们需要创建新类型的数据时,我们将 class 留在 class-path 中,并在属性中配置它。