如何使用JPA实现时态表?

2024-01-29

我想知道如何实施时态表 http://en.wikipedia.org/wiki/Temporal_database在带有 EclipseLink 的 JPA 2 中。我所说的时间是指定义有效期的表。

我面临的一个问题是,由于引用表的性质(现在它们的主键包括有效期),引用表不能再对引用表(临时表)具有外键约束。

  • 我将如何映射我的实体的关系?
  • 这是否意味着我的实体不再与那些有效时间实体有关系?
  • 现在初始化这些关系的责任应该由我在某种服务或专门的 DAO 中手动完成吗?

我唯一发现的是一个名为道融合 http://opensource.anasoft.com/daofusion-site/index.html这涉及到这个问题。

  • 还有其他方法可以解决这个问题吗?
  • 您能否提供有关此主题的示例或资源(带有时态数据库的 JPA)?

这是数据模型及其类的虚构示例。它从一个简单的模型开始,不必处理时间方面的问题:

第一种情况:非时间模型

Data Model: Non Temporal Data Model

Team:

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


}

Player:

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


}

测试类:

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


}

现在,您需要保留团队和玩家在特定时间点的历史记录,因此您需要做的是为每个要跟踪的桌子添加一段时间。因此,让我们添加这些时间列。我们将从Player.

场景二:时间模型

Data Model: Temporal Data Model

正如您所看到的,我们必须删除主键并定义另一个包含日期(句点)的主键。此外,我们还必须删除唯一约束,因为现在它们可以在表中重复。现在该表可以包含当前条目以及历史记录。

如果我们还必须使团队临时化,事情会变得非常丑陋,在这种情况下,我们需要放弃外键约束Player表必须Team。问题是如何在 Java 和 JPA 中对其进行建模。

请注意,ID 是代理键。但现在代理键必须包含日期,因为如果不包含日期,就不允许存储多个日期”version”同一实体(在时间线期间)。


我对这个话题很感兴趣。我已经工作了几年,致力于开发使用这些模式的应用程序,这个想法来自于我们的一篇德国文凭论文。

我不知道“DAO Fusion”框架,它们提供了有趣的信息和链接,感谢您提供这些信息。尤其是图案页 http://opensource.anasoft.com/daofusion-site/reference/bitemporal-pattern.html方面页面 http://opensource.anasoft.com/daofusion-site/technote/temporal-aspects.html太棒了!

对于你的问题:不,我不能指出其他网站、示例或框架。恐怕您必须使用 DAO Fusion 框架或自己实现此功能。您必须区分您真正需要哪种功能。从“DAO Fusion”框架来看:“有效时间”和“记录时间”是否都需要?记录更改应用于数据库时的时间状态(通常用于审核问题)、更改在现实生活中发生或在现实生活中有效(由应用程序使用)时的有效时间状态,这可能与记录时间不同。在大多数情况下,一维就足够了,不需要第二维。

无论如何,时间功能会对您的数据库产生影响。正如你所说:“现在他们的主键包括有效期”。那么如何对实体的身份进行建模呢?我更喜欢使用代理键 http://en.wikipedia.org/wiki/Surrogate_key。在这种情况下,这意味着:

  • 实体的一个 id
  • 数据库中对象的一个​​ id(行)
  • 时间列

表的主键是对象 ID。每个实体在表中都有一个或多个 (1-n) 条目,由对象 id 标识。表之间的链接基于实体 ID。由于时间条目会增加数据量,因此标准关系不起作用。标准的 1-n 关系可能会变成 x*1-y*n 关系。

你如何解决这个问题?标准方法是引入映射表,但这不是一种自然的方法。仅为了编辑一张表(例如,发生居住地变更),您还必须更新/插入映射表,这对于每个程序员来说都是陌生的。

另一种方法是不使用映射表。在这种情况下,您不能使用引用完整性和外键,每个表都是独立运行的,从一个表到其他表的链接必须手动实现,而不是使用 JPA 功能。

初始化数据库对象的功能应该在对象内(如在 DAO Fusion 框架中)。我不会把它放在服务中。是否将其放入 DAO 或使用 Active Record 模式取决于您。

我知道我的答案并没有为您提供“随时可用”的框架。你处在一个非常复杂的领域,从我的经验资源到这个使用场景都很难找到。谢谢你的提问!但无论如何,我希望对您的设计有所帮助。

在这个答案中,您将找到参考书“用 SQL 开发面向时间的数据库应用程序”,请参阅https://stackoverflow.com/a/800516/734687 https://stackoverflow.com/a/800516/734687

