【问题标题】:JPA: How do I specify the table name corresponding to a class at runtime?JPA:如何在运行时指定与类对应的表名?
【发布时间】:2010-10-28 17:53:50
【问题描述】:

(注意:我对 Java 非常熟悉,但对 Hibernate 或 JPA 还不是很熟悉 :))

我想编写一个通过 JPA 与 DB2/400 数据库对话的应用程序,现在我可以获取表中的所有条目并将它们列出到 System.out(使用 MyEclipse 进行逆向工程)。我知道 @Table 注释会导致名称与类一起静态编译,但我需要能够使用在运行时提供名称和模式的表(它们的定义是相同的,但我们有很多他们)。

显然这不是那么容易做到的,我很感激一个提示。

我目前选择 Hibernate 作为 JPA 提供程序,因为它可以处理这些数据库表不被记录。

那么,问题是,我如何在运行时告诉 JPA 的 Hibernate 实现类 A 对应于数据库表 B?

(编辑:Hibernate NamingStrategy 中覆盖的 tableName() 可能允许我解决这个内在限制,但我仍然更喜欢与供应商无关的 JPA 解决方案)

【问题讨论】:

  • 您需要在每次程序执行期间处理多个表(使用相同的 JPA @Entity),还是每次执行只处理一个表?
  • 可能有任意数量的表格 - 这很可能是一个 Web 应用程序

标签: java hibernate jpa


【解决方案1】:

您需要使用配置的XML version 而不是注解。这样您就可以在运行时动态生成 XML。

或者像Dynamic JPA 这样的东西你会感兴趣吗?

我认为有必要进一步澄清这个问题的问题。

第一个问题是:可以存储实体的表集是否已知?我的意思是您不是在运行时动态创建表并且希望将实体与它们相关联。例如,这种情况需要在编译时知道三个表。如果是这种情况,您可以使用 JPA 继承。 OpenJPA 文档详细介绍了table per class 继承策略。

这种方法的优点是它是纯JPA。但是它有局限性,因为表必须是已知的,并且您不能轻易更改给定对象存储在哪个表中(如果这是您的要求),就像 OO 系统中的对象通常不会更改类一样或输入。

如果您希望它是真正动态的并(基本上)在表之间移动实体,那么我不确定 JPA 是否适合您。 awful lot of magic 让 JPA 工作,包括加载时编织(仪器)和通常一个或多个级别的缓存。更重要的是,实体管理器需要记录更改并处理托管对象的更新。据我所知,没有简单的工具可以指示实体管理器将给定实体存储在一个或另一个表中。

这样的移动操作会隐含地要求从一个表中删除并插入到另一个表中。如果有子实体,这将变得更加困难。请注意,这并非不可能,但这是一个不寻常的极端案例,我不确定是否有人会打扰。

较低级别的 SQL/JDBC 框架(例如 Ibatis)可能是更好的选择,因为它可以为您提供所需的控制。

我还考虑过在运行时动态更改或分配注释。虽然我还不确定这是否可能,但即使是我也不确定它是否一定会有所帮助。我无法想象实体管理器或缓存不会被发生的这种事情弄得一头雾水。

我想到的另一种可能性是在运行时动态创建子类(作为匿名子类),但这仍然存在注释问题,我再次不确定如何将其添加到现有的持久性单元中。

如果您提供有关您正在做什么以及为什么做的更多详细信息,这可能会有所帮助。不管它是什么,我倾向于认为你需要重新考虑你在做什么或你是如何做的,或者你需要选择一种不同的持久性技术。

【讨论】:

  • 如果可能的话,我想留在 JPA。
  • 问题一:表的schema是固定的,但是表名不是。这意味着如果要编辑 A、B 和 C,它们将共享相同的模式并且在逻辑上是独立的身份。 IE。项目不会从 A 移动到 B 或类似的。原则上,我可以在每行的地图中阅读页面并使用它,但我正在尝试获得“嘿,其他人更新了这一行”功能。
  • 我会根据反馈考虑一下如何最好地解决这个问题,这可能是技术上的矫枉过正。我还必须考虑到这可能会使用很长时间,因此对于未来的维护者来说它不应该是不必要的复杂。
  • 表名更改的问题很重要。如果您考虑一下:JPA 如何知道从特定表中获取特定实体?它怎么知道在那里持久化变化?您必须以某种方式动态地将一个新的动态创建的实体“注册”到持久性单元中。
  • 话虽如此,也许动态 JPA + OSGi 路由可能对您有益,因此您可以在运行时使用新表的实体重新部署实体结构等。不过,这并不是它的设计目的。
【解决方案2】:

作为 XML 配置的替代方案,您可能希望使用首选的字节码操作框架动态生成带有注释的 java 类

