2009-05-25 9 views
11

(nota: ho abbastanza familiarità con Java, ma non con Hibernate o JPA - ancora :))JPA: Come si specifica il nome della tabella corrispondente a una classe in fase di esecuzione?

Voglio scrivere un'applicazione che comunica con un database DB2/400 tramite JPA e ora ho che io può ottenere tutte le voci nella tabella ed elencarle a System.out (usato MyEclipse per decodificare). Capisco che l'annotazione @Table porti al nome che viene compilato staticamente con la classe, ma devo essere in grado di lavorare con una tabella in cui il nome e lo schema sono forniti in fase di esecuzione (la loro definizione è la stessa, ma ne abbiamo molti loro).

Apparentemente questo non è COSÌ facile da fare e apprezzerei un suggerimento.

Attualmente ho scelto Hibernate come provider JPA, in quanto è in grado di gestire che queste tabelle del database non sono journal.

Quindi, la domanda è, come posso in runtime dire all'implementazione Hibernate di JPA che la classe A corrisponde alla tabella B del database?

(edit: un tableName sovrascritto() nella Hibernate NamingStrategy può permettermi di aggirare questa limitazione intrinseca, ma ho ancora preferirei un fornitore della soluzione agnostica JPA)

+0

Hai bisogno di lavorare con più tabelle (con lo stesso JPA @Entity) durante l'esecuzione di ogni programma, o è solo una tabella per esecuzione? – mtpettyp

+0

Potenzialmente un numero qualsiasi di tabelle - questa probabilmente sarà un'applicazione web –

risposta

14

È necessario utilizzare il XML version della configurazione piuttosto che le annotazioni. In questo modo è possibile generare dinamicamente l'XML in fase di runtime.

0 0 Forse qualcosa come Dynamic JPA ti interessa?

Penso che sia necessario chiarire ulteriormente i problemi relativi a questo problema.

La prima domanda è: l'insieme di tabelle in cui è possibile memorizzare un'entità? Con questo voglio dire che non stai creando dinamicamente tabelle in runtime e vuoi associare le entità con esse. Questo scenario richiede, per esempio, tre tabelle da conoscere al in fase di compilazione. In questo caso è possibile utilizzare l'ereditarietà JPA. La documentazione di OpenJPA descrive la strategia di ereditarietà table per class.

Il vantaggio di questo metodo è che è puro JPA. Tuttavia presenta limitazioni, poiché le tabelle devono essere conosciute e non è possibile modificare facilmente la tabella in cui è archiviato un dato oggetto (se questo è un requisito per te), proprio come gli oggetti nei sistemi OO generalmente non cambiano classe o digita

Se si desidera che questo sia veramente dinamico e spostare le entità tra le tabelle (essenzialmente), non sono sicuro che JPA sia lo strumento giusto per te. Un awful lot of magic contribuisce a far funzionare l'APP, compresa la tessitura a tempo di caricamento (strumentazione) e di solito uno o più livelli di memorizzazione nella cache. Inoltre, il gestore delle entità deve registrare le modifiche e gestire gli aggiornamenti degli oggetti gestiti. Non vi è alcuna facilità che io conosca per istruire il gestore di entità che una determinata entità debba essere memorizzata in una tabella o in un'altra.

Tale operazione di spostamento richiederebbe implicitamente l'eliminazione da una tabella e l'inserimento in un'altra. Se ci sono entità figlio questo diventa più difficile. Non è impossibile pensarci, ma è un caso d'angolo così insolito che non sono sicuro che qualcuno possa mai darci fastidio.

Un framework SQL/JDBC di livello inferiore come Ibatis può essere una soluzione migliore in quanto fornisce il controllo desiderato.

Ho anche pensato di cambiare o assegnare dinamicamente alle annotazioni in fase di esecuzione.Anche se non sono ancora sicuro se ciò è possibile, anche se non sono sicuro che sarebbe necessariamente d'aiuto. Non riesco ad immaginare un gestore di entità o il caching che non si confonda irrimediabilmente con quel genere di cose che accadono.

