2011-03-24 13 views
5

Imagine las siguientes dos entidades. Element es la clase simple que contiene algunos datos:JPA - entidad "versionada", se necesita asesoramiento de diseño

@Entity 
public class Element { 
    private String data; 

    public String getData() { return data; }  
    public void setData(String data) { this.data = data; } 
} 

La siguiente clase, llamada VersionedElement, se extiende Element y contiene diferentes versiones a lo largo de la versión actual. Aquí está mi "solución":

@Entity 
public class VersionedElement extends Element { 
    private Set<Element> versions; 
    private Element currentVersion; 

    @Override 
    public String getData() { 
     return getCurrentVersion().getData(); 
    } 

    @Override 
    public void setData(String data) { 
     getCurrentVersion().setData(data); 
    } 

    @OneToMany 
    public Set<Element> getVersions() { 
     return versions; 
    } 

    public void setVersions(Set<Element> versions) { 
     this.versions = versions; 
    } 

    @ManyToOne 
    public Element getCurrentVersion() { 
     return currentVersion; 
    } 

    public void setCurrentVersion(Element currentVersion) { 
     this.currentVersion = currentVersion; 
    } 
} 

Y no me gusta lo que he escrito, algo malo en ello, el enfoque demasiado sencillo. En primer lugar, en la última clase currentVersion no está limitado por y no tiene relación con versions. Parece que el código carece de algunas clases de ayuda, o nivel de abstracción, o técnica de anotación JPA, o todo lo anterior. Necesito una solución manual elegante, digna de JPA para este caso simple. Cualquier sugerencia, enlace o fragmento de código sería apreciado.

+0

¿no es suVersion actual la "presente" instancia? – ThomasRS

+0

@Thomas, muy buena pregunta, ¿qué pasa con los ids en este caso, especialmente al cambiar la versión actual? No lo sé. Tal vez las clases necesitan ser reescritas, los datos tomados como clases separadas, etc. Pero diferentes datos deben tener diferentes identificadores, eso es seguro. Estoy un poco confundido, esperaba "enterrar tu código, hacer eso y eso, así es como solía hacerse, es un patrón común". No puedo creer que nadie haya hecho eso ya. – Osw

Respuesta

13

si desea una solución de control de versiones de hibernación lista para trabajar, pruebe hibernate-envers. Hará que la versión/auditoría de objetos sea muy fácil para usted. Consulte la documentación en http://docs.jboss.org/envers/docs/index.html

¡Salud y buena suerte!

+0

Por cierto, consulte la página de introducción de jboss envers en http://www.jboss.org/envers para una vista rápida de la solución –

+0

sí, existe y es tan increíble que es una parte interna del núcleo de hibernación (desde 3.5 en);) –

+0

+100 para el enlace! un poco fuera del alcance pero ahorrará mucho tiempo en mis otros proyectos. – Osw

2

Su solución funcionaría, pero tener otra tabla para VersionedElement sería una sobrecarga de rendimiento: VersionedElement no tendría datos útiles, excepto algunas columnas de clave externa.

Lo que me gustaría hacer es simplemente añadir Elemento última como campo para Elemento clase. Luego, en el DAO, me gustaría añadir algunos métodos que realizan consultas basadas en este campo:

List<Element> getHistory(Element element)... 
Element getLatest(Element element)... 

APP también es compatible con la anotación @Version, sino que se utiliza para el control de concurrencia optimista. Sin embargo, podría usarse para rastrear números de versión.

+0

Gracias por una respuesta. Así es como lo hago ahora, está funcionando.Por supuesto, el diseño ideal que estoy pidiendo tendrá un impacto en el rendimiento de Hibernate, pero espero que se compense de alguna manera desde el lado sql: menos filas involucradas, consultas más optimizadas, etc. Y, al menos, no tengo ningún otro diseño para compararlos. – Osw

-1

Puede mantener una referencia a la última entidad en una tabla separada (con una fila). Algo así como:

@Entity 
public class CurrentElement { 

    @OneToOne   
    private Element currentVersion; 

    public static Element getCurrentVersion(EntityManager em) { 
     return em.createQuery("select x from Element x ").getSingleResult().currentVersion; 
    } 

    public static void setCurrentVersion(EntityManager em, Element newVersion) { 
     em.remove(getCurrentVersion(em)); 
     CurrentElement ce = new CurrentElement(); 
     ce.currentVersion = newVersion; 
     em.persist(ce); 
    } 
} 
0

