(与版本控制数据库架构无关)
与数据库交互的应用程序通常具有由许多表中的数据组成的领域对象。假设该应用程序支持对这些领域对象进行版本控制(类似于CVS)。
对于某个任意的领域对象,您将如何设计数据库模式以处理此要求?是否有任何经验可以分享?
(与版本控制数据库架构无关)
与数据库交互的应用程序通常具有由许多表中的数据组成的领域对象。假设该应用程序支持对这些领域对象进行版本控制(类似于CVS)。
对于某个任意的领域对象,您将如何设计数据库模式以处理此要求?是否有任何经验可以分享?
过去我用过的一种技术是在数据库中有一个“世代”的概念,每次更改都会使数据库的当前世代号增加——如果你使用Subversion,请考虑版本。 每个记录都有两个与其关联的世代号(表中的2个额外列)-记录开始有效的世代和停止有效的世代。 如果数据当前有效,则第二个数字将为NULL或其他通用标记。
因此,要插入到数据库中:
如果您要更新某些数据:
删除仅涉及将数据标记为在当前世代结束。
要获取特定版本的数据,请找到您想要的世代,并查找在那些世代版本之间有效的数据。
例子:
创建一个人。
|Name|D.O.B |Telephone|From|To |
|Fred|1 april|555-29384|1 |NULL|
更新电话号码。
|Name|D.O.B |Telephone|From|To |
|Fred|1 april|555-29384|1 |1 |
|Fred|1 april|555-43534|2 |NULL|
删除Fred:
|Name|D.O.B |Telephone|From|To |
|Fred|1 april|555-29384|1 |1 |
|Fred|1 april|555-43534|2 |2 |
严格版本控制的替代方案是将数据分为两个表:当前表和历史表。
当前表包含所有实时数据,并具有您构建的所有性能优势。任何更改都会首先将当前数据写入相关的“历史”表中,并附带一个日期标记,以指示更改时间。
你需要在一个主表中拥有一个主记录,其中包含所有版本共同的信息。
然后每个子表使用主记录ID + 版本号作为主键的一部分。
虽然可以不使用主表来完成,但根据我的经验,这往往会使SQL语句变得更加混乱。
一旦对象保存在数据库中,我们可以随意修改该对象的任意次数。如果我们想知道对象被修改的次数,则需要应用版本控制概念。
每当我们使用版本控制时,当对象第一次保存在数据库中时,Hibernate将版本号插入为零。稍后,当对该特定对象进行修改时,Hibernate会自动将版本号递增一次。 为了使用此版本控制概念,我们需要在应用程序中进行以下两个更改。
Add one property of type int in our pojo class.
In hibernate mapping file, add an element called version soon after id element
我不确定我们是否有相同的问题,但我需要对当前数据集进行大量“建议性”更改(包括链式建议,即建议上的建议)。
就像源代码控制中的分支一样,但是针对数据库表格。
我们还想要一个历史日志,但这是最不重要的因素——主要问题是管理变更建议,这些建议可能会挂起6个月或更长时间,因为业务部门正在考虑变更批准并准备实施实际变更。
这个想法是用户可以加载一个变更并开始创建、编辑、删除当前状态的数据,而不必实际应用这些变更。撤消他们可能做出的任何更改,或取消整个变更。
我能够实现这一点的唯一方法是在我的版本化表格上设置一组公共字段:
根ID:必填——当记录的第一个版本创建时,设置为主键。这代表所有时间的主键,并复制到记录的每个版本中。在命名关系列时,您应该考虑根ID(例如,PARENT_ROOT_ID而不是PARENT_ID)。由于根ID也是初始版本的主键,因此可以根据实际的主键创建外键——实际所需的行将由下面定义的版本过滤器确定。
变更ID:必填项 - 每个记录都通过变更来创建、更新、删除
复制自ID:可空 - null表示新创建的记录,非null表示在更新时从哪条记录ID克隆了该行
生效日期/时间:可空 - null表示建议的记录,非null表示记录何时变为当前记录。不幸的是,无法在根ID /生效时间上放置唯一索引,因为任何根ID都可以有多个空值(除非您想限制每个记录只有一个建议更改)
失效日期/时间:可空 - null表示当前/建议,非null表示何时成为历史性的。虽然从技术上讲它不是必需的,但能帮助加速查询查找当前数据。如果手动编辑此字段,可能会破坏它,但可以从“生效日期/时间”重建。
删除标记:布尔型 - 当建议将记录删除后将其设置为true以成为当前记录。当提交删除时,他们的" 失效日期/时间"被设置为与" 生效日期/时间"相同的值,使它们过滤出当前数据集。
按照变更获取数据的当前状态的查询应为;
SELECT * FROM table WHERE (CHANGE_ID IN :ChangeId OR (EFFECTIVE_FROM <= :Now AND (EFFECTIVE_TO IS NULL OR EFFECTIVE_TO > :Now) AND ROOT_ID NOT IN (SELECT ROOT_ID FROM table WHERE CHANGE_ID IN :ChangeId)))
(变化-变化倍数的过滤是在此查询之外完成的)。
获取某一时刻数据当前状态的查询将是;
SELECT * FROM table WHERE EFFECTIVE_FROM <= :Now AND (EFFECTIVE_TO IS NULL OR EFFECTIVE_TO > :Now)
常见的索引创建在 (ROOT_ID, EFFECTIVE_FROM), (EFFECTIVE_FROM, EFFECTIVE_TO) 和 (CHANGE_ID) 上。
如果有更好的解决方案,我很乐意听取建议。
ZoDB + ZEO 实现了一个基于修订的数据库,支持完全回滚到任何时间点。快去看看吧。
不好的地方:它与 Zope 绑定。