[英]JPA: How do I specify the table name corresponding to a class at runtime?
(注意:我對Java非常熟悉,但不熟悉Hibernate或JPA - 但:)
我想編寫一個通過JPA與DB2 / 400數據庫通信的應用程序,現在我可以獲取表中的所有條目並將它們列出到System.out(使用MyEclipse進行逆向工程)。 我知道@Table注釋導致名稱與類靜態編譯,但我需要能夠使用一個表,其中名稱和模式在運行時提供(他們的定義是相同的,但我們有很多他們)。
顯然這不是那么容易做到的,我很欣賞這一點。
我目前選擇Hibernate作為JPA提供程序,因為它可以處理這些數據庫表沒有記錄。
所以,問題是,我如何在運行時告訴JPA的Hibernate實現,類A對應於數據庫表B?
(編輯:在Hibernate NamingStrategy中重寫的tableName()可能允許我解決這個內在限制,但我仍然希望與供應商無關的JPA解決方案)
您需要使用配置的XML版本而不是注釋。 這樣,您就可以在運行時動態生成XML。
或者像動態JPA這樣的東西會讓你感興趣嗎?
我認為有必要進一步澄清這個問題的問題。
第一個問題是:是否可以存儲實體的表集? 我的意思是你不是在運行時動態創建表並希望將實體與它們相關聯。 例如,這種情況要求在編譯時知道三個表。 如果是這種情況,您可以使用JPA繼承。 OpenJPA文檔詳細說明了每個類繼承策略的表 。
這種方法的優點是它是純粹的JPA。 它有一些限制,因為表必須是已知的,你不能輕易地改變給定對象存儲在哪個表中(如果這是你的要求),就像OO系統中的對象通常不會改變類一樣或者輸入。
如果你想讓它真正動態並在表之間移動實體(本質上),那么我不確定JPA是否適合你。 制作JPA的工作包括加載時間編織(儀器)以及通常一個或多個級別的緩存,這里有很多魔力 。 實體管理器還需要記錄更改並處理托管對象的更新。 我知道沒有簡單的工具來指示實體管理器將給定實體存儲在一個表或另一個表中。
這樣的移動操作將隱含地要求從一個表中刪除並插入另一個表。 如果有子實體,則會變得更加困難。 你不是不可能,但這是一個不尋常的角落,我不確定是否有人會打擾。
像Ibatis這樣的低級SQL / JDBC框架可能是更好的選擇,因為它可以為您提供所需的控件。
我還考慮過在運行時動態更改或分配注釋。 雖然我還不確定這是否可能,即使是我不確定它是否一定有幫助。 我無法想象一個實體經理或者緩存沒有因為那種事情而無可救葯地混淆。
我想到的另一種可能性是在運行時動態創建子類(作為匿名子類),但仍然存在注釋問題,而且我不確定如何將其添加到現有的持久性單元。
如果你提供了一些關於你正在做什么和為什么做的更多細節,這可能會有所幫助。 不管它是什么,我傾向於認為你需要重新思考你正在做什么或者你是如何做的,或者你需要選擇不同的持久性技術。
您可以通過自定義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";
}
}
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)
<?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>
作為XML配置的替代方案,您可能希望使用首選的字節碼操作框架動態生成帶有注釋的java類
如果您不介意將自己綁定到Hibernate,可以使用https://www.hibernate.org/171.html中描述的一些方法。 根據數據的復雜程度,您可能會發現自己使用了大量的休眠注釋,因為它們超出了JPA規范,因此可能需要付出很小的代價。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.