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

时间:2009-05-25 13:03:41

标签: java hibernate jpa

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

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

显然这不是那么容易做到的,我很欣赏这一点。

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

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

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

5 个答案:

答案 0 :(得分:15)

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

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

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

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

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

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

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

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

我还考虑过在运行时动态更改或分配注释。虽然我还不确定这是否可能,即使是我不确定它是否一定有帮助。我无法想象一个实体经理或缓存没有因为那种事情而无可救药地混淆。

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

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

答案 1 :(得分:9)

您可以通过自定义ClassLoader在加载时指定表名,该类在加载时在类上重写@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最终负责更改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);
        }
    }

}

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

}

答案 2 :(得分:4)

听起来像你所追求的是Overriding the JPA Annotations with an ORM.xml

这将允许您指定注释,但仅在它们更改的位置覆盖它们。我已完成同样的操作,覆盖schema注释中的@Table,因为它在我的环境之间发生变化。

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

[更新此答案,因为它没有详细记录,其他人可能会发现它有用]

这是我的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>

答案 3 :(得分:3)

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

答案 4 :(得分:1)

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