2009-05-25 20 views
11

(nota: Estoy bastante familiarizado con Java, pero no con Hibernate o JPA - aún :))JPA: ¿Cómo especifico el nombre de la tabla correspondiente a una clase en tiempo de ejecución?

Quiero escribir una aplicación que hable con una base de datos DB2/400 a través de JPA y ahora tengo que puede obtener todas las entradas en la tabla y enumerarlas en System.out (usó MyEclipse para realizar ingeniería inversa). Entiendo que la anotación @Table hace que el nombre se compile estáticamente con la clase, pero necesito poder trabajar con una tabla donde el nombre y el esquema se proporcionan en tiempo de ejecución (su definición es la misma, pero tenemos muchos ellos).

Aparentemente esto no es TAN fácil de hacer, y agradecería una pista.

Actualmente he elegido Hibernate como el proveedor de JPA, ya que puede manejar que estas tablas de bases de datos no tengan registros.

Entonces, la pregunta es, ¿cómo puedo en el tiempo de ejecución decir a la implementación de Hibernate de JPA que la clase A corresponde a la tabla B de la base de datos?

(edit: un nombreTabla() sustituido en el Hibernate NamingStrategy puede me permitirá trabajar alrededor de esta limitación intrínseca, pero todavía prefiere un proveedor de soluciones de APP agnóstico)

+0

¿Necesita trabajar con varias tablas (con la misma JPA @Entity) durante cada ejecución del programa, o es solo una tabla por ejecución? – mtpettyp

+0

Potencialmente cualquier número de tablas: probablemente será una aplicación web –

Respuesta

14

Es necesario utilizar el XML version de la configuración en lugar de las anotaciones De esta forma, puede generar dinámicamente el XML en tiempo de ejecución.

O tal vez algo así como Dynamic JPA te interesaría?

Creo que es necesario aclarar aún más los problemas con este problema.

La primera pregunta es: ¿es conocido el conjunto de tablas donde se puede almacenar una entidad? Con esto quiero decir que no estás creando dinámicamente tablas en tiempo de ejecución y que deseas asociar entidades con ellas. Este escenario requiere, por ejemplo, que se conozcan tres tablas en en tiempo de compilación. Si ese es el caso, posiblemente pueda usar la herencia JPA. La documentación de OpenJPA detalla la estrategia de herencia table per class.

La ventaja de este método es que es puro JPA. Sin embargo, viene con limitaciones, ya que se deben conocer las tablas y no se puede cambiar fácilmente en qué tabla se almacena un objeto determinado (si eso es un requisito para usted), al igual que los objetos en los sistemas OO generalmente no cambian de clase o escribe

Si desea que esto sea realmente dinámico y mover entidades entre tablas (esencialmente), entonces no estoy seguro de que JPA sea la herramienta adecuada para usted. Un awful lot of magic va a hacer que el trabajo JPA incluya el tejido en tiempo de carga (instrumentación) y generalmente uno o más niveles de almacenamiento en caché. Además, el gestor de entidades necesita registrar cambios y gestionar actualizaciones de objetos gestionados. No hay una facilidad fácil que yo sepa que indique al gerente de la entidad que una entidad determinada debe almacenarse en una tabla u otra.

Tal operación de movimiento requeriría implícitamente una eliminación de una tabla y la inserción en otra. Si hay entidades secundarias, esto se vuelve más difícil. No es imposible, pero es un caso de esquina tan inusual que no estoy seguro de que alguien se moleste.

Un marco SQL/JDBC de nivel inferior como Ibatis puede ser una mejor opción, ya que le dará el control que desea.

También he pensado cambiar dinámicamente o asignar en anotaciones en tiempo de ejecución.Aunque todavía no estoy seguro de si eso es posible, incluso si lo es, no estoy seguro de que sea necesariamente útil. No me puedo imaginar que un administrador de entidades o el almacenamiento en caché no se confundan irremediablemente con ese tipo de cosas que suceden.

