- 浏览: 289791 次
- 性别:
- 来自: 上海
文章分类
最新评论
-
SpringJava:
摘过来的
小心使用ArrayList和LinkedList -
jingjing0907:
我要成为第一个赞的人!呵呵,
小心使用ArrayList和LinkedList -
SpringJava:
cilendeng 写道应该用ConcurrentHashMa ...
实现单用户登陆session先进先出(session踢出) -
lingxiajiudu:
不错,完美解决我了的问题,赞一个。
子窗体给父窗体传值 javascript opener -
cilendeng:
应该用ConcurrentHashMap
实现单用户登陆session先进先出(session踢出)
重构 Java™ 代码远远比重构关系数据库简单,但幸运的是,对于对象数据库却并非如此。在本期的面向 Java 开发人员的 db4o 指南 中,Ted Neward 介绍他喜欢的对象数据库的另一个优点:db4o 简化了重构,使之变得非常容易。
在 本系列的上一篇文章 中,我谈到了查询 RDBMS 与查询像 db4o 这样的对象数据库的不同之处。正如我所说的那样,与通常的关系数据库相比, db4o 可以提供更多的方法来进行查询,为您处理不同应用程序场景提供了更多选择。
这一次,我将继续这一主题 —— db4o 的众多选项 —— 看看 db4o 如何处理重构。自 6.1 版开始,db4o 能自动识别和处理三种不同类型的重构:添加字段、删除字段和添加一个类的新接口。我不会讨论所有这三种重构(我将着重介绍添加字段和更改类名),但我将介绍 db4o 中的重构最令人兴奋的内容 —— 将向后兼容和向前兼容引入到数据库变更管理中。
您将看到,db4o 能够静默地处理更新,并确保代码与磁盘的一致性,这大大减轻了重构系统中持久性存储的压力。这样的灵活性也使得 db4o 非常适合于测试驱动开发过程。
|
上个月 ,我谈到了使用原生和 QBE 样式的查询来查询 db4o。在上次讨论中,我建议运行示例代码的读者删除包含之前运行结果的已有数据库文件。这是为了避免由于一致性概念在 OODBMS 中与在关系理论中的不同而导致的 “怪异” 结果。
这 种变通办法对于我的例子是适用的,但是也提出了现实中存在的一个有趣的问题。当定义其中所存储的对象的代码发生改变时,OODBMS 会怎样?在一个 RDBMS 中,“存储” 与 “对象” 之间的联系很清晰:RDBMS 遵从在使用数据库之前执行的 DDL 语句所定义的一种关系模式。然后,Java 代码要么使用手写的 JDBC 处理代码将查询结果映射到 Java 对象,要么通过 Hibernate 之类的库或新的 Java Persistence API (JPA) “自动” 完成映射。不管通过何种方式,这种映射是显式的,每当发生重构时,都必须作出修改。
从理论上讲,理论与实践之间没有不同。但也只是从理论上才能这么讲。重构关系数据库和对象/关系映射文件应该 是简单的。但在实际中,只有当重构仅仅与 Java 代码有关时,RDBMS 重构才比较简单。在这种情况下,只需更改映射就可以完成重构。但是,如果更改发生在数据的关系存储本身上,那么就突然进入一个全新的、复杂的领域,这个专 题复杂到足够写一本书。(我的一个同事曾经描述到 “500 页数据库表、触发器和视图” 这样的一本书。) 可以说,由于现实中的 RDBMS 常常包含需要保留的数据,仅仅删除模式然后通过 DDL 语句重新构建模式不是 正确的选择。
现在我们知道,当定义其中的对象的 Java 代码发生改变时 RDBMS 会发生什么样的变化。(或者,至少我们知道 RDBMS 管理器会怎样,这是一个很头痛的问题。)现在我们来看看当代码发生改变时 db4o 数据库的反应。
|
|
如果您已经阅读了本系列中的前两篇文章,那么应该熟悉我的非常简单的数据库。目前,它由一种类型组成,即 Person
类型,该类型的定义包含在清单 1 中:
package com.tedneward.model; public class Person { public Person() { } public Person(String firstName, String lastName, int age) { this.firstName = firstName; this.lastName = lastName; this.age = age; } public String getFirstName() { return firstName; } public void setFirstName(String value) { firstName = value; } public String getLastName() { return lastName; } public void setLastName(String value) { lastName = value; } public int getAge() { return age; } public void setAge(int value) { age = value; } public String toString() { return "[Person: " + "firstName = " + firstName + " " + "lastName = " + lastName + " " + "age = " + age + "]"; } public boolean equals(Object rhs) { if (rhs == this) return true; if (!(rhs instanceof Person)) return false; Person other = (Person)rhs; return (this.firstName.equals(other.firstName) && this.lastName.equals(other.lastName) && this.age == other.age); } private String firstName; private String lastName; private int age; } |
接下来,我填充数据库,如清单 2 所示:
import java.io.*; import java.lang.reflect.*; import com.db4o.*; import com.tedneward.model.*; // Version 1 public class BuildV1 { public static void main(String[] args) throws Exception { new File(".", "persons.data").delete(); ObjectContainer db = null; try { db = Db4o.openFile("persons.data"); Person brianG = new Person("Brian", "Goetz", 39); Person jason = new Person("Jason", "Hunter", 35); Person brianS = new Person("Brian", "Sletten", 38); Person david = new Person("David", "Geary", 55); Person glenn = new Person("Glenn", "Vanderberg", 40); Person neal = new Person("Neal", "Ford", 39); Person clinton = new Person("Clinton", "Begin", 19); db.set(brianG); db.set(jason); db.set(brianS); db.set(david); db.set(glenn); db.set(neal); db.set(clinton); db.commit(); // Find all the Brians ObjectSet brians = db.get(new Person("Brian", null, 0)); while (brians.hasNext()) System.out.println(brians.next()); } finally { if (db != null) db.close(); } } } |
注意,在清单 2 中开始的代码片段中,我显式地删除了文件 “persons.data”。这样做可以保证一开始就有整洁的记录。在 Build 应用程序将来的版本中,为了演示重构过程,我将保持
persons.data 文件不动。还需注意,Person
类型将来要发生改变(这将是我的重构的重点),所以请务必熟悉为每个例子存储和/或取出的版本。(请在 本文的源代码
中查看每个版本的 Person
中的注释,以及源代码树中的 Person.java.svn
文件。这些内容有助于理解例子。)
|
|
到目前为止,公司一直运营良好。公司数据库填满了 Person
,可以随时查询、存储和使用,基本上可以满足每个人的需求。但公司高层读了一本最近非常畅销的叫做 People have feelings too!
的管理书籍之后,决定修改该数据库,以包括 Person
的情绪(mood)。
在传统的对象/关系场景中,这意味着两个主要任务:一是重构代码(下面我会对此进行讨论),二是重构数据库模式,以包括反映 Person
情绪的新数据。现在,Scott Ambler 已经有了一些 RDBMS 重构方面的很好的资源(见 参考资料
),但重构关系数据库远比重构 Java 代码复杂这一事实丝毫没有改变,而在必须保留已有生产数据的情况下,这一点特别明显。
然而,在 OODBMS 中,事情就变得简单多了,因为重构完全发生在代码(在这里就是 Java 代码)中。需要记住的是,在 OODBMS 中,代码就是 模式。因此可以说, OODBMS 提供一个 “单一数据源(single source of truth)”,而不是 O/R 世界,即数据被编码在两个不同的位置:数据库模式和对象模型。(两者发生冲突时哪一方 “胜出”,这是 Java 开发人员中争论得很多的一个话题。
我的第一步是创建一个新类型,用于定义要跟踪的所有情绪。使用一个 Java 5 枚举类型就很容易做到这一点,如清单 3 所示:
package com.tedneward.model; public enum Mood { HAPPY, CONTENT, BLAH, CRANKY, DEPRESSED, PSYCHOTIC, WRITING_AN_ARTICLE } |
第二步,我需要更改 Person
代码,添加一个字段和一些用于跟踪情绪的属性方法,如清单 4 所示:
清单 4. No, howYOUdoin'?(不,你好吗?)
package com.tedneward.model; // Person v2 public class Person { // ... as before, with appropriate modifications to public constructor and // toString() method public Mood getMood() { return mood; } public void setMood(Mood value) { mood = value; } private Mood mood; } |
在做其它事情之前,我们先来看看 db4o 对查找数据库中所有 Brian
的查询如何作出响应。换句话说,当数据库中没有存储 Mood
实例时,如果在数据库上运行一个基于已有的 Person
的查询,db4o 将如何作出响应(见清单 5)?
import com.db4o.*;
import com.tedneward.model.*;
// Version 2
public class ReadV2
{
public static void main(String[] args)
throws Exception
{
// Note the absence of the File.delete() call
ObjectContainer db = null;
try
{
db = Db4o.openFile("persons.data");
// Find all the Brians
ObjectSet brians = db.get(new Person("Brian", null, 0, null));
while (brians.hasNext())
System.out.println(brians.next());
}
finally
{
if (db != null)
db.close();
}
}
}
|
结果有些令人吃惊,如清单 6 所示:
[Person: firstName = Brian lastName = Sletten age = 38 mood = null] [Person: firstName = Brian lastName = Goetz age = 39 mood = null] |
在 Person
的两个定义(一个在磁盘上,另一个在代码中)并不一致的事实面前,db4o 不但没有
卡壳,而且更进了一步:它查看磁盘上的数据,确定那里的 Person
实例没有 mood
字段,并静默地用默认值
null
替代。(顺便说一句,在这种情况下,Java Object
Serialization API 也是这样做的。)
这 里最重要的一点是,db4o 静默地处理它看到的磁盘上的数据与类型定义之间的不匹配。这成为贯穿 db4o 重构行为的永恒主题:db4o 尽可能静默地处理版本失配。它或者扩展磁盘上的元素以包括添加的字段,或者,如果给定 JVM 中使用的类定义中不存在这些字段,则忽略它们。
|
|
db4o 对磁盘上缺失的或不必要的字段进行某种调整,这一思想需要解释一下,所以让我们看看当更新磁盘上的数据以包括情绪时会出现什么情况,如清单 7 所示:
import com.db4o.*; import com.tedneward.model.*; // Version 2 public class BuildV2 { public static void main(String[] args) throws Exception { ObjectContainer db = null; try { db = Db4o.openFile("persons.data"); // Find all the Persons, and give them moods ObjectSet people = db.get(Person.class); while (people.hasNext()) { Person person = (Person)people.next(); System.out.print("Setting " + person.getFirstName() + "'s mood "); int moodVal = (int)(Math.random() * Mood.values().length); person.setMood(Mood.values()[moodVal]); System.out.println("to " + person.getMood()); db.set(person); } db.commit(); } finally { if (db != null) db.close(); } } } |
在清单 7 中,我发现数据库中的所有 Person
,并随机地为他们赋予
Mood
。在更现实的应用程序中,我会使用一组基准数据,而不是随即选择数据,但对于这个例子而言这样做也行。运行上述代码后产生清单 8 所示的输出:
Setting Brian's mood to BLAH Setting David's mood to WRITING_AN_ARTICLE Setting Brian's mood to CONTENT Setting Jason's mood to PSYCHOTIC Setting Glenn's mood to BLAH Setting Neal's mood to HAPPY Setting Clinton's mood to DEPRESSED |
可以通过再次运行 ReadV2
验证该输出。最好是再运行一下初始的查询版本 ReadV1
(该版本看上去很像 ReadV2,只是它是在 V1 版本的 Person
的基础上编译的)。
运行之后,产生如下输出:
[Person: firstName = Brian lastName = Sletten age = 38] [Person: firstName = Brian lastName = Goetz age = 39] |
对于清单 9 中的输出,值得注意的是,与我将 Mood
扩展添加到 Person
类(在 清单 6
中)之前 db4o 的输出相比,这里的输出并无不同 —— 这意味着 db4o 是同时向后兼容和向前兼容的。
|
|
假设要更改已有类中一个字段的类型,例如将 Person
的 age 从整型改为短整型。(毕竟,通常没有人能活过 32,000 岁 —— 而且我相信,即使真的
有那么长寿的人,仍然可以重构代码,将该字段改回整型。)假定两种类型在本质上是类似的,就像 int 与 short,或 float 与
double,db4o 只是静默地处理更改 —— 同样是或多或少仿效 Java Object Serialization
API。这种操作的缺点是,db4o 可能会意外地将一个值截尾。只有当一个值 “转换为范围更小的类型”
时,即这个值超出了新类型允许的范围,例如试图从 long 类型转换为 int 类型,才会发生这样的情况。货物出门概不退货,买主需自行小心
—— 在开发期间或原型开发期间,务必进行彻底的单元测试。
|
实际 上,db4o 向后兼容的妙法值得解释一下。基本上,当 db4o 看到新类型的字段时,就会在磁盘上创建一个新字段,该字段有相同的名称,但是具有新的类型,就好像它是添加到类中的另一个新字段一样。这还意味着,旧的值 仍然保留在旧类型的字段中。因此,通过将字段重构回初始值,总可以 “回调” 旧值,取决于观察问题的角度,这可以说是一个特性,也可以说是一个 bug。
注意,对类中方法的更改与 db4o 无关,因为它不将方法或方法实现作为存储的对象数据的一部分,对于构造函数的重构也是如此。只有字段和类名本身(接下来会进行讨论)对于 db4o 才是重要的。
|
|
在某些情况下,需要发生的重构可能更剧烈一些,例如整个更改一个类的名称(可以是类名,也可以是类所在的包的名称)。像这样的更改对于 db4o 是比较大的更改,因为它需要根据 classname
来存储对象。例如,当 db4o 查找 Person
实例时,它在标有名称 com.tedneward.model.Person
的块的特定区域中进行查找。因此,改变名称会使 db4o 不知所措:它不能魔术般地推断 com.tedneward.model.Person
现在就是 com.tedneward.persons.model.Individual
。幸运的是,有两种方法可以教会 db4o 如何管理这样的转换。
使 db4o 适应这样剧烈的更改的一种方法是编写自己的重构工具,使用 db4o Refactoring API 打开已有的数据文件,并更改在磁盘上的名称。可以通过一组简单的调用做到这一点,如清单 10 所示:
清单 10. 从 Person 重构为 Individual
import com.db4o.*; import com.db4o.config.*; // ... Db4o.configure().objectClass("com.tedneward.model.Person") .rename("com.tedneward.persons.model.Individual"); |
注意,清单 10 中的代码使用
db4o Configuration API 获得一个配置对象,该配置对象被用作对 db4o 的大多数选项的
“元控制(meta-control)” —— 在运行时,您将使用这个 API
而不是命令行标志或配置文件来设置特定的设置(虽然您完全可以创建自己的命令行标志或配置文件来驱动 Configuration API
调用)。然后,使用 Configuration
对象获得 Person
类的 ObjectClass
实例……或者更确切地说,是表示磁盘上存储的 Person
实例的 ObjectClass
实例。ObjectClass
还包含很多其它选项,在本系列的后面我会展示其中的一些选项。
在某些情况下,磁盘上的数据必须存在,以支持由于技术或策略上的某种原因而不能重新编译的早期应用程序。在这些情况下,V2 应用程序必须能够提取 V1 实例,并在内存中将它们转换成 V2 实例。 幸运的是,在向磁盘存储并从中检索对象时,可以依靠 db4o 的别名 特性创建一个 shuffle 步骤。这样便可以区别内存中使用的类型和存储的类型。
db4o 支持三种类型的别名,其中一种类型只有当 .NET 和 Java 风格的 db4o 之间共享数据文件时才有用。
清单 11 中出现的别名是 TypeAlias
,它有效地告诉 db4o 用内存中的 “A” 类型(运行时名称
)替换磁盘上的 “B” 类型(存储的名称
)。启用这种别名是一种双线操作。
import com.db4o.config.*; // ... TypeAlias fromPersonToIndividual = new TypeAlias("com.tedneward.model.Person", "com.tedneward.persons.model.Individual"); Db4o.configure().addAlias(fromPersonToIndividual); |
当运行时,db4o 现在将查询数据库中的 Individual
对象的任何调用识别为一个请求,而不会查找存储的 Person
实例;这意味着,Individual
类中的名称和类型应该和 Person
中存储的名称和类型类似,db4o 将适当地处理它们之间的映射。然后,Individual
实例将被存储在 Person
名称之下。
|
由 于对象数据库中的模式就是类定义本身,而不是采用不同语言的单独的 DDL 定义,因此本文中的每个重构例子都显得简单很多。db4o 中的重构是使用代码完成的,常常可以通过一个配置调用来确定,最坏情况也只不过是编写和运行一个转换实用程序,以将已有实例从旧的类型更新为新的类型。而 且这种类型的转换对于几乎所有生产中的 RDBMS 重构都是必需的。
db4o 强大的重构能力使之在开发期间非常有用,因为在开发期间,正在设计的很多对象仍然是变化无常的,即使不需要每个小时都重构,至少也需要每天都重构。如果将 db4o 用于单元测试和测试驱动开发,则可以节省大量更改数据库的时间,如果重构只是简单的字段添加/删除或类型/名称更改,这一点就更加明显了。
这就是本文讨论的内容,但是请记住:如果要用对象编写应用程序,并且持久性存储实际上 “只是和实现有关”,那么为什么非得把很好的对象限制成规规矩矩、四四方方的样子呢?
以下是db4o 中的数据库重构
- j-db4o3-source.zip (24.8 KB)
- 下载次数: 4
发表评论
-
栈的简单应用--单词反转
2014-07-03 16:00 695我们知道栈是一种先进后出,也就是后进先出的数据 ... -
java实现简单的栈
2014-07-01 11:56 643栈--只允许访问第一个数据项即:最后插入的数据。最简单的一句 ... -
小心使用ArrayList和LinkedList
2014-06-30 16:32 785ArrayList内部是使用可増长数组实现的,所以是用ge ... -
有趣的Java算法(3)
2014-06-30 16:29 681给定两个排序后的数组A和B,其中A的末端有足够的空间容纳B ... -
有趣的Java算法(2)
2014-06-30 16:29 1057今天分享一个"将一个整数的每位数分解并按逆序输 ... -
有趣的Java算法
2014-06-20 17:00 747题目及源码分析: /* * 今天在BBS里面看到这 ... -
java 值传递 引用传递
2010-12-17 23:11 2065java方法用的是值传递还是引用传递。你在blogjava上还 ... -
用java代码编写堆栈
2010-05-03 17:39 1233public class Stack { int[] ... -
几种读取属性文件的JAVA实现方式
2010-04-30 19:20 1178转载:http://darkranger.iteye.com/ ... -
Site
2010-04-30 19:20 966http://www.szikao.com/computer/ ... -
JAVA对XML的几种解析方式
2010-04-29 19:53 957对于XML介绍比较全面的还是IBM的专栏: ... -
集合与通用集合
2010-04-29 19:44 1430URL: http://www.ibm.com/develop ... -
HashMap 和TreeMap
2010-04-29 19:41 1276本文重点介绍HashMap。首先介绍一下什么是Map。在数组中 ... -
TreeMap和HashMap的问题
2010-04-29 19:39 2090在一次面试的过程 ... -
实现单用户登陆session先进先出(session踢出)
2010-04-29 19:33 9478首先在系统增加sessionListener 来监听sessi ... -
Java单态模式的实现
2010-04-29 19:23 15801.饿汉式:public class Sing ... -
请教java反射机制里可以调用私有方法吗?
2010-04-27 19:17 1626如题:请教java反射机制里可以调用私有方法吗? Metho ... -
利用java反射机制调用类的私有方法
2010-04-27 18:59 14171.将getInstance()方法设置为private ... -
String是原始数据类型还是引用数据类型
2010-04-26 19:22 1711请教各位高手,String是原始数据类型还是引用数据类型?谢谢 ... -
java中堆(heap)和堆栈(stack)有什么区别
2010-04-26 19:13 2195stack 和 heap 都是内存的 ...
相关推荐
面向Java开发人员的db4o指南db4o中的数据库重构
总结来说,面向Java开发人员的db4o指南是关于如何使用db4o这个对象数据库系统来高效、便捷地管理结构化对象和集合的教程。它涵盖了db4o的基本概念、API使用、查询语言、性能优化以及数据库的配置和管理等方面,旨在...
3. **版本控制**:db4o可以跟踪对象的历史版本,允许你在任何时候回滚到之前的对象状态。 4. **事件驱动**:db4o提供了对象生命周期事件,如对象插入、更新和删除时的回调机制,方便进行业务逻辑处理。 5. **内存...
该资源为 db4o 之旅 系列文章: ...3.介绍面向对象数据库 db4o 的修改和删除,并对其中出现的问题进行细致分析,引入了“更新深度(update depth)”这一重要概念。 最后更新于2006 年 12 月 14 日 另外再推荐一个站点 ...
db4o 是一个开源的面向对象数据库,能够轻松地将 Java 对象持久化到数据库中。本文将详细介绍 db4o 的安装、启动、查询方式、对象持久化、数据库文件结构、主要包结构等知识点。 一、db4o 安装和启动 db4o 的安装...
5. **分布式数据库**:db4o支持多客户端并发访问,可以在分布式环境中运行,实现数据的共享和同步。 6. **动态模式**:db4o允许在运行时动态添加新类和字段,无需预先定义表结构,具有很好的灵活性。 ### 示例程序...
DB4O面向对象数据库使用指南
这款数据库引擎已被验证具备优秀的性能,根据描述中的基准测试,db4o在与传统持久化方案的对比中排名第二,仅次于JDBC,且明显优于使用Hibernate/HSQLDB的方案,证明了面向对象并不一定意味着性能损失。 ### 1. db4...
《db4o 权威指南》是一本深入探讨db4o这一开源面向对象数据库系统的专业书籍,对于Java开发者来说尤其有价值。db4o是Database for Objects的缩写,它允许开发者以自然、直观的方式存储和检索Java对象,无需编写SQL...
db4o(Database for Objects)是一款开源的对象数据库系统,它允许开发者直接将Java或.NET对象存储到数据库中,无需进行ORM(对象关系映射)。db4o的目标是简化数据管理,提供更接近自然编程的方式,使开发过程更加...
在实际开发中,DB4O提供了强大的特性,如透明持久化、延迟加载、查询优化等,使得开发人员可以更专注于业务逻辑,而不是数据库交互的细节。不过,由于DB4O不再维护,对于新的项目,可能需要考虑其他现代的对象数据库...
1. **面向对象存储**:db4o的核心特性是其对对象模型的支持,可以直接将Java对象持久化到数据库中,而不需要进行任何对象-关系映射(ORM)。这种设计使得开发过程更为简洁,因为它避免了对象与表之间的转换。 2. **...
**db4o 开发指南与实例详解** **一、db4o 简介** db4o(Database for Objects)是一款开源的对象关系数据库管理系统(Object-Relational Mapping, ORM),它允许开发者直接将Java对象存储到数据库中,无需编写SQL...
Db4o,全称为“Database for Objects”,是一个开源的对象数据库管理系统,主要应用于Java和.NET平台。这个项目专注于提供一种简单的方式来存储和检索Java对象,无需SQL或其他中间映射层。在“Db4o的简单操作项目”...
- **db4o**:db4o是一款专为Java、.NET和Mono平台设计的开源面向对象数据库系统。它提供了高效的数据存储和检索功能,支持多种数据类型,并且能够直接存储复杂的对象结构而无需进行序列化或反序列化的操作。 - **...
总结来说,"db4o-8.0-java" 是一个完整的db4o对象数据库解决方案,包含所有必要的组件,让Java开发者能够轻松地在自己的项目中实施对象数据库。源码、jar包、Eclipse插件和文档的提供,使得开发、调试和学习过程变得...
标题:db4o-5.0-tutorial-java帮助 描述:这份资料是关于db4o数据库的使用教程,特别针对Java开发人员。db4o是一款开源的对象数据库,支持Java、.NET以及Mono平台。该教程旨在帮助用户快速上手db4o,并在开发过程中...
在DB4o中,我们首先需要创建一个配置对象,即`Configuration.java`。这个配置对象允许我们设置数据库的各种属性,如缓存大小、事务管理等。例如: ```java Configuration config = CommonConfigurations.DEFAULT();...