No hay nada malo con su solución, pero es posible que desee utilizar entity listeners para garantizar el estado de sus entidades de una manera más elegante, supongo que con @PrePersist y @preUpdate oyentes. O alternativamente busque una lista en lugar de un conjunto.

4

Element puede tener un campo entero version en el objeto Element sí, actuando como una cuenta corriente de filas, y es actualizado por una secuencia. Cuando desee lo último, simplemente necesita ordenar por este campo en orden descendente y obtener el primer resultado.

@Entity 
@NamedQueries({ 
    @NamedQuery(name="GetHistory", query = "FROM Element e WHERE e.id = :id"), 
    @NamedQuery(name="GetLatest", query = "FROM Element e \ 
             WHERE e.id = :id order by e.version"), 
}) 
public class Element { 
    private String data; 

    @GeneratedValue(strategy = GenerationType.SEQUENCE, 
        generator = "SEQ_ELEMENT_VERSION") 
    private int version; 
    private int id; 


    public String getData() { return data; }  
    public void setData(String data) { this.data = data; } 
} 
2

Bueno, vi tu comentario sobre la respuesta de @Ioan Alexandru Cucu

espero que será compensado de alguna manera de un lado sql - menos filas involucrados, consulta más optimizado

Según la asignación que se muestra en su pregunta, si necesita recuperar una entidad VersionedElement completamente inicializada, debe realizar una que ria como éste

from 
    VersionedElement v 
inner join fetch 
    v.versions 
inner join fetch 
    v.currentVersion 
where 
    v.id = :id 

Como se puede ver que necesita dos combinaciones para recuperar su entidad VersionedElement. Pero Element así como VersionedElement comparten la propiedad de datos. Para evitar la duplicación de código, podemos definir una clase abstracta que contiene los datos necesarios en ambas entidades, de la siguiente manera

@MappedSuperclass 
public abstract class AbstractElement { 

    private String data; 

    public String getData() { return data; } 
    public void setData(String data) { this.data = data; } 

} 

La especificación JPA 1.0 es sencillo

Ambas clases abstractas y concretas pueden ser entidades . Las entidades pueden extender las clases sin entidad, así como las clases de entidad, y las clases que no sean entidades pueden extender las clases de entidad.

Necesitamos anotación @MappedSuperclass porque su información de mapeo debe aplicarse a las entidades que heredan de que. En nuestro caso, Element y VersionedElement.

lo tanto, podemos volver a escribir la entidad de elementos como

@Entity 
public class Element extends AbstractElement {} 

Y para evitar unirse a la interior se ha podido recuperar v.currentVersion, ¿por qué no almacenar los datos suministrados por AbstractElement como una propiedad en lugar de @Embedded una propiedad @ManyToOne?

@Embeddable 
public class ElementAsEmbeddable extends AbstractElement {} 

@Entity 
public class VersionedElement { 

    private ElementAsEmbeddable currentElement; 

    private Set<Element> versions; 

    @Embedded 
    public ElementAsEmbeddable getCurrentVersion() { return currentVersion; } 
    public void setCurrentVersion(ElementAsEmbeddable currentVersion) { this.currentVersion = currentVersion; } 

    @OneToMany 
    public Set<Element> getVersions() { return versions; } 
    public void setVersions(Set<Element> versions) { this.versions = versions; } 

} 

Ahora su consulta debería parecerse

from 
    VersionedElement v 
inner join fetch 
    v.versions 
where 
    v.id = :id 

Sólo uno unen

Para configurar la propiedad currentVersion de un elemento, sólo tiene que echar elemento como una AbstractElement

versionedElement.setCurrentVersion((AbstractElement) element); 
+0

Parece muy interesante. Pero, pensando de manera similar, ¿qué nos impide convertir la clase de datos en una verdadera @Entity en lugar de @MappedSuperClass? – Osw

+0

@Osw @Embeddable - necesario para evitar dos uniones - no se puede extender una entidad porque @Embeddable en sí ** no es una entidad simple **. –

+0

Entiendo su punto con respecto a la optimización de sql, pero, aparte de eso, ¿cuál es su opinión sobre cómo convertirlo en una entidad independiente? Puede parecer razonable y cercano a la vida real: datos diferentes -> entidades diferentes -> identificaciones diferentes. – Osw

Cuestiones relacionadas