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

Tho*_*sen 13 java hibernate jpa

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

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

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

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

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

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

cle*_*tus 15

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

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

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

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

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

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

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

Ibatis这样的低级SQL/JDBC框架可能是更好的选择,因为它可以为您提供所需的控件.

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

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

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


Ada*_*ter 9

您可以通过自定义ClassLoader在加载时指定表名,该类在加载时在上重写@Table注释.目前,我并不是100%确定如何确保Hibernate通过此ClassLoader加载其类.

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

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

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

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

  • 非常好,适用于少量按摩,使用它来管理DynamoDBTable注释.现在看看我的Web容器是否允许自定义类加载器. (2认同)

Dam*_*amo 5

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

这将允许您指定注释,然后仅在它们更改的地方覆盖它们。我对我所做的覆盖相同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>
Run Code Online (Sandbox Code Playgroud)