query namedquery manytoone manager example ejemplos ejemplo create java jpa-2.0 eclipselink temporal-database

java - namedquery - jpql ejemplos



¿Cómo implementar una tabla temporal utilizando JPA? (4)

Me gustaría saber cómo implementar tablas temporales en JPA 2 con EclipseLink. Por temporal me refiero a tablas que definen el período de validez.

Un problema al que me enfrento es que las tablas de referencia ya no pueden tener restricciones de claves externas a las tablas de referencia (tablas temporales) debido a la naturaleza de las tablas de referencia, que ahora sus claves principales incluyen el período de validez.

  • ¿Cómo mapearía las relaciones de mis entidades?
  • ¿Significaría eso que mis entidades ya no pueden tener una relación con esas entidades de tiempo válido?
  • ¿Debería la responsabilidad de iniciar esas relaciones ahora hacerlas manualmente en algún tipo de Servicio o DAO especializado?

Lo único que he encontrado es un marco denominado DAO Fusion que se ocupa de esto.

  • ¿Hay alguna otra forma de resolver esto?
  • ¿Podría proporcionar un ejemplo o recursos sobre este tema (JPA con bases de datos temporales)?

Aquí hay un ejemplo ficticio de un modelo de datos y sus clases. Comienza como un modelo simple que no tiene que lidiar con aspectos temporales:

1er Escenario: Modelo No Temporal

Modelo de datos :

Equipo :