La otra posibilidad en la que pensé fue crear subclases dinámicamente en tiempo de ejecución (como subclases anónimas) pero eso todavía tiene el problema de anotación y nuevamente no estoy seguro de cómo agregar eso a una unidad de persistencia existente.

Puede ser útil si proporciona más detalles sobre lo que está haciendo y por qué. Sin embargo, sea lo que sea, me inclino a pensar que necesitas replantearte lo que estás haciendo o cómo lo estás haciendo o si necesitas elegir una tecnología de persistencia diferente.

+0

Me gustaría permanecer dentro de JPA si es posible. –

+0

Pregunta 1: El esquema de la tabla es fijo, pero los nombres de la tabla no lo son. Esto significa que si se editaran A, B y C, todos compartirían el mismo esquema y serían identidades lógicamente separadas. Es decir. los artículos no se moverán de A a B o similar. En principio, podría aceptar leer páginas en un mapa por fila y trabajar con eso, pero estoy tratando de obtener la funcionalidad "hey, alguien más actualizó esta fila". –

+0

Voy a reflexionar un poco sobre cómo resolver esto de la mejor manera en función de los comentarios, y es posible que esto sea tecnológicamente excesivo. También tengo que considerar que esto probablemente se use durante mucho tiempo, por lo que no debería ser innecesariamente complejo para los futuros mantenedores. –

2

como alternativa de configuración XML, es posible que desee generar dinámicamente clases Java con anotación utilizando el marco de la manipulación de código de bytes preferido

1

Si no te importa la unión de su auto para hibernar, podría utilizar algunos de los métodos descritos al https://www.hibernate.org/171.html. Puede encontrar su auto utilizando bastantes anotaciones de hibernación dependiendo de la complejidad de sus datos, ya que van más allá de las especificaciones de JPA, por lo que puede ser un pequeño precio a pagar.

+0

Preferiría JPA, pero Hibernate también es una opción. Como se mencionó, el esquema es estático, pero necesitaré editar una o más tablas de la base de datos en una sola sesión, solo que difiera por el nombre de la tabla. –

8

Puede especificar el nombre de la tabla en el momento de la carga a través de un ClassLoader personalizado que vuelve a escribir la anotación @Table en las clases a medida que se cargan. Por el momento, no estoy 100% seguro de cómo se aseguraría de que Hibernate esté cargando sus clases a través de este ClassLoader.

Las clases se vuelven a escribir usando the ASM bytecode framework.

Advertencia: Estas clases son experimentales.

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

} 

El TableClassLoader se basa en la TableClassVisitor para atrapar el método visitAnnotation llama:

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

} 

El TableAnnotationVisitor es en última instancia responsable de cambiar el campo name del @Table anotación:

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

} 

Debido No encontré un AnnotationAdapter clase en la biblioteca de ASM, aquí es uno que hice yo:

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 genial! –

+2

Excelente, funciona con una pequeña cantidad de masaje, utilizándolo para administrar la anotación de DynamoDBTable. Ahora para ver si mi contenedor web permite el cargador de clases personalizado. – stjohnroe

4

Me suena como lo que está buscando es Overriding the JPA Annotations with an ORM.xml.

Esto le permitirá especificar las Anotaciones, pero las anulará solo cuando cambien. He hecho lo mismo para anular el schema en la anotación @Table, ya que cambia entre mis entornos.

Al usar este enfoque, también puede anular el nombre de la tabla en entidades individuales.

[Actualización de esta respuesta, ya que no está bien documentado y otra persona puede encontrar útil]

Aquí está mi ORM.archivo XML (tenga en cuenta que yo soy única reemplazando el esquema y dejando las otras anotaciones JPA & Hibernate solo, sin embargo el cambio de la mesa aquí es totalmente posible. También tenga en cuenta que estoy realizar anotaciones en el campo no es el 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

perfecto esto resolvió el problema para mí. ** Por cierto: ** tienes que pero orm.xml en META-INF en tu classpath –

Cuestiones relacionadas