更新:示例

  • 问题:假设我有一个 PERSON 表,它有一个代理键,它是一个名为“id”的字段。此时每个引用表都将具有该“ID”作为外键约束。如果我现在添加临时列,我必须将主键更改为“id+from_date+to_date”。在更改主键之前,我必须首先将每个引用表的每个外来约束删除到该引用表(Person)。我对吗?我相信这就是你所说的代理键的意思。 ID 是可以由序列生成的生成密钥。 Person表的业务键是SSN。
  • 答:不完全是。 SSN 将是一个自然密钥,我不将其用于 objcet 身份。另外“id+from_date+to_date”将是复合键 http://en.wikipedia.org/wiki/Composite_key,我也会避免。如果你看一下example http://opensource.anasoft.com/daofusion-site/technote/temporal-aspects.html#Bitemporal_example您将有两个表:人员和住所,对于我们的示例,假设我们与外键住所具有 1-n 关系。 现在我们在每个表上添加时间字段。是的,我们放弃了每个外键约束。 Person 将获得 2 个 ID,一个用于标识行的 ID(称为 ROW_ID),一个用于标识人员本身的 ID(称为 ENTIDY_ID)以及该 id 上的索引。对人来说也是如此。当然,您的方法也可以工作,但在这种情况下,您将进行更改 ROW_ID 的操作(当您关闭时间间隔时),我会避免这种情况。

为了延长example http://opensource.anasoft.com/daofusion-site/technote/temporal-aspects.html#Bitemporal_example根据上述假设实施(2 个表,1-n):

  • 显示数据库中所有条目的查询(包括所有有效性信息和记录 - 又名技术 - 信息):

    SELECT * FROM Person p, Residence r
    WHERE p.ENTITY_ID = r.FK_ENTITY_ID_PERSON          // JOIN 
  • 隐藏记录(又名技术信息)的查询。这显示了实体的所有有效更改。

    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
  • 显示实际值的查询。

    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

如您所见,我从不使用 ROW_ID。将 [now] 替换为时间戳以返回过去。

更新以反映您的更新
我推荐以下数据模型:

引入一个“PlaysInTeam”表:

  • ID
  • ID Team(团队的外键)
  • ID Player(播放器的外键)
  • 有效日期
  • ValidTo

当您列出球队的球员时,您必须查询关系有效的日期,并且必须位于 [ValidFrom, ValidTo)

为了让团队变得临时化,我有两种方法;

方法一: 引入一个“季节”表,该表对季节的有效性进行建模

  • ID
  • 季节名称(例如 Summer 2011)
  • 从(也许没有必要,因为每个人都知道季节是什么时候)
  • 到(也许没有必要,因为每个人都知道季节是什么时候)

拆分团队表。您将拥有属于球队且与时间无关的字段(姓名、地址……)以及与赛季时间相关的字段(胜利、失败……)。在这种情况下,我会使用 Team 和 TeamInSeason。 PlaysInTeam 可以链接到 TeamInSeason 而不是 Team(必须考虑 - 我会让它指向 Team)

赛季球队

  • ID
  • ID Team
  • 身份季节
  • Win
  • Loss
  • ...

方法2: 不要明确地模拟季节。拆分团队表。您将拥有属于团队且与时间无关的字段(姓名、地址等)以及与时间相关的字段(获胜、失败等)。在这种情况下,我会使用 Team 和 TeamInterval。 TeamInterval 将具有表示间隔的字段“from”和“to”。 PlaysInTeam 可以链接到 TeamInterval 而不是 Team(我会将其链接到 Team)

团队间隔

  • ID
  • ID Team
  • From
  • To
  • Win
  • Loss
  • ...

在这两种方法中:如果您不需要为没有时间相关字段的单独团队表,请不要拆分。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用JPA实现时态表? 的相关文章

