java JPA:如何在运行时指定类对应的表名?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/906671/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 14:18:15  来源:igfitidea点击:

JPA: How do I specify the table name corresponding to a class at runtime?

javahibernatejpa

提问by Thorbj?rn Ravn Andersen

(note: I'm quite familiar with Java, but not with Hibernate or JPA - yet :) )

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

I want to write an application which talks to a DB2/400 database through JPA and I have now that I can get all entries in the table and list them to System.out (used MyEclipse to reverse engineer). I understand that the @Table annotation results in the name being statically compiled with the class, but I need to be able to work with a table where the name and schema are provided at runtime (their defintion are the same, but we have many of them).

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

Apparently this is not SO easy to do, and I'd appreciate a hint.

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

I have currently chosen Hibernate as the JPA provider, as it can handle that these database tables are not journalled.

我目前选择 Hibernate 作为 JPA 提供者,因为它可以处理这些数据库表没有被记录的情况。

So, the question is, how can I at runtime tell the Hibernate implementation of JPA that class A corresponds to database table B?

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

(edit: an overridden tableName() in the Hibernate NamingStrategy may allow me to work around this intrinsic limitation, but I still would prefer a vendor agnostic JPA solution)

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

回答by cletus

You need to use the XML versionof the configuration rather than the annotations. That way you can dynamically generate the XML at runtime.

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

Or maybe something like Dynamic JPAwould interest you?

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

I think it's necessary to further clarify the issues with this problem.

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

The first question is: are the set of tables where an entity can be stored known? By this I mean you aren't dynamically creating tables at runtime and wanting to associate entities with them. This scenario calls for, say, three tables to be known at compile-time. If that is the case you can possibly use JPA inheritance. The OpenJPA documentation details the table per classinheritance strategy.

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

The advantage of this method is that it is pure JPA. It comes with limitations however, being that the tables have to be known and you can't easily change which table a given object is stored in (if that's a requirement for you), just like objects in OO systems don't generally change class or type.

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

If you want this to be truly dynamic and to move entities between tables (essentially) then I'm not sure JPA is the right tool for you. An awful lot of magicgoes into making JPA work including load-time weaving (instrumentation) and usually one or more levels of caching. What's more the entity manager needs to record changes and handle updates of managed objects. There is no easy facility that I know of to instruct the entity manager that a given entity should be stored in one table or another.

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

Such a move operation would implicitly require a delete from one table and insertion into another. If there are child entities this gets more difficult. Not impossible mind you but it's such an unusual corner case I'm not sure anyone would ever bother.

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

A lower-level SQL/JDBC framework such as Ibatismay be a better bet as it will give you the control that you want.

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

I've also given thought to dynamically changing or assigning at annotations at runtime. While I'm not yet sure if that's even possible, even if it is I'm not sure it'd necessarily help. I can't imagine an entity manager or the caching not getting hopelessly confused by that kind of thing happening.

我还考虑过在运行时动态更改或分配注释。虽然我还不确定这是否可能,但即使是我也不确定它是否一定会有所帮助。我无法想象实体管理器或缓存不会因为发生这种事情而绝望地混淆。

The other possibility I thought of was dynamically creating subclasses at runtime (as anonymous subclasses) but that still has the annotation problem and again I'm not sure how you add that to an existing persistence unit.

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

It might help if you provided some more detail on what you're doing and why. Whatever it is though, I'm leaning towards thinking you need to rethink what you're doing or how you're doing it or you need to pick a different persistence technology.

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

回答by Adam Paynter

You may be able to specify the table name at load time via a custom ClassLoaderthat re-writes the @Tableannotation on classes as they are loaded. At the moment, I am not 100% sure how you would ensure Hibernate is loading its classes via this ClassLoader.

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

Classes are re-written using the ASM bytecode framework.

使用ASM 字节码框架重写类。

Warning:These classes are experimental.

警告:这些类是实验性的。

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

}

The TableClassLoaderrelies on the TableClassVisitorto catch the visitAnnotationmethod calls:

TableClassLoader依靠TableClassVisitorvisitAnnotation方法调用:

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

}

The TableAnnotationVisitoris ultimately responsible for changing the namefield of the @Tableannotation:

TableAnnotationVisitor最终改变了负责name该领域的@Table注释:

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

}

Because I didn't happen to find an AnnotationAdapterclass in ASM's library, here is one I made myself:

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

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

}

回答by Damo

It sounds to me like what you're after is Overriding the JPA Annotations with an ORM.xml.

在我看来,您所追求的是用 ORM.xml 覆盖 JPA 注释

This will allow you to specify the Annotations but then override them only where they change. I've done the same to override the schemain the @Tableannotation as it changes between my environments.

这将允许您指定注释,然后仅在它们更改的地方覆盖它们。我对我所做的覆盖相同schema@Table注解,因为它我的环境之间变化。

Using this approach you can also override the table name on individual entities.

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

[Updating this answer as it's not well documented and someone else may find it useful]

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

Here's my orm.xml file (note that I am onlyoverriding the schema and leaving the other JPA & Hibernate annotations alone, however changing the table here is totally possible. Also note that I am annotating on the field not the Getter)

这是我的 orm.xml 文件(请注意,我只是覆盖了架构并单独留下其他 JPA 和 Hibernate 注释,但是在这里更改表是完全可能的。另请注意,我是在字段上而不是 Getter 上进行注释)

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

回答by dfa

as alternative of XML configuration, you may want to dynamically generate java class with annotation using your preferred bytecode manipulation framework

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

回答by Steve Skrla

If you don't mind binding your self to Hibernate, you could use some of the methods described at https://www.hibernate.org/171.html. You may find your self using quite a few hibernate annotations depending on the complexity of your data, as they go above and beyond the JPA spec, so it may be a small price to pay.

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