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

2022-09-03 17:21:03

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

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

显然,这并不容易做到,我很感激得到提示。

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

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

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


答案 1

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

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

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

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

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

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

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

Ibatis 这样的低级 SQL/JDBC 框架可能是一个更好的选择,因为它会给你想要的控制。

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

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

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


答案 2

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

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

}

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

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

}

最终负责更改注释的字段:TableAnnotationVisitorname@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);
    }

}

推荐