【讨论】:

    【解决方案3】:

    如果您不介意将自己绑定到 Hibernate,您可以使用 https://www.hibernate.org/171.html 中描述的一些方法。根据数据的复杂性,您可能会发现自己使用了相当多的 hibernate 注释,因为它们超出了 JPA 规范,因此付出的代价可能很小。

    【讨论】:

    • 我更喜欢 JPA,但 Hibernate 也是一种选择。如前所述,架构是静态的,但我需要在单个会话中编辑一个或多个数据库表,仅表名不同。
    【解决方案4】:

    您可以在加载时通​​过自定义ClassLoader 指定表名,该自定义@Table 在类加载时重写@Table 注释。目前,我不能 100% 确定如何确保 Hibernate 通过这个 ClassLoader 加载它的类。

    使用the ASM bytecode framework 重写类。

    警告:这些课程是实验性的。

    public class TableClassLoader extends ClassLoader {
    
        private final Map<String, String> tablesByClassName;
    
        public TableClassLoader(Map<String, String> tablesByClassName) {
            super();
            this.tablesByClassName = tablesByClassName;
        }
    
        public TableClassLoader(Map<String, String> tablesByClassName, ClassLoader parent) {
            super(parent);
            this.tablesByClassName = tablesByClassName;
        }
    
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            if (tablesByClassName.containsKey(name)) {
                String table = tablesByClassName.get(name);
                return loadCustomizedClass(name, table);
            } else {
                return super.loadClass(name);
            }
        }
    
        public Class<?> loadCustomizedClass(String className, String table) throws ClassNotFoundException {
            try {
                String resourceName = getResourceName(className);
                InputStream inputStream = super.getResourceAsStream(resourceName);
                ClassReader classReader = new ClassReader(inputStream);
                ClassWriter classWriter = new ClassWriter(0);
                classReader.accept(new TableClassVisitor(classWriter, table), 0);
    
                byte[] classByteArray = classWriter.toByteArray();
    
                return super.defineClass(className, classByteArray, 0, classByteArray.length);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
        private String getResourceName(String className) {
            Type type = Type.getObjectType(className);
            String internalName = type.getInternalName();
            return internalName.replaceAll("\\.", "/") + ".class";
        }
    
    }
    

    TableClassLoader 依赖于TableClassVisitor 来捕获visitAnnotation 方法调用:

    public class TableClassVisitor extends ClassAdapter {
    
        private static final String tableDesc = Type.getDescriptor(Table.class);
    
        private final String table;
    
        public TableClassVisitor(ClassVisitor visitor, String table) {
            super(visitor);
            this.table = table;
        }
    
        @Override
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            AnnotationVisitor annotationVisitor;
    
            if (desc.equals(tableDesc)) {
                annotationVisitor = new TableAnnotationVisitor(super.visitAnnotation(desc, visible), table);
            } else {
                annotationVisitor = super.visitAnnotation(desc, visible);
            }
    
            return annotationVisitor;
        }
    
    }
    

    TableAnnotationVisitor 最终负责更改@Table 注释的name 字段:

    public class TableAnnotationVisitor extends AnnotationAdapter {
    
        public final String table;
    
        public TableAnnotationVisitor(AnnotationVisitor visitor, String table) {
            super(visitor);
            this.table = table;
        }
    
        @Override
        public void visit(String name, Object value) {
            if (name.equals("name")) {
                super.visit(name, table);
            } else {
                super.visit(name, value);
            }
        }
    
    }
    

    因为我没有碰巧在 ASM 的库中找到 AnnotationAdapter 类,所以这是我自己制作的:

    public class AnnotationAdapter implements AnnotationVisitor {
    
        private final AnnotationVisitor visitor;
    
        public AnnotationAdapter(AnnotationVisitor visitor) {
            this.visitor = visitor;
        }
    
        @Override
        public void visit(String name, Object value) {
            visitor.visit(name, value);
        }
    
        @Override
        public AnnotationVisitor visitAnnotation(String name, String desc) {
            return visitor.visitAnnotation(name, desc);
        }
    
        @Override
        public AnnotationVisitor visitArray(String name) {
            return visitor.visitArray(name);
        }
    
        @Override
        public void visitEnd() {
            visitor.visitEnd();
        }
    
        @Override
        public void visitEnum(String name, String desc, String value) {
            visitor.visitEnum(name, desc, value);
        }
    
    }
    

    【讨论】:

    • 非常棒,只需少量按摩即可使用,使用它来管理 DynamoDBTable 注释。现在看看我的 Web 容器是否允许自定义类加载器。
    【解决方案5】:

    在我看来,你所追求的是Overriding the JPA Annotations with an ORM.xml

    这将允许您指定注释,然后仅在它们更改的地方覆盖它们。我已经做了同样的事情来覆盖@Table 注释中的schema,因为它在我的环境之间发生变化。

    使用这种方法,您还可以覆盖单个实体上的表名。

    [更新此答案,因为它没有很好的文档记录,其他人可能会觉得它有用]

    这是我的 orm.xml 文件(请注意,我 覆盖架构并单独保留其他 JPA 和 Hibernate 注释,但是完全可以在此处更改表。另请注意,我正在注释在场上不是吸气剂)

    <?xml version="1.0" encoding="UTF-8"?>
    <entity-mappings 
      xmlns="http://java.sun.com/xml/ns/persistence/orm"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd"
      version="1.0">
        <package>models.jpa.eglobal</package>
        <entity class="MyEntityOne" access="FIELD">
            <table name="ENTITY_ONE" schema="MY_SCHEMA"/>
        </entity> 
        <entity class="MyEntityTwo" access="FIELD">
            <table name="ENTITY_TWO" schema="MY_SCHEMA"/>
        </entity> 
    </entity-mappings>
    

    【讨论】:

    • 完美解决了我的问题。 顺便说一句: 你必须在你的类路径下 META-INF 中的 orm.xml
    猜你喜欢
    • 2015-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 2012-06-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多