L'altra possibilità che ho pensato è stata creare dinamicamente sottoclassi in fase di esecuzione (come sottoclassi anonime) ma che ha ancora il problema di annotazione e di nuovo non sono sicuro di come lo si aggiunge a un'unità di persistenza esistente.

Potrebbe essere utile fornire ulteriori dettagli su ciò che si sta facendo e perché. Comunque sia, sono propenso a pensare che devi ripensare a quello che stai facendo o come lo stai facendo o devi scegliere una diversa tecnologia di persistenza.

+0

Mi piacerebbe rimanere all'interno di JPA se possibile. –

+0

Domanda 1: lo schema della tabella è corretto, ma i nomi delle tabelle no. Ciò significa che se A, B e C dovessero essere modificati, tutti condividono lo stesso schema ed essere logicamente separati identità. Cioè gli articoli non verranno spostati da A a B o simili. In teoria, in teoria, posso andare avanti con la lettura di una mappa per riga e lavorare con quella, ma sto cercando di ottenere la funzionalità "hey, qualcun altro ha aggiornato questa riga". –

+0

Mi soffermerò un po 'su come risolvere questo meglio sulla base del feedback, e potrebbe essere che questo sia tecnologicamente eccessivo. Devo anche considerare che questo sarà probabilmente usato per molto tempo, quindi non dovrebbe essere inutilmente complesso per i futuri manutentori. –

2

come alternativa di configurazione XML, si consiglia di generare dinamicamente classe Java con annotazione di utilizzare il framework di manipolazione bytecode preferito

1

Se non ti dispiace vincolante la vostra auto per Hibernate, è possibile utilizzare alcuni dei metodi descritti a https://www.hibernate.org/171.html. Potresti trovare te stesso usando un bel po 'di annotazioni di ibernazione a seconda della complessità dei tuoi dati, dato che vanno oltre la specifica JPA, quindi potrebbe essere un piccolo prezzo da pagare.

+0

Preferirei JPA, ma Hibernate è anche un'opzione. Come accennato, lo schema è statico ma dovrò modificare una o più tabelle di database in una singola sessione che differiscono solo per il nome della tabella. –

8

Potrebbe essere possibile specificare il nome della tabella al momento del caricamento tramite un numero personalizzato ClassLoader che riscrive l'annotazione @Table sulle classi mentre vengono caricate. Al momento, non sono sicuro al 100% su come garantire che Hibernate stia caricando le sue classi tramite questo ClassLoader.

Le classi vengono riscritte utilizzando the ASM bytecode framework.

Attenzione: Queste classi sono sperimentali.

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

} 

La TableClassLoader si basa sulla TableClassVisitor per catturare il metodo visitAnnotation chiama:

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

} 

Il TableAnnotationVisitor è il responsabile ultimo per cambiare il campo name del @Table della nota:

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

} 

Perché Non ho trovato uno AnnotationAdapter classe nella biblioteca di ASM, qui è uno io stesso ho fatto:

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

idea molto bella! –

+2

Eccellente, funziona con una piccola quantità di massaggio, utilizzandolo per gestire l'annotazione DynamoDBTable. Ora per vedere se il mio contenitore web consente il caricatore di classe personalizzato. – stjohnroe

4

Sembra a me come quello che siete dopo è Overriding the JPA Annotations with an ORM.xml.

Ciò consentirà di specificare le Annotazioni, ma di sovrascriverle solo dove cambiano. Ho fatto lo stesso per ignorare lo schema nell'annotazione @Table mentre cambia tra i miei ambienti.

Utilizzando questo approccio è anche possibile sovrascrivere il nome della tabella su singole entità.

[Aggiornamento questa risposta come non è ben documentato e qualcun altro può essere utile]

Ecco la mia ORM.file xml (notare che io sono solo sovrascrivendo lo schema e lasciando stare le altre annotazioni JPA & Hibernate, tuttavia modificare la tabella qui è del tutto possibile. Si noti inoltre che sto annotare sul campo non il 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> 
+0

perfetto questo ha risolto il problema per me. ** BTW: ** devi ma orm.xml in META-INF sotto il classpath –

Problemi correlati