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

Java JPA:如何在运行时指定与类对应的表名?,java,hibernate,jpa,Java,Hibernate,Jpa,(注意:我非常熟悉Java,但不熟悉Hibernate或JPA-现在:) 我想编写一个通过JPA与DB2/400数据库对话的应用程序,现在我可以获得表中的所有条目,并将它们列出到System.out(使用MyEclipse进行反向工程)。我知道@Table注释会导致名称与类一起静态编译,但我需要能够处理在运行时提供名称和模式的表(它们的定义相同,但我们有很多) 显然,这不是那么容易做到的,我希望得到一个提示 我目前选择Hibernate作为JPA提供程序,因为它可以处理这些数据库表没有日志记录的

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

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

显然,这不是那么容易做到的,我希望得到一个提示

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

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

(编辑:Hibernate NamingStrategy中的重写tableName()可能允许我绕过这一固有限制,但我仍然希望使用与供应商无关的JPA解决方案)

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

或者你会感兴趣

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

第一个问题是:可以存储实体的一组表是已知的吗?我的意思是,您不会在运行时动态创建表,也不会希望将实体与表关联。这个场景要求(比如)在编译时知道三个表。如果是这种情况,您可以使用JPA继承。OpenJPA文档详细介绍了继承策略

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

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

这样的移动操作将隐含地要求从一个表中删除并插入到另一个表中。如果存在子实体,这将变得更加困难。请注意,这不是不可能的,但这是一个非常罕见的情况,我不确定是否有人会介意

一个较低级别的SQL/JDBC框架,例如,可能是更好的选择,因为它将为您提供所需的控制

我还考虑了在运行时动态更改或分配注释。虽然我还不确定这是否可能,但即使可能,我也不确定这是否会有帮助。我无法想象一个实体管理器或缓存不会被这种事情搞糊涂

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


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

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

如果您不介意将自己绑定到Hibernate,可以使用中描述的一些方法。根据数据的复杂程度,您可能会发现自己使用了很多hibernate注释,因为它们超出了JPA规范,所以这可能是一个很小的代价。

您可以在加载时通过一个自定义的方法指定表名,在加载类时重新写入
@table
注释。目前,我不能100%确定如何确保Hibernate通过这个类加载器加载它的类

类是使用重新编写的

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

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

}
TableAnnotationVisitor
最终负责更改
@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);
        }
    }

}
因为我没有在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);
    }

}

我觉得你要的是什么

这将允许您指定注释,但仅在注释发生更改时覆盖它们。在
@表
注释中的
模式
在我的环境之间变化时,我也做了同样的操作来覆盖它

使用此方法,还可以覆盖单个实体上的表名

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

这是我的orm.xml文件(请注意,我只重写模式,而不使用其他JPA和Hibernate注释,但是在这里更改表是完全可能的。还要注意,我在字段上注释,而不是在Getter上)


models.jpa.eglobal

如果可能的话,我想留在JPA内。问题1:表模式是固定的,但表名不是固定的。这意味着,如果要编辑A、B和C,它们将共享相同的模式,并且在逻辑上是独立的标识。即项目不会从A移动到B或类似位置。原则上我可以做到
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);
    }

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