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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 public class TableClassLoader extends ClassLoader { private final Map tablesByClassName; public TableClassLoader(Map tablesByClassName) { super(); this.tablesByClassName = tablesByClassName; } public TableClassLoader(Map 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最终负责更改@Table注释的name字段:

 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 Annotations 。

这将允许您指定注释,但只在它们更改的位置覆盖它们。 我已经做了同样的事情来覆盖@Table注释中的schema ,因为它在我的环境之间变化。

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

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

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

   models.jpa.eglobal  

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

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