@Entity public class Team implements Serializable { private Long id; private String name; private Integer wins = 0; private Integer losses = 0; private Integer draws = 0; private List<Player> players = new ArrayList<Player>(); public Team() { } public Team(String name) { this.name = name; } @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQTEAMID") @SequenceGenerator(name="SEQTEAMID", sequenceName="SEQTEAMID", allocationSize=1) public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Column(unique=true, nullable=false) public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getWins() { return wins; } public void setWins(Integer wins) { this.wins = wins; } public Integer getLosses() { return losses; } public void setLosses(Integer losses) { this.losses = losses; } public Integer getDraws() { return draws; } public void setDraws(Integer draws) { this.draws = draws; } @OneToMany(mappedBy="team", cascade=CascadeType.ALL) public List<Player> getPlayers() { return players; } public void setPlayers(List<Player> players) { this.players = players; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Team other = (Team) obj; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } }

Jugador :

@Entity @Table(uniqueConstraints={@UniqueConstraint(columnNames={"team_id","number"})}) public class Player implements Serializable { private Long id; private Team team; private Integer number; private String name; public Player() { } public Player(Team team, Integer number) { this.team = team; this.number = number; } @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQPLAYERID") @SequenceGenerator(name="SEQPLAYERID", sequenceName="SEQPLAYERID", allocationSize=1) public Long getId() { return id; } public void setId(Long id) { this.id = id; } @ManyToOne @JoinColumn(nullable=false) public Team getTeam() { return team; } public void setTeam(Team team) { this.team = team; } @Column(nullable=false) public Integer getNumber() { return number; } public void setNumber(Integer number) { this.number = number; } @Column(unique=true, nullable=false) public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((number == null) ? 0 : number.hashCode()); result = prime * result + ((team == null) ? 0 : team.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Player other = (Player) obj; if (number == null) { if (other.number != null) return false; } else if (!number.equals(other.number)) return false; if (team == null) { if (other.team != null) return false; } else if (!team.equals(other.team)) return false; return true; } }

Clase de prueba

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration({"/META-INF/application-context-root.xml"}) @Transactional public class TestingDao { @PersistenceContext private EntityManager entityManager; private Team team; @Before public void setUp() { team = new Team(); team.setName("The Goods"); team.setLosses(0); team.setWins(0); team.setDraws(0); Player player = new Player(); player.setTeam(team); player.setNumber(1); player.setName("Alfredo"); team.getPlayers().add(player); player = new Player(); player.setTeam(team); player.setNumber(2); player.setName("Jorge"); team.getPlayers().add(player); entityManager.persist(team); entityManager.flush(); } @Test public void testPersistence() { String strQuery = "select t from Team t where t.name = :name"; TypedQuery<Team> query = entityManager.createQuery(strQuery, Team.class); query.setParameter("name", team.getName()); Team persistedTeam = query.getSingleResult(); assertEquals(2, persistedTeam.getPlayers().size()); //Change the player number Player p = null; for (Player player : persistedTeam.getPlayers()) { if (player.getName().equals("Alfredo")) { p = player; break; } } p.setNumber(10); } }

Ahora se le pide que mantenga un historial de cómo el Equipo y el Jugador estuvieron en cierto punto del tiempo, así que lo que debe hacer es agregar un período de tiempo para cada tabla que desea ser rastreada. Así que vamos a añadir estas columnas temporales. Vamos a empezar con solo Player .

2do Escenario: Modelo Temporal

Modelo de datos:

Como puede ver, tuvimos que eliminar la clave principal y definir otra que incluya las fechas (período). También tuvimos que eliminar las restricciones únicas porque ahora se pueden repetir en la tabla. Ahora la tabla puede contener las entradas actuales y también el historial.

Las cosas se ponen bastante feas si también tenemos que hacer que el Equipo sea temporal, en este caso tendríamos que eliminar la restricción de clave externa que la tabla de Player tiene para el Team . El problema es cómo modelarías eso en Java y JPA.

Tenga en cuenta que ID es una clave sustituta. Pero ahora las claves sustitutas tienen que incluir la fecha porque, si no lo hacen, no permitirían almacenar más de una " versión " de la misma entidad (durante la línea de tiempo).


En DAO Fusion , el seguimiento de una entidad en ambas líneas de tiempo (validez e intervalo de registro) se realiza envolviendo esa entidad mediante BitemporalWrapper .

La documentación de referencia bitemporal presenta un ejemplo con una entidad de Order normal envuelta por la entidad de orden BitemporalOrder . BitemporalOrder asigna a una tabla de base de datos separada, con columnas para la validez y el intervalo de registro, y una referencia de clave externa a Order (a través de @ManyToOne ), para cada fila de la tabla.

La documentación también indica que cada envoltorio bitemporal (p. Ej., BitemporalOrder ) representa un elemento dentro de la cadena de registro bitemporal . Por lo tanto, necesita una entidad de nivel superior que contenga una recopilación de envoltorio bitemporal, por ejemplo, una entidad del Customer que contenga @OneToMany Collection<BitemporalOrder> orders .

Por lo tanto, si necesita una entidad "lógica infantil" (por ejemplo, Order o Player ) para ser rastreada bitemporalmente, y su entidad "lógica primaria" (por ejemplo, Customer o Team ) también debe ser rastreada bitemporalmente, debe proporcionar envolturas bitemporales para ambos . Tendrás BitemporalPlayer y BitemporalTeam . BitemporalTeam puede declarar @OneToMany Collection<BitemporalPlayer> players . Pero necesita una entidad de nivel superior para contener los @OneToMany Collection<BitemporalTeam> teams , como se mencionó anteriormente. Por ejemplo, podría crear una entidad de Game que contenga la colección BitemporalTeam .

Sin embargo, si no necesita un intervalo de registro y solo necesita un intervalo de validez (por ejemplo, no un seguimiento temporal de las entidades, sino un seguimiento temporal temporal), lo mejor es lanzar su propia implementación personalizada.


Estoy muy interesado en este tema. Estoy trabajando desde hace varios años en el desarrollo de aplicaciones que utilizan estos patrones, la idea surgió en nuestro caso de una tesis de diploma alemana.

No conocía los marcos "DAO Fusion", proporcionan información interesante y enlaces, gracias por proporcionar esta información. ¡Especialmente la página de patrones y la página de aspectos son geniales!

A sus preguntas: no, no puedo señalar otros sitios, ejemplos o marcos. Me temo que tiene que usar el marco DAO Fusion o implementar esta funcionalidad por su cuenta. Tienes que distinguir qué tipo de funcionalidad realmente necesitas. Para hablar en términos del marco "DAO Fusion": ¿necesita tanto "tiempo temporal válido" como "registro temporal"? Registre los estados temporales cuando el cambio se aplicó a su base de datos (generalmente se usa para auditar problemas), los estados temporales válidos cuando el cambio ocurrió en la vida real o si es válido en la vida real (usado por la aplicación) que puede diferir del registro temporal. En la mayoría de los casos, una dimensión es suficiente y la segunda dimensión no es necesaria.

De todos modos, la funcionalidad temporal tiene impactos en su base de datos. Como dijiste: "que ahora sus claves primarias incluyen el período de validez" . Entonces, ¿cómo modelas la identidad de una entidad? Prefiero el uso de en.wikipedia.org/wiki/Surrogate_key . En ese caso esto significa:

  • un id para la entidad
  • un id para el objeto en la base de datos (la fila)
  • las columnas temporales

La clave principal para la tabla es la identificación del objeto. Cada entidad tiene una o más entradas (1-n) en una tabla, identificadas por la identificación del objeto. La vinculación entre tablas se basa en la identificación de la entidad. Dado que las entradas temporales multiplican la cantidad de datos, las relaciones estándar no funcionan. Una relación estándar 1-n puede convertirse en relación ax * 1-y * n.

¿Cómo resuelves esto? El enfoque estándar sería introducir una tabla de mapeo, pero este no es un enfoque natural. Solo para editar una tabla (por ejemplo, se produce un cambio de residencia) también tendría que actualizar / insertar la tabla de asignación, lo cual es extraño para todos los programadores.

El otro enfoque sería no usar una tabla de mapeo. En este caso, no puede utilizar la integridad referencial y las claves externas, cada tabla actúa de forma aislada, el enlace de una tabla a la otra debe implementarse de forma manual y no con la funcionalidad JPA.

La funcionalidad de inicialización de los objetos de la base de datos debe estar dentro de los objetos (como en el marco DAO Fusion). No lo pondría en un servicio. Si lo asignas a un DAO o usas un patrón de registro activo, depende de ti.

Soy consciente de que mi respuesta no le proporciona un marco "listo para usar". Se encuentra en un área muy complicada, desde mi experiencia, los recursos hasta este escenario de uso son muy difíciles de encontrar. Gracias por tu pregunta! Pero de todos modos espero haberte ayudado en tu diseño.

En esta respuesta, encontrará el libro de referencia "Desarrollo de aplicaciones de base de datos orientadas en el tiempo en SQL", consulte https://.com/a/800516/734687

Actualización: Ejemplo

  • Pregunta: Digamos que tengo una tabla de PERSON que tiene una clave sustituta que es un campo llamado "id". Cada tabla de referencia en este punto tendrá esa "ID" como una restricción de clave externa. Si agrego columnas temporales ahora tengo que cambiar la clave principal a "id + from_date + to_date". Antes de cambiar la clave principal, primero tendría que eliminar todas las restricciones externas de cada tabla de referencia a la tabla a la que se hace referencia (Persona). Estoy en lo cierto Creo que eso es lo que quieres decir con la clave sustituta. ID es una clave generada que podría ser generada por una secuencia. La clave de negocio de la tabla de persona es el SSN.
  • Respuesta: No exactamente. El SSN sería una clave natural, que no utilizo para la identidad de objcet. También "id + from_date + to_date" sería una clave compuesta , que también evitaría. Si observa el example , tendrá dos tablas, persona y residencia, y para nuestro ejemplo, digamos que tenemos una relación 1-n con una residencia de clave extranjera. Ahora estamos agregando campos temporales en cada tabla. Sí, eliminamos todas las restricciones de clave externa. La persona obtendrá 2 ID, una ID para identificar la fila (llámela ROW_ID), una ID para identificar a la persona misma (llámela ENTIDY_ID) con un índice en esa ID. Lo mismo para la persona. Por supuesto, su enfoque también funcionaría, pero en ese caso tendría operaciones que cambian el ROW_ID (cuando cierra un intervalo de tiempo), lo que evitaría.

Para ampliar el example implementado con los supuestos anteriores (2 tablas, 1-n):

  • una consulta para mostrar todas las entradas en la base de datos (toda la información de validez y registro, también conocida como información técnica incluida):

    SELECT * FROM Person p, Residence r WHERE p.ENTITY_ID = r.FK_ENTITY_ID_PERSON // JOIN

  • una consulta para ocultar el registro, también conocido como información técnica. Esto muestra todos los cambios de validez de las entidades.

    SELECT * FROM Person p, Residence r WHERE p.ENTITY_ID = r.FK_ENTITY_ID_PERSON AND p.recordTo=[infinity] and r.recordTo=[infinity] // only current technical state

  • una consulta para mostrar los valores reales.

    SELECT * FROM Person p, Residence r WHERE p.ENTITY_ID = r.FK_ENTITY_ID_PERSON AND p.recordTo=[infinity] and r.recordTo=[infinity] AND p.validFrom <= [now] AND p.validTo > [now] AND // only current valid state person r.validFrom <= [now] AND r.validTo > [now] // only current valid state residence

Como puedes ver, nunca uso el ROW_ID. Reemplace [ahora] con una marca de tiempo para retroceder en el tiempo.

Actualiza para reflejar tu actualización
Recomendaría el siguiente modelo de datos:

Presentar una tabla "PlaysInTeam":

  • CARNÉ DE IDENTIDAD
  • Equipo de ID (clave extranjera para el equipo)
  • ID Player (clave extranjera para jugador)
  • Válida desde
  • Válido hasta

Cuando enumera a los jugadores de un equipo, debe consultar la fecha para la cual la relación es válida y debe estar en [ValdFrom, ValidTo)

Para hacer el equipo temporal tengo dos enfoques;

Enfoque 1: Presente una tabla de "Temporada" que modela una validez para una temporada

  • CARNÉ DE IDENTIDAD
  • Nombre de la temporada (ej. Verano 2011)
  • De (tal vez no sea necesario, porque todos saben cuándo es la temporada)
  • Para (tal vez no sea necesario, porque todos saben cuándo es la temporada)

Dividir la mesa del equipo. Tendrá campos que pertenecen al equipo y que no son relevantes para el tiempo (nombre, dirección, ...) y campos que son relevantes para una temporada (ganar, perder, ...). En ese caso usaría Team y TeamInSeason. PlaysInTeam podría vincularse a TeamInSeason en lugar de Team (tiene que ser considerado, dejaría que apunte a Team)

Temporada de equipo

  • CARNÉ DE IDENTIDAD
  • Equipo de identificación
  • Temporada de identificación
  • Ganar
  • Pérdida
  • ...

Enfoque 2: No modele la temporada explícitamente. Dividir la mesa del equipo. Tendrá campos que pertenecen al equipo y que no son relevantes para el tiempo (nombre, dirección, ...) y campos que son relevantes para el tiempo (ganar, perder, ...). En ese caso yo usaría Team y TeamInterval. TeamInterval tendría campos "desde" y "hasta" para el intervalo. PlaysInTeam podría enlazar con TeamInterval en lugar de Team (lo dejaría en Team)

TeamInterval

  • CARNÉ DE IDENTIDAD
  • Equipo de identificación
  • Desde
  • A
  • Ganar
  • Pérdida
  • ...

En ambos enfoques: si no necesita una tabla de equipo separada para ningún campo relevante de tiempo, no divida.


No sé exactamente a qué se refiere, pero EclipseLink tiene soporte completo para el historial. Puede habilitar una HistoryPolicy en un ClassDescriptor a través de un @DescriptorCustomizer.


Parece que no puede hacerlo con JPA ya que asume que el nombre de la tabla y todo el esquema es estático.

La mejor opción podría ser hacerlo a través de JDBC (por ejemplo, utilizando el patrón DAO)

Si el problema es el rendimiento, a menos que hablemos de decenas de millones de registros, dudo que crear clases y compilarlas dinámicamente y luego cargarlas sea mejor.

Otra opción podría ser usar vistas (si debe usar JPA) puede ser de alguna manera abstraer la tabla (mapear @Entity (name = "myView"), entonces tendría que actualizar / reemplazar la vista dinámicamente como en CREAR O REEMPLAZAR VER nombre de usuario VER COMO SELECCIONAR * DESDE prefix_sessionId

por ejemplo, podría escribir una vista para decir:

if (EVENT_TYPE = ''crear_tabla'' AND ObjectType = ''tabla '' && ObjectName starts with ''userName'') then CREATE OR REPLACE VIEW userNameView AS SELECT * FROM ObjectName //the generated table.

Espero que esto ayude (espero que te ayude)