随机推荐

  • 如何使用 firebase 模拟器设置 firebase firestore 和云功能测试套件进行 JS 开发

    根据 Firebase 团队的以下 google I O 2019 帖子 新的模拟器允许我们结合 Firebase 数据库加上云功能来完全模拟我们的 Firebase 服务器代码 这也意味着我们应该能够为其编写测试 我们正在发布一个全新的
  • Azure 持续部署错误无法加载文件或程序集 Microsoft.WindowsAzure.Packaging

    当我尝试将项目部署到云服务时 使用持续部署和托管构建控制器 我突然开始收到此错误 C Program Files x86 MSBuild Microsoft VisualStudio v14 0 Windows Azure Tools 2
  • 如何使用 Hydra-fb 将配置文件收集到列表中?

    假设我有一个抽象类db在我的代码和类中db1 db1 db1继承自db 我的项目使用hydra https hydra cc 并具有以下结构 my app py conf yaml db db1 yaml db2 yaml db3 yaml
  • 将图像从phonegap应用程序上传到WCF服务

    我正在尝试使用phonegap文件传输API将图像从我的手机上传到WCF服务器 以下是我的代码 但我无法将图像上传到服务器 请给一些建议 测试 html div div div div
  • 使用 ZingChart 的多维堆叠条形图

    我正在尝试使用 ZingChart 创建多维堆叠条形图 据我所知 这是 由于某种原因 图像没有显示 它的链接是 https drive google com file d 0B14IyWv9zwZ9a0hWR0lXTDZQXzQ view
  • 如何创建像球拍一样的 make-curry 函数

    我想看看如何模仿 curry func that racket提供 下面是我如何手动柯里化函数的示例 lang sicp convert to a curried function define add1 x y x y define ad
  • 为什么background-filter:blur() 不能正常工作?

    我正在尝试实现这个图像 其中 带有文本 Dog 的 div 被部分覆盖并且blurring图片 所以我尝试了这个 profile background image url https townofbeekmantown com wp con
  • 如何将Dockerfile和应用程序文件部署到boot2docker

    我刚刚开始学习 Docker 我正在尝试在 Windows 上使用 boot2docker 进行简单的设置 我无法在任何在线教程或示例中找到如何使 boot2docker VM 可以访问我的项目文件 我下载了 boot2docker 并成功
  • jQuery 和其他库

    我在 jQuery 方面遇到了这个问题 并且不知道如何解决它 任何人都可以帮助或提供一些帮助 异常 组件返回失败代码 0x80070057 NS ERROR ILLEGAL VALUE nsIDOMXPathEvaluator evalua
  • 使用 ng-controller 时延迟模板加载

    我试图推迟加载我的主控制器 模板 AppController 直到我从服务加载用户配置文件 对于所有导航路线 我使用 routeProvider 进行解析 when edit editId templateUrl editTemplate
  • 在 ggplot2 中的一系列点周围放置边界

    我想在一系列点周围设置边界 我该怎么办 These are my points 我尝试了 geom line 但这显然是错误的 因为它产生了这个 Thanks Use geom path代替geom line 这是一个例子 i lt seq
  • 从旧的init_timer到新的timer_setup的适应

    我一直在尝试将驱动程序从 2 6 移植到 4 X 而没有原始板制造商的支持 并且 Linux 经验非常有限 原来的驱动使用的是init timer 并传入一个指向timer list结构 那timer list结构的data元素被设置为指向
  • WPF DragDrop.DoDragDrop(用于右键单击?)

    在 WPF 中 我想使用右键单击来启动拖放 像 Windows 资源管理器一样 右键单击 稍微移动 您就会得到装饰器来指示操作中的拖放 不要移动 当您松开按钮时 您会看到上下文菜单 我已经启动了这一切 但是当我调用 DragDrop DoD
  • 自然对齐的内存地址

    我需要从现有的64位值中提取一个内存地址 这个地址指向一个4K数组 起始值为 0x000000030c486000 我需要的地址存储在位 51 12 中 因此我使用以下方法提取这些位 address start gt gt 12 0x000
  • 如何从在线检查器中隐藏 codeigniter 足迹区域?

    这可以删除吗codeigniter来自在线工具的足迹区域 例如wappalyzer com or builtwith com并使 敏感项目技术无法检测 是的 这是可能的 首先 您需要知道它是如何被检测到的 看一下 Wappalyzer 代码
  • 如何使用 C# 'foreach' DataTable 中的列?

    如何使用循环遍历数据行中的每一列foreach DataTable dtTable new DataTable MySQLProcessor DTTable mysqlCommand out dtTable foreach DataRow
  • Node.js 和 Tornado 之间的差异[关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 除了node js是用JS编写而Tornado是用Python编写之外 两者之间还有哪些区别 它们都是非阻塞异步 Web 服务器 对吧 除了语言
  • asp.net 中的文件处理程序

    我需要跟踪 pdf 在我的网络应用程序中打开的时间 现在 当用户单击链接时 我正在写入数据库 然后从后面的代码中使用 window open 这并不理想 因为 Safari 会阻止弹出窗口 而其他网络浏览器在运行时会发出警告 所以我想Fil
  • 继续 Jenkins 管道过去失败的阶段

    我有一系列执行快速检查的阶段 我想把它们全部完成 即使有失败 例如 stage one node sh exit 0 stage two node sh exit 1 failure stage three node sh exit 0 S
  • 如何使用JPA实现时态表?

    我想知道如何实施时态表 http en wikipedia org wiki Temporal database在带有 EclipseLink 的 JPA 2 中 我所说的时间是指定义有效期的表 我面临的一个问题是 由于引用表的性质 现在它