¿Cómo implementar una tabla temporal usando JPA?

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

Un problema que estoy enfrentando es que las tablas de referencia ya no pueden tener restricciones de claves foráneas para las tablas referenciadas (tablas temporales) debido a la naturaleza de las tablas referenciadas, que ahora sus claves principales incluyen el período de validez.

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

Lo único que he encontrado es un marco llamadoDAO 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 dato:

Equip:

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


}

Jugado:

@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 de tiempo, por lo que lo que debe hacer es agregar un período de tiempo para cada mesa que desee rastrear. Entonces, agreguemos estas columnas temporales. Vamos a comenzar con soloPlayer.

Segundo escenario: modelo temporal

Modelo de datos

Como puede ver, tuvimos que soltar la clave principal y definir otra que incluya las fechas (punto). 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.

as 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 quePlayer table tiene queTeam. El problema es cómo modelaría eso en Java y JPA.

Note 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ó "de la misma entidad (durante la línea de tiempo).