- 浏览: 42109 次
- 来自: ...
文章分类
最新评论
-
zhulongxing_sz:
2楼的,何出此言呢?你不了解就不要乱发炎··· 你测试过 ...
深入db4o -
学会做人:
这个性能其实不怎么样!
深入db4o -
天机老人:
哇,全看完了,呵呵确实不错,但是还是没有抵消我心中的念头,内存 ...
深入db4o -
天空之城:
挺好!
UML -
szg26188:
好,很好,非常好
Struts2 与 Struts1.x比较
深入db4o
这是Rick Grehan发表在TheServerSide上的一篇关于面向对象数据库--db4o的文章,较全面地介绍了db4o的关键特性,希望对大家认识db4o能有所帮助。(2007.12.07最后更新)
db4o-针对对象的数据库-是一个完全的对象数据库;它以使对象在其生命周期中-无论是在数据库内或是在外-都保持着它们的本性这样一种方式操纵对象。不论类的复杂性如何,对象的内容,结构和关系都能够被保存。
更准确地说,db4o是一个数据库引擎,你只要将它的一个jar文件包含到你的数据库应用的类路径中就可以使用它了(至少对于Java是这样的)。所以,db4o运行在与你的应用程序相同的进程空间中,并能被直接地调用;它不需要类似于在ODBC或JDBC中使用的驱动文件。db4o存在针对Java,.NET和Mono的版本;它们在功能上都彼此相等。(事实上,使用.NET创建的db4o数据库也能由Java程序访问;反之亦然。)
db4o是开源的。可执行文件,源代码和文档可从http://www.db4objects.com/中下载。广泛的例子程序,和一个活跃的用户社区一样,也都可以从这个站点中找到。
db4o最引人的特性之一就是它在简易性与强大的功能之间的显著平衡。一方面,它的API是如此地易于掌握和方便使用,即使是初学者也能在相同的时间内创建一个功能完备的数据库对象。另一方面,这些相同的API也提供了更底层的能够深入调用数据库引擎的方法,以允许核心开发者为了得到适当的性能而能深入到该引擎的内部中去调整db4o的工具。
db4o的特性就是最好的证明--这胜过只是讨论--所以我们将通过示例这种方法去证明db4o。然而,我们必须牢记本文通篇只是展示了db4o特性中的一部分罢了。感兴趣的朋友会发现为了知晓该数据库引擎的全部功能而去查阅db4o的文档所花的时间是值得的。
db4o基础
让我们以初学者使用db4o时可能会做的事情开始:定义了一些类,然后持久化这些类的对象。我们所假定的类为同样也是假定的QA项目做一个跟踪测试的系统。我们的系统由两个类组成,首先是TestSuite类:
public class TestSuite {
private String name; // Test Suite name
private String description;
private String configuration;
private char overallScore;
private ArrayList <TestCase> cases;
private long dateExec;
... <remainder of TestSuite definition> ...
}
TestSuite是TestCase对象的容器,(一个测试用例就是一个可单独执行的测试程序--相关的测试用例组合成一个测试组)。测试组使用额外的,全局的数据成员,每个数据成员的用途也是相对明显的:configuration记录被测试的指定系统;overallScore是对整个测试组一个简要的评分('P'代表通过,'F'代表失败,'B'代表被阻塞,等等。);dateExec是一个毫秒级的域,标识该测试组被执行时的日期与时刻。是ArrayList对象的cases含有单个的测试用例,由TestCase类定义:
public class TestCase {
private String name;
private String comment;
private char status;
private long duration;
private float result;
... <remainder of TestCase definition> ...
}
每个测试用例都有一个名称,形式自由的注释字段,状态(通过或失败),持续时间和结果(例如,为了与测试-字节/秒的吞吐量-的任意数据关联)。
因为我们所关注是db4o的使用,所以我们不想在描述这些类的使用细节上停留。就让我们简单地说,我们已经执行了一个特别地测试组中所有的测试用例,将测试的结果存放在一个TestSuite对象中(与TestCase对象相关的ArrayList对象cases中),然后关闭数据库。是不是太容易了。
// Create the database
new File("testsuites.YAP").delete();
ObjectContainer db = Db4o.openFile("testsuites.YAP");
// Store the TestSuite object
db.set(testsuite);
// Close the database
db.close();
就是它。弹指一挥间,你就已经做到了。(当然,为了保持简洁,我们去掉了创建TestSuite对象和它的TestCase组件的细节)
停下来想想上述代码做了什么事情。特别要考虑你没有看到的--db4o已经做了但还未被告之的事情。
首先,我们不需要告诉db4o任何关于TestSuite类的结构的信息;不需要我们的帮助,db4o就能发现这个结构。利用Java反射机制的能力,db4o测定TestSuite类的结构,并勾勒出该类的装配方式以推导出此类对象的成员与关键数据。
第二,我们不必建议db4o去关注ArrayList。不仅我们不必将ArrayList的大小告诉db4o,而且我们也不必把它里面的内容告诉db4o。正如db4o在它刚接触testsuite对象时就能够发现它所需要的一切,db4o也能知道它所需要的关于(在ArrayList中的)TestCase对象的所有信息。
结果就是,如果我们把testsuite作为一个任意宠大而复杂的对象树的根,db4o能找到并存储整个树而不需要我们的任何协助。所以,存储这个处于根部的对象testsuite也就是存储了整个ArrayList对象。
最后,我们也没有必须要求db4o以事务的保护性方式去调用set方法。任何会修改ObjectContainer(表示数据库的db4o对象)的调用都会自动地开启一个事务,除非已经有一个活跃的事务了。此外还会调用close方法去终止这个事务,所以上述代码等价于:
db.startTransaction();
db.set(testsuite);
db.commitTransaction();
db.close();
此处的startTransaction和commitTransaction方法是为了证明我们的观点而虚构的。db4o也确实有显示地提交或中止事务的方法,但为了使原先的代码足够的简洁我们没有使用这些方法。db4o隐式的事务使得数据库能够一直处于一致的状态;一旦commit方法已经执行了,数据库的完整性就能够得到保证,甚至是发生了灾难性失败也能如此。
查询I - QBE
有了已经存于数据库中的对象,下一步我们想要展示的操作将肯定就是查询和恢复。db4o提供了三种查询API:有一种简单,有一种优雅,还有一种则复杂。每一种API都有其所长,并适用于不同的查询条件。以db4o的眼光来看,你选择哪一种API并没有关系:它们都是可兼容的。
我们以简单的API开始:query by exampel(QBE)。
使用QBE是如此的容易:为你的查询目标构建一个'模板'对象,然后把它传入ObjectContainer的query方法。实际上,你就是告诉db4o'去拿到所有与这个对象看起来一样的对象'。(这与JavaSpaces查询API非常相似;为了清楚地了解如何处理基本数据类型,可以看下面的内容,db4o的处理方式与JavaSpaces不同。也要注意,JavaSpace Entry对象期望使用public字段,db4o则没有这种要求。)
假设一个测试组名为"Network Throughput",我们想取出这个测试组执行的所有测试以便我们能确定失败了的测试所占的百分比(基于TestSuite的overalScore值)。使用QBE,完成该工作的代码如下:
// Open the database
ObjectContainer db = Db4o.openFile("testsuites.YAP");
// Instantiate the template object, filling in
// only the name field
testTemplate = new TestSuite("Network Throughput");
// Execute the query
ObjectSet result = db.get(testTemplate);
fails = 0.0f;
total = 0.0f;
// Step through results,
while(result.hasNext())
{
testsuite = (TestSuite)result.next();
if(testsuite.getOverallScore()=='F')
fails += 1.0f;
total += 1.0f;
}
if(total == 0.0f)
System.out.println("No tests of that type found.");
else
{
System.out.println("Percent failed: " + (fails/total * 100.0f) + "%");
System.out.println("Total executed: " + total);
}
db.close();
在上面的代码中,testTemplate是QBE的模板对象。注意,只有它的name字段有真实的值;所有其它的成员变量不是为null就是为0。Null或0字段不参与QBE查询;因此,调用db.get方法就会返回在该数据库中name字段匹配"Network Throughput"的所有TestSuite对象。匹配的TestSuite对象将返回在一个ObjectSet结果对象中。上述代码遍历该结果,取出对象,然后计算结果并展示出来。
QBE明显的优点就是它的简易性。不需要掌握其它单独的查询语言。另外,QBE也是类型安全的:你不需要创建一个类似于SQL的查询语句
SELECT TestSuite.overallScore FROM TestSuite WHERE TestSuite.name = 200.0
另一方面,由于该查询是由Java代码创建的,编译器不会允许你把一个浮点值赋给一个String字段;反之亦然。
QBE明显的缺点就是它只能执行"等于"查询。另外,QBE使用null值去确定不参与查询的String或对象引用成员变量,使用0值去指定不参与查询的数字字段。所以,例如,我不能发明一个QBE查询去获得所有result字段的值为0的TestCase对象。
更为精细的查询要求一个能力更强的查询机制,而db4o恰恰就有这样一种机制。
查询方式II - 原生查询(Native Query)
db4o的原生查询系统应该是能想像得到的最具弹性的查询机制。不像使用数据库查询语言去构建查询,你是使用"无格式的普通Java语句"去构造原生查询。原生查询用两种手段去实现这件不可思意的工作:一个是Predicate类;另一个是QueryComparator接口。这个类包含一个可重载的(overrideable)回调方法,该方法将指定如何从数据库中选择对象(如果你愿意,你将会看到查询语句的主体....)。这个接口只声明了一个方法,该方法用于指明如何对查询结果进行排序。
作为一个例子,我们假设想找到在给定的一周内执行过了的总得分为"failed",但与之关联的测试用例中有超过一半的被评为"passed"的测试组。这不是一个简单的"等于"查询,所以它不能使用QBE构建。
然而,db4o的原生查询可以直接地生成该查询。首先,我们继承db4o的Predicate类:
// Predicate class sub-class for native query example
public class NativeQueryQuery extends Predicate<TestSuite>
{
ObjectContainer db;
private long startdate;
private long enddate;
// 构造器要在本地获得ObjectContainer对象和日期范围
public NativeQueryQuery(ObjectContainer _db,
long _start, long _end)
{
db = _db;
startdate = _start;
enddate = _end;
}
// 这就是查询的主体
public boolean match(TestSuite testsuite)
{
float passed;
float total;
TestCase testcase;
// 判断testsuite是否在指定的日期范围内
if(testsuite.getDateExec()<startdate ||
testsuite.getDateExec()>enddate) return false;
// 如果该测试组对象中没有测试用例对象,则拒绝将该测试组对象放入查询结果中
if(testsuite.getNumberOfCases()==0)
return false;
// 检查该测试组对象中的测试用例的通过率是否超过50%
passed = 0.0f;
total = 0.0f;
for(int i=0; i<testsuite.getNumberOfCases(); i++)
{
testcase = testsuite.getTestCase(i);
if(testcase.getStatus()=='P')
passed+=1.0f;
total+=1.0f;
}
if((passed/total)<.5) return false;
return true;
}
}
注意在这个类的使用中使用了Java泛型语义,这就是告诉db4o只去取TestSuite对象。当查询执行时,TestSuite对象就会传入match方法(我们之前提到过的回调方法),如果传入的TestSuite对象符合查询规范该方法就返回true,否则就返回false。
match方法中的代码首先确定侯选对象是否是在一周的日期范围内。如果是,则循环该对象中的成员变量测试用例的对象,计算出所有通过了的测试用例的总数。如果,得到的通过率小于50%,该测试组就被拒绝;否则,就让它通过。
我们可以使用如下的代码准确地展示该查询程序:
. . .
TestSuite testsuite;
NativeQueryQuery nqqClass;
Date now;
// Open the database
ObjectContainer db = Db4o.openFile("testsuites.YAP");
// Instantiate a NativeQueryQuery object,
// setting the start and end dates for
// any test in the past week
// 604800000 = milliseconds in a week
now = new Date();
nqqClass = new NativeQueryQuery(db,
now.getTime()-604800000L,
now.getTime());
// Execute the query and display the
// results
System.out.println("Results:");
ObjectSet results = db.query(nqqClass);
if(results.isEmpty())
System.out.println(" NOTHING TO DISPLAY");
while(results.hasNext())
{
testsuite = (TestSuite)(results.next());
System.out.println(testsuite.toString());
}
db.close();
. . .
可以把原生查询想像成这样:目标类的对象一个接一个从数据库中取出,然后把它们传入match方法中。只有那些被match方法返回true的对象才会置于查询结果ObjectSet对象中。基本上可以说,如果你会知道如何写Java代码,那么你就知道如何写原生查询。
那么排序呢?如果想按日期的升序排列查询结果,我们就要实现QueryComparator接口,如下所示:
public class NativeQuerySort implements QueryComparator<TestSuite>{
public int compare(TestSuite t1, TestSuite t2)
{
if (t1.getDateExec() < t2.getDateExec()) return -1;
if (t1.getDateExec() > t2.getDateExec()) return 1;
return 0;
}
}
compare方法的作用十分明显。那些在查询中得以胜出的对象会成对的传入compare方法,如果第一个对象会排在第二个对象之前,相同或之后的位置,该方法就会分别返回一个小于,等于或大于0的值。为了准确地说明对查询结果的排序,我们实例化NativeQuerySort,并把对query方法的调用修改成如下:
. . .
// Instantiate the sort class
nqsClass = new NativeQuerySort();
. . .
ObjectSet results = db.query(nqqClass, nqsClass);
. . .
其它的代码仍然与原先的保持一致。
好怀疑的读者可能会抱怨道,原生查询只是一种编程上的小伎俩--相比较于直接去拿到所有的TestSuite对象然后再排除其中不符合条件的对象这样的程序,原生查询并不快。
是的,但并不十分准确。原生能够被优化。你所需要做的只是把两个jar文件--db4o-xxx-nqopt.jar(xxx表示db4o的版本)和bloat.jar--置于CLASSPATH环境变量中。在查询执行的时候,这些类库中的代码会对(在match方法中)例如基本数据类型比较,算术和布尔表达式,简单的对象成员访问,以及更多方面的结构进行优化。这个被支持的优化的列表在不停的增长,因为db4o引擎还在扩展优化的范围。
查询方式III - S.O.D.A.
db4o独一无二的能力之一就是它的API被分层了。开发者能够选择通过高层次--赋予数据库引擎相当大的自由度,让它决定如何去实现它的操作--或者开发者也可以使用一种更直接地方式去访问db4o。后一种选择为程序员平添了更多的负担,程序员必须更加小心地引导数据库引擎的内部工作。但回报就是得到一个更快,能力更强的数据库应用。
db4o的S.O.D.A.(Simple Object Data Access)查询机制就是该层次API的一个完美的例子。S.O.D.A.就是db4o的内部查询系统--QBE和原生查询都被翻译成了S.O.D.A.。然而,应用程序也能直接地调用S.O.D.A.。
假设我们想找到所有名为"Network Throughput",且至少拥有一个其result字段--我们使用这个参数作为字节/秒的量度--不小于指定值(比方说,100)的测试用例的测试组。为该请求而做的S.O.D.A.查询可能就像这样:
. . .
TestSuite testsuite;
// Open the database
ObjectContainer db = Db4o.openFile("testsuites.YAP");
// Construct the query
Query query = db.query();
query.constrain(TestSuite.class);
Constraint nameConst = query.descend("name").
constrain("Network Throughput");
query.descend("cases").descend("result").
constrain(100.0f).smaller().and(nameConst);
System.out.println("Results:");
// Execute the query
ObjectSet result = query.execute();
if(result.isEmpty())
System.out.println("NOTHING TO DISPLAY");
while(result.hasNext())
{
testsuite = (TestSuite)(result.next());
System.out.println(testsuite.toString());
}
db.close();
. . .
在由Illustration 1所示图表的帮助下,这些有点儿神秘的代码就变得不那么神秘了。该程序所构建的归总起来就是一个用于指导底层数据库引擎的查询图(Query Graph)。descend方法创建了该图的一个分支,该分支向下步入对象的结构中。每个descend方法就在这个树中构建一个结点,可以在这些结点上再附上一个约束(使用constrain方法)。用SQL的话来说,约束指定了查询的"WHERE"子句部分。多个约束可以在与(and)或或(or)方法的协助下结合起来。在上面的查询中我们已经使用了and方法去关联这些约束。
与其它的查询方式一样,查询结果返回到ObjectSet对象中,通过遍历该对象就可取出那些拿到的对象。
注意,由于S.O.D.A.是一种低层次的访问方法,没有智能的指示,它就没有默认的行为。访问cases对象的成员变量result字段的代码很简单
query.descend("cases").descend("result"). ...
我们并没有告诉S.O.D.A."cases"是一个集合对象。所以当查询执行时,它会不被察觉地检测ArrayList对象cases中所有元素(TestCase对象)的result字段,然后会正确地返回那些拥有符合搜索规范的测试用例的测试组。
db4o性能调优
我们已经展示了db4o的基本操作(但无关紧要的删除操作除外,下面将会提到它)。但,正如我们在本文通篇所提到的,db4o发布(expose)了一个API层次结构,以允许开发者能够选择以何种层次的API去控制建立在该数据库引擎之上的应用程序。从另一方面看,如果你所想做的只是向数据库存入,及从数据库取出对象,那么你就已经看到了你所需要的一切。然而,如果你的应用的需求超出了添加,更新,查询和删除,可能还有一个db4o的特性可解决你的问题。
db4o的ObjectContainer实际上发布(expose)了两个API。第一个API非常的简单,由十个方法组成。这些方法处理数据库的打开与关闭;添加,更新,查询和删除对象;及提交或中止事务。短言之,该API为你提供了在操纵数据库时所需要所有功能。然而,该API中的一个方法--ext()--是进入"被扩展的"ObjectContainer的一个入口。该被扩展的ObjectContainer为深入控制db4o的内部发布(expose)了更多方法。例如,你可以获得并改变数据库的配置上下文,使用它你能够修改该引擎的行为。
例如,假设你已经从数据库中拿到了一个TestSuite对象,发现该对象中的数据是错误的,并决定该对象应该被删除。此外,你还决定你必须删除的不仅是该TestSuite对象,而且还有所有与之关联的TestCase对象(在ArrayList对象cases中)。
你是可以冗长而乏味地遍历这个ArrayList对象,一个接一个地删除每一个TestCase对象,然后再删除TestSuite对象本身。可能一种更好的解决方案是为这个TestSuite对象启用db4o的"级联删除"特性。
. . .
// Fetch the database's configuration context
Configuration config = db.ext().configure();
// Get the ObjectClass for TestSuite
ObjectClass oc = config.objectClass("testsuites.TestSuite");
// Turn on cascaded delete
oc.cascadeOnDelete(true);
... ...
db.delete(ts1);
. . .
在上述代码中,我们实例化了一个ObjectClass对象,该对象使我们能够访问到TestSuite对象的db4o内部表现形式。我们打开cascadeOnDelete标记,以便当执行db4o.delete(ts1)时,不仅ts1对象会被删除,而且所有由ts1引用的TestCase对象也会被删除。(由于显而易见的原因,默认情况下,级联删除是被关闭的)
作为另一个例子,假设你想为数据库预分配存储空间,以至于要最小化磁盘驱动器的头移动(head movement)。(最好是在硬盘碎片整理之后,并新创建一个数据库时这样做。)并且假设以ObjectContainer对象db作为打开了的数据库:
// Fetch the database's configuration context
// 获得数据库的配置上下文
Configuration config = db.ext().configure();
// Pre-allocate 200,000 bytes
// 预分配200000000字节
config.reserveStorageSpace(200000000L);
把数据库文件预扩展到200000000字节(略小于200兆字节)。假设该磁盘已经做碎片整理了,那么被分配的块就是连续的,这可显著提升数据库的访问。
db4o高级应用
完全可以说,db4o在它不大的空间(约500K)内已装入了足够多的特性,相比较于db4o在运行过程中所做的众多事情,我们不能花费更多的笔墨去解释它们了。但是有两个特性十分突出,以至于肯定要提到它们。
db4o的对象复制实现了被总称为面向对象版的数据库同步。使用复制所提供的功能,你能为一个数据库中的一个对象做一个副本,并将该副本放入另一个数据库中。使用这样的方法,副本对象就无形中和原始对象关联在了一起。对任一对象--原始对象或副本对象--的改变都会被跟踪到,以便在之后的某个时候,数据库能够被重组,并且这两个数据库中对象的不同之处可被分解(例如,可同步这两个数据库)。
它工作起来就像这样:为使一个数据库可被复制,与事务计数器一样,该数据库中被创建的任何一个对象都用一个唯一全局标识符(UUID)进行了标记。当你从原始数据库中"复制"一个对象到另一个数据库中,副本对象会带着与它的原始对象相同的UUID和事务计数器。副本数据库现在就可以从它的原始数据库那儿弄走了。修改副本对象的内容将会导致对象的事务计数器被修改。所以,当这两个数据库重新连接起来,db4o内建的同步处理机制就能一个对象一个对象地进行正确的匹配(使用UUID),并确定原始或副本对象是否已经改变了。db4o甚至能追踪到每个对象发生最后一次修改时的时间,以便用户写的冲突解决代码能确定哪个对象是最近更新的。
从操作行为来看,db4o的同步处理机制与原生查询十分相似。回想一下,当实现了一个原生查询类时,我们要定义一个match方法,该方法确定哪些对象符合(或不符合)查询规范。使用同步复制,我们要定义一个ReplicationProcess对象,我们会把冲突处理对象传入该方法中。这些Java代码可能像这样:
. . .
ReplicationProcess replication = db1.ext().
replicationBegin(db2, new ReplicationConflictHandler()
{
public Object resolveConflict(
ReplicationProcess rprocess, Object a, Object b)
{
. . . ...
return winning_object;
}
)
};
在上述代码中,Object a是来自于数据库db1的对象,Object b则来自于数据库db2。默认情况下,同步是双向的。同步处理保证胜出的对象(由resolveConflict方法返回的对象)能存入这两个数据库中。所以当复制完成时,这两个数据库中被复制的对象就同步了。
最后,db4o最强大的特性之一就是它能毫不费力地容忍类结构的演变。假设,在向数据库内加入数百个TestSuite对象之后,我们决定这个类必须要被修改。就是说,我们已经被告之,该系统必须能追踪到每个TestSuite的执行QA工程师,所以必须加入如下字段
private int engineerID;
到TestSuite类的定义中。
现在我们就遇到了两个相关联的问题。第一个问题不是很糟糕:已存在于数据库中的用于表示测试的TestSuite对象,并没有为它们记录QA工程师的ID,所以我们将不得不将一个虚拟的值赋给这些对象的engineerID字段;该值会指出"未记录QA工程师的ID"。第二个问题就更难应付了:我们必须不知原因地把已有的TestSuite对象移植到"新的"类结构中。我们必须为数据库中所有已存在的TestSuite对象加上一个engineerID字段。除了把旧数据库中的对象复制到一个中间性的文件,然后重新创建一个数据库这种方法之外,我们还能怎么做呢?
幸运地是,使用db4o,我们确实什么都不用做。为了能操纵新的engineerID字段,以完成业务逻辑上所要求的变化,如果就只是向(我们的应用程序中的)TestSuite类添加一个engineerID字段,我们完全不必触动db4o API的任何调用。当db4o使用"新的"TestSuite类结构去读取"旧的"TestSuite对象,db4o将认为这些对象中的engineerID字段消失了,并且会优雅地把该字段的值设为0。如果我们把0当作"未记录QA工程师的ID",那么我们所做的移植就完成了。写入到数据库中的新TestSuite对象将会包括新字段。(事实上,对旧的TestSuite对象本身进行重写,会使db4o不被察觉地为这些对象加上新字段。)所以,通过发布一个包含新的TestSuite定义的更新应用,我们完全可以不被察觉地从旧的TestSuite对象移植到新的...正如前述应用所做的那样。
全方位数据库
通过适当地应用,db4o就能成为数据库中的"瑞士军刀"。它占用足够小的内存空间,使它能够被包含在一个不需要消耗大量资源的项目中。同样地,一个数据库只在磁盘上占用一个文件的事实可能会使人们在第一眼看到它时,不能认识到它丰富的功能。将数据库从一个地方移到另一个地方就是一个简单的文件拷贝;你不必担心分离的索引文件,数据文件,数据库结构文件等等这些文件的位置。对于快速部署和零管理的数据库应用,db4o很能胜任。
另外,根据我们已多次描述过的,db4o在简单性和优雅之间达到了适度的平衡。db4o的QBE既如此的简单,又如此的功能强大,对于一组令人惊奇的应用,它经常是我们唯一需要的查询API。如果你主要是通过指针导航而不是查询去访问数据库,QBE就特别有吸引力。在这种情况下,QBE经常能高效地拿到一个对象网络(Object Network)的根对象。然后,你就能使用db4o的激活(activation)功能从根部向下进行对象引用导航,如果这些对象都完全在内存中的话,你就更可以这么做了。
而在使用QBE并不高效的时候,原生查询和S.O.D.A.就能派上用场了,并且它们伴随着一堆特性和低层次的API。我们还没有展示db4o的加密功能,插入式文件I/O(例如,它允许你添加"写后读(read-after-write)"验证),信号量,客户端/服务器端模式,以及其它难以计数的功能。我们结论性的建议很简单:当你的下一个Java应用需要一个数据库,在最终开始编码之前,你可以去访问一下http://www.db4objects.com/。这是很值得的。
这是Rick Grehan发表在TheServerSide上的一篇关于面向对象数据库--db4o的文章,较全面地介绍了db4o的关键特性,希望对大家认识db4o能有所帮助。(2007.12.07最后更新)
db4o-针对对象的数据库-是一个完全的对象数据库;它以使对象在其生命周期中-无论是在数据库内或是在外-都保持着它们的本性这样一种方式操纵对象。不论类的复杂性如何,对象的内容,结构和关系都能够被保存。
更准确地说,db4o是一个数据库引擎,你只要将它的一个jar文件包含到你的数据库应用的类路径中就可以使用它了(至少对于Java是这样的)。所以,db4o运行在与你的应用程序相同的进程空间中,并能被直接地调用;它不需要类似于在ODBC或JDBC中使用的驱动文件。db4o存在针对Java,.NET和Mono的版本;它们在功能上都彼此相等。(事实上,使用.NET创建的db4o数据库也能由Java程序访问;反之亦然。)
db4o是开源的。可执行文件,源代码和文档可从http://www.db4objects.com/中下载。广泛的例子程序,和一个活跃的用户社区一样,也都可以从这个站点中找到。
db4o最引人的特性之一就是它在简易性与强大的功能之间的显著平衡。一方面,它的API是如此地易于掌握和方便使用,即使是初学者也能在相同的时间内创建一个功能完备的数据库对象。另一方面,这些相同的API也提供了更底层的能够深入调用数据库引擎的方法,以允许核心开发者为了得到适当的性能而能深入到该引擎的内部中去调整db4o的工具。
db4o的特性就是最好的证明--这胜过只是讨论--所以我们将通过示例这种方法去证明db4o。然而,我们必须牢记本文通篇只是展示了db4o特性中的一部分罢了。感兴趣的朋友会发现为了知晓该数据库引擎的全部功能而去查阅db4o的文档所花的时间是值得的。
db4o基础
让我们以初学者使用db4o时可能会做的事情开始:定义了一些类,然后持久化这些类的对象。我们所假定的类为同样也是假定的QA项目做一个跟踪测试的系统。我们的系统由两个类组成,首先是TestSuite类:
public class TestSuite {
private String name; // Test Suite name
private String description;
private String configuration;
private char overallScore;
private ArrayList <TestCase> cases;
private long dateExec;
... <remainder of TestSuite definition> ...
}
TestSuite是TestCase对象的容器,(一个测试用例就是一个可单独执行的测试程序--相关的测试用例组合成一个测试组)。测试组使用额外的,全局的数据成员,每个数据成员的用途也是相对明显的:configuration记录被测试的指定系统;overallScore是对整个测试组一个简要的评分('P'代表通过,'F'代表失败,'B'代表被阻塞,等等。);dateExec是一个毫秒级的域,标识该测试组被执行时的日期与时刻。是ArrayList对象的cases含有单个的测试用例,由TestCase类定义:
public class TestCase {
private String name;
private String comment;
private char status;
private long duration;
private float result;
... <remainder of TestCase definition> ...
}
每个测试用例都有一个名称,形式自由的注释字段,状态(通过或失败),持续时间和结果(例如,为了与测试-字节/秒的吞吐量-的任意数据关联)。
因为我们所关注是db4o的使用,所以我们不想在描述这些类的使用细节上停留。就让我们简单地说,我们已经执行了一个特别地测试组中所有的测试用例,将测试的结果存放在一个TestSuite对象中(与TestCase对象相关的ArrayList对象cases中),然后关闭数据库。是不是太容易了。
// Create the database
new File("testsuites.YAP").delete();
ObjectContainer db = Db4o.openFile("testsuites.YAP");
// Store the TestSuite object
db.set(testsuite);
// Close the database
db.close();
就是它。弹指一挥间,你就已经做到了。(当然,为了保持简洁,我们去掉了创建TestSuite对象和它的TestCase组件的细节)
停下来想想上述代码做了什么事情。特别要考虑你没有看到的--db4o已经做了但还未被告之的事情。
首先,我们不需要告诉db4o任何关于TestSuite类的结构的信息;不需要我们的帮助,db4o就能发现这个结构。利用Java反射机制的能力,db4o测定TestSuite类的结构,并勾勒出该类的装配方式以推导出此类对象的成员与关键数据。
第二,我们不必建议db4o去关注ArrayList。不仅我们不必将ArrayList的大小告诉db4o,而且我们也不必把它里面的内容告诉db4o。正如db4o在它刚接触testsuite对象时就能够发现它所需要的一切,db4o也能知道它所需要的关于(在ArrayList中的)TestCase对象的所有信息。
结果就是,如果我们把testsuite作为一个任意宠大而复杂的对象树的根,db4o能找到并存储整个树而不需要我们的任何协助。所以,存储这个处于根部的对象testsuite也就是存储了整个ArrayList对象。
最后,我们也没有必须要求db4o以事务的保护性方式去调用set方法。任何会修改ObjectContainer(表示数据库的db4o对象)的调用都会自动地开启一个事务,除非已经有一个活跃的事务了。此外还会调用close方法去终止这个事务,所以上述代码等价于:
db.startTransaction();
db.set(testsuite);
db.commitTransaction();
db.close();
此处的startTransaction和commitTransaction方法是为了证明我们的观点而虚构的。db4o也确实有显示地提交或中止事务的方法,但为了使原先的代码足够的简洁我们没有使用这些方法。db4o隐式的事务使得数据库能够一直处于一致的状态;一旦commit方法已经执行了,数据库的完整性就能够得到保证,甚至是发生了灾难性失败也能如此。
查询I - QBE
有了已经存于数据库中的对象,下一步我们想要展示的操作将肯定就是查询和恢复。db4o提供了三种查询API:有一种简单,有一种优雅,还有一种则复杂。每一种API都有其所长,并适用于不同的查询条件。以db4o的眼光来看,你选择哪一种API并没有关系:它们都是可兼容的。
我们以简单的API开始:query by exampel(QBE)。
使用QBE是如此的容易:为你的查询目标构建一个'模板'对象,然后把它传入ObjectContainer的query方法。实际上,你就是告诉db4o'去拿到所有与这个对象看起来一样的对象'。(这与JavaSpaces查询API非常相似;为了清楚地了解如何处理基本数据类型,可以看下面的内容,db4o的处理方式与JavaSpaces不同。也要注意,JavaSpace Entry对象期望使用public字段,db4o则没有这种要求。)
假设一个测试组名为"Network Throughput",我们想取出这个测试组执行的所有测试以便我们能确定失败了的测试所占的百分比(基于TestSuite的overalScore值)。使用QBE,完成该工作的代码如下:
// Open the database
ObjectContainer db = Db4o.openFile("testsuites.YAP");
// Instantiate the template object, filling in
// only the name field
testTemplate = new TestSuite("Network Throughput");
// Execute the query
ObjectSet result = db.get(testTemplate);
fails = 0.0f;
total = 0.0f;
// Step through results,
while(result.hasNext())
{
testsuite = (TestSuite)result.next();
if(testsuite.getOverallScore()=='F')
fails += 1.0f;
total += 1.0f;
}
if(total == 0.0f)
System.out.println("No tests of that type found.");
else
{
System.out.println("Percent failed: " + (fails/total * 100.0f) + "%");
System.out.println("Total executed: " + total);
}
db.close();
在上面的代码中,testTemplate是QBE的模板对象。注意,只有它的name字段有真实的值;所有其它的成员变量不是为null就是为0。Null或0字段不参与QBE查询;因此,调用db.get方法就会返回在该数据库中name字段匹配"Network Throughput"的所有TestSuite对象。匹配的TestSuite对象将返回在一个ObjectSet结果对象中。上述代码遍历该结果,取出对象,然后计算结果并展示出来。
QBE明显的优点就是它的简易性。不需要掌握其它单独的查询语言。另外,QBE也是类型安全的:你不需要创建一个类似于SQL的查询语句
SELECT TestSuite.overallScore FROM TestSuite WHERE TestSuite.name = 200.0
另一方面,由于该查询是由Java代码创建的,编译器不会允许你把一个浮点值赋给一个String字段;反之亦然。
QBE明显的缺点就是它只能执行"等于"查询。另外,QBE使用null值去确定不参与查询的String或对象引用成员变量,使用0值去指定不参与查询的数字字段。所以,例如,我不能发明一个QBE查询去获得所有result字段的值为0的TestCase对象。
更为精细的查询要求一个能力更强的查询机制,而db4o恰恰就有这样一种机制。
查询方式II - 原生查询(Native Query)
db4o的原生查询系统应该是能想像得到的最具弹性的查询机制。不像使用数据库查询语言去构建查询,你是使用"无格式的普通Java语句"去构造原生查询。原生查询用两种手段去实现这件不可思意的工作:一个是Predicate类;另一个是QueryComparator接口。这个类包含一个可重载的(overrideable)回调方法,该方法将指定如何从数据库中选择对象(如果你愿意,你将会看到查询语句的主体....)。这个接口只声明了一个方法,该方法用于指明如何对查询结果进行排序。
作为一个例子,我们假设想找到在给定的一周内执行过了的总得分为"failed",但与之关联的测试用例中有超过一半的被评为"passed"的测试组。这不是一个简单的"等于"查询,所以它不能使用QBE构建。
然而,db4o的原生查询可以直接地生成该查询。首先,我们继承db4o的Predicate类:
// Predicate class sub-class for native query example
public class NativeQueryQuery extends Predicate<TestSuite>
{
ObjectContainer db;
private long startdate;
private long enddate;
// 构造器要在本地获得ObjectContainer对象和日期范围
public NativeQueryQuery(ObjectContainer _db,
long _start, long _end)
{
db = _db;
startdate = _start;
enddate = _end;
}
// 这就是查询的主体
public boolean match(TestSuite testsuite)
{
float passed;
float total;
TestCase testcase;
// 判断testsuite是否在指定的日期范围内
if(testsuite.getDateExec()<startdate ||
testsuite.getDateExec()>enddate) return false;
// 如果该测试组对象中没有测试用例对象,则拒绝将该测试组对象放入查询结果中
if(testsuite.getNumberOfCases()==0)
return false;
// 检查该测试组对象中的测试用例的通过率是否超过50%
passed = 0.0f;
total = 0.0f;
for(int i=0; i<testsuite.getNumberOfCases(); i++)
{
testcase = testsuite.getTestCase(i);
if(testcase.getStatus()=='P')
passed+=1.0f;
total+=1.0f;
}
if((passed/total)<.5) return false;
return true;
}
}
注意在这个类的使用中使用了Java泛型语义,这就是告诉db4o只去取TestSuite对象。当查询执行时,TestSuite对象就会传入match方法(我们之前提到过的回调方法),如果传入的TestSuite对象符合查询规范该方法就返回true,否则就返回false。
match方法中的代码首先确定侯选对象是否是在一周的日期范围内。如果是,则循环该对象中的成员变量测试用例的对象,计算出所有通过了的测试用例的总数。如果,得到的通过率小于50%,该测试组就被拒绝;否则,就让它通过。
我们可以使用如下的代码准确地展示该查询程序:
. . .
TestSuite testsuite;
NativeQueryQuery nqqClass;
Date now;
// Open the database
ObjectContainer db = Db4o.openFile("testsuites.YAP");
// Instantiate a NativeQueryQuery object,
// setting the start and end dates for
// any test in the past week
// 604800000 = milliseconds in a week
now = new Date();
nqqClass = new NativeQueryQuery(db,
now.getTime()-604800000L,
now.getTime());
// Execute the query and display the
// results
System.out.println("Results:");
ObjectSet results = db.query(nqqClass);
if(results.isEmpty())
System.out.println(" NOTHING TO DISPLAY");
while(results.hasNext())
{
testsuite = (TestSuite)(results.next());
System.out.println(testsuite.toString());
}
db.close();
. . .
可以把原生查询想像成这样:目标类的对象一个接一个从数据库中取出,然后把它们传入match方法中。只有那些被match方法返回true的对象才会置于查询结果ObjectSet对象中。基本上可以说,如果你会知道如何写Java代码,那么你就知道如何写原生查询。
那么排序呢?如果想按日期的升序排列查询结果,我们就要实现QueryComparator接口,如下所示:
public class NativeQuerySort implements QueryComparator<TestSuite>{
public int compare(TestSuite t1, TestSuite t2)
{
if (t1.getDateExec() < t2.getDateExec()) return -1;
if (t1.getDateExec() > t2.getDateExec()) return 1;
return 0;
}
}
compare方法的作用十分明显。那些在查询中得以胜出的对象会成对的传入compare方法,如果第一个对象会排在第二个对象之前,相同或之后的位置,该方法就会分别返回一个小于,等于或大于0的值。为了准确地说明对查询结果的排序,我们实例化NativeQuerySort,并把对query方法的调用修改成如下:
. . .
// Instantiate the sort class
nqsClass = new NativeQuerySort();
. . .
ObjectSet results = db.query(nqqClass, nqsClass);
. . .
其它的代码仍然与原先的保持一致。
好怀疑的读者可能会抱怨道,原生查询只是一种编程上的小伎俩--相比较于直接去拿到所有的TestSuite对象然后再排除其中不符合条件的对象这样的程序,原生查询并不快。
是的,但并不十分准确。原生能够被优化。你所需要做的只是把两个jar文件--db4o-xxx-nqopt.jar(xxx表示db4o的版本)和bloat.jar--置于CLASSPATH环境变量中。在查询执行的时候,这些类库中的代码会对(在match方法中)例如基本数据类型比较,算术和布尔表达式,简单的对象成员访问,以及更多方面的结构进行优化。这个被支持的优化的列表在不停的增长,因为db4o引擎还在扩展优化的范围。
查询方式III - S.O.D.A.
db4o独一无二的能力之一就是它的API被分层了。开发者能够选择通过高层次--赋予数据库引擎相当大的自由度,让它决定如何去实现它的操作--或者开发者也可以使用一种更直接地方式去访问db4o。后一种选择为程序员平添了更多的负担,程序员必须更加小心地引导数据库引擎的内部工作。但回报就是得到一个更快,能力更强的数据库应用。
db4o的S.O.D.A.(Simple Object Data Access)查询机制就是该层次API的一个完美的例子。S.O.D.A.就是db4o的内部查询系统--QBE和原生查询都被翻译成了S.O.D.A.。然而,应用程序也能直接地调用S.O.D.A.。
假设我们想找到所有名为"Network Throughput",且至少拥有一个其result字段--我们使用这个参数作为字节/秒的量度--不小于指定值(比方说,100)的测试用例的测试组。为该请求而做的S.O.D.A.查询可能就像这样:
. . .
TestSuite testsuite;
// Open the database
ObjectContainer db = Db4o.openFile("testsuites.YAP");
// Construct the query
Query query = db.query();
query.constrain(TestSuite.class);
Constraint nameConst = query.descend("name").
constrain("Network Throughput");
query.descend("cases").descend("result").
constrain(100.0f).smaller().and(nameConst);
System.out.println("Results:");
// Execute the query
ObjectSet result = query.execute();
if(result.isEmpty())
System.out.println("NOTHING TO DISPLAY");
while(result.hasNext())
{
testsuite = (TestSuite)(result.next());
System.out.println(testsuite.toString());
}
db.close();
. . .
在由Illustration 1所示图表的帮助下,这些有点儿神秘的代码就变得不那么神秘了。该程序所构建的归总起来就是一个用于指导底层数据库引擎的查询图(Query Graph)。descend方法创建了该图的一个分支,该分支向下步入对象的结构中。每个descend方法就在这个树中构建一个结点,可以在这些结点上再附上一个约束(使用constrain方法)。用SQL的话来说,约束指定了查询的"WHERE"子句部分。多个约束可以在与(and)或或(or)方法的协助下结合起来。在上面的查询中我们已经使用了and方法去关联这些约束。
与其它的查询方式一样,查询结果返回到ObjectSet对象中,通过遍历该对象就可取出那些拿到的对象。
注意,由于S.O.D.A.是一种低层次的访问方法,没有智能的指示,它就没有默认的行为。访问cases对象的成员变量result字段的代码很简单
query.descend("cases").descend("result"). ...
我们并没有告诉S.O.D.A."cases"是一个集合对象。所以当查询执行时,它会不被察觉地检测ArrayList对象cases中所有元素(TestCase对象)的result字段,然后会正确地返回那些拥有符合搜索规范的测试用例的测试组。
db4o性能调优
我们已经展示了db4o的基本操作(但无关紧要的删除操作除外,下面将会提到它)。但,正如我们在本文通篇所提到的,db4o发布(expose)了一个API层次结构,以允许开发者能够选择以何种层次的API去控制建立在该数据库引擎之上的应用程序。从另一方面看,如果你所想做的只是向数据库存入,及从数据库取出对象,那么你就已经看到了你所需要的一切。然而,如果你的应用的需求超出了添加,更新,查询和删除,可能还有一个db4o的特性可解决你的问题。
db4o的ObjectContainer实际上发布(expose)了两个API。第一个API非常的简单,由十个方法组成。这些方法处理数据库的打开与关闭;添加,更新,查询和删除对象;及提交或中止事务。短言之,该API为你提供了在操纵数据库时所需要所有功能。然而,该API中的一个方法--ext()--是进入"被扩展的"ObjectContainer的一个入口。该被扩展的ObjectContainer为深入控制db4o的内部发布(expose)了更多方法。例如,你可以获得并改变数据库的配置上下文,使用它你能够修改该引擎的行为。
例如,假设你已经从数据库中拿到了一个TestSuite对象,发现该对象中的数据是错误的,并决定该对象应该被删除。此外,你还决定你必须删除的不仅是该TestSuite对象,而且还有所有与之关联的TestCase对象(在ArrayList对象cases中)。
你是可以冗长而乏味地遍历这个ArrayList对象,一个接一个地删除每一个TestCase对象,然后再删除TestSuite对象本身。可能一种更好的解决方案是为这个TestSuite对象启用db4o的"级联删除"特性。
. . .
// Fetch the database's configuration context
Configuration config = db.ext().configure();
// Get the ObjectClass for TestSuite
ObjectClass oc = config.objectClass("testsuites.TestSuite");
// Turn on cascaded delete
oc.cascadeOnDelete(true);
... ...
db.delete(ts1);
. . .
在上述代码中,我们实例化了一个ObjectClass对象,该对象使我们能够访问到TestSuite对象的db4o内部表现形式。我们打开cascadeOnDelete标记,以便当执行db4o.delete(ts1)时,不仅ts1对象会被删除,而且所有由ts1引用的TestCase对象也会被删除。(由于显而易见的原因,默认情况下,级联删除是被关闭的)
作为另一个例子,假设你想为数据库预分配存储空间,以至于要最小化磁盘驱动器的头移动(head movement)。(最好是在硬盘碎片整理之后,并新创建一个数据库时这样做。)并且假设以ObjectContainer对象db作为打开了的数据库:
// Fetch the database's configuration context
// 获得数据库的配置上下文
Configuration config = db.ext().configure();
// Pre-allocate 200,000 bytes
// 预分配200000000字节
config.reserveStorageSpace(200000000L);
把数据库文件预扩展到200000000字节(略小于200兆字节)。假设该磁盘已经做碎片整理了,那么被分配的块就是连续的,这可显著提升数据库的访问。
db4o高级应用
完全可以说,db4o在它不大的空间(约500K)内已装入了足够多的特性,相比较于db4o在运行过程中所做的众多事情,我们不能花费更多的笔墨去解释它们了。但是有两个特性十分突出,以至于肯定要提到它们。
db4o的对象复制实现了被总称为面向对象版的数据库同步。使用复制所提供的功能,你能为一个数据库中的一个对象做一个副本,并将该副本放入另一个数据库中。使用这样的方法,副本对象就无形中和原始对象关联在了一起。对任一对象--原始对象或副本对象--的改变都会被跟踪到,以便在之后的某个时候,数据库能够被重组,并且这两个数据库中对象的不同之处可被分解(例如,可同步这两个数据库)。
它工作起来就像这样:为使一个数据库可被复制,与事务计数器一样,该数据库中被创建的任何一个对象都用一个唯一全局标识符(UUID)进行了标记。当你从原始数据库中"复制"一个对象到另一个数据库中,副本对象会带着与它的原始对象相同的UUID和事务计数器。副本数据库现在就可以从它的原始数据库那儿弄走了。修改副本对象的内容将会导致对象的事务计数器被修改。所以,当这两个数据库重新连接起来,db4o内建的同步处理机制就能一个对象一个对象地进行正确的匹配(使用UUID),并确定原始或副本对象是否已经改变了。db4o甚至能追踪到每个对象发生最后一次修改时的时间,以便用户写的冲突解决代码能确定哪个对象是最近更新的。
从操作行为来看,db4o的同步处理机制与原生查询十分相似。回想一下,当实现了一个原生查询类时,我们要定义一个match方法,该方法确定哪些对象符合(或不符合)查询规范。使用同步复制,我们要定义一个ReplicationProcess对象,我们会把冲突处理对象传入该方法中。这些Java代码可能像这样:
. . .
ReplicationProcess replication = db1.ext().
replicationBegin(db2, new ReplicationConflictHandler()
{
public Object resolveConflict(
ReplicationProcess rprocess, Object a, Object b)
{
. . . ...
return winning_object;
}
)
};
在上述代码中,Object a是来自于数据库db1的对象,Object b则来自于数据库db2。默认情况下,同步是双向的。同步处理保证胜出的对象(由resolveConflict方法返回的对象)能存入这两个数据库中。所以当复制完成时,这两个数据库中被复制的对象就同步了。
最后,db4o最强大的特性之一就是它能毫不费力地容忍类结构的演变。假设,在向数据库内加入数百个TestSuite对象之后,我们决定这个类必须要被修改。就是说,我们已经被告之,该系统必须能追踪到每个TestSuite的执行QA工程师,所以必须加入如下字段
private int engineerID;
到TestSuite类的定义中。
现在我们就遇到了两个相关联的问题。第一个问题不是很糟糕:已存在于数据库中的用于表示测试的TestSuite对象,并没有为它们记录QA工程师的ID,所以我们将不得不将一个虚拟的值赋给这些对象的engineerID字段;该值会指出"未记录QA工程师的ID"。第二个问题就更难应付了:我们必须不知原因地把已有的TestSuite对象移植到"新的"类结构中。我们必须为数据库中所有已存在的TestSuite对象加上一个engineerID字段。除了把旧数据库中的对象复制到一个中间性的文件,然后重新创建一个数据库这种方法之外,我们还能怎么做呢?
幸运地是,使用db4o,我们确实什么都不用做。为了能操纵新的engineerID字段,以完成业务逻辑上所要求的变化,如果就只是向(我们的应用程序中的)TestSuite类添加一个engineerID字段,我们完全不必触动db4o API的任何调用。当db4o使用"新的"TestSuite类结构去读取"旧的"TestSuite对象,db4o将认为这些对象中的engineerID字段消失了,并且会优雅地把该字段的值设为0。如果我们把0当作"未记录QA工程师的ID",那么我们所做的移植就完成了。写入到数据库中的新TestSuite对象将会包括新字段。(事实上,对旧的TestSuite对象本身进行重写,会使db4o不被察觉地为这些对象加上新字段。)所以,通过发布一个包含新的TestSuite定义的更新应用,我们完全可以不被察觉地从旧的TestSuite对象移植到新的...正如前述应用所做的那样。
全方位数据库
通过适当地应用,db4o就能成为数据库中的"瑞士军刀"。它占用足够小的内存空间,使它能够被包含在一个不需要消耗大量资源的项目中。同样地,一个数据库只在磁盘上占用一个文件的事实可能会使人们在第一眼看到它时,不能认识到它丰富的功能。将数据库从一个地方移到另一个地方就是一个简单的文件拷贝;你不必担心分离的索引文件,数据文件,数据库结构文件等等这些文件的位置。对于快速部署和零管理的数据库应用,db4o很能胜任。
另外,根据我们已多次描述过的,db4o在简单性和优雅之间达到了适度的平衡。db4o的QBE既如此的简单,又如此的功能强大,对于一组令人惊奇的应用,它经常是我们唯一需要的查询API。如果你主要是通过指针导航而不是查询去访问数据库,QBE就特别有吸引力。在这种情况下,QBE经常能高效地拿到一个对象网络(Object Network)的根对象。然后,你就能使用db4o的激活(activation)功能从根部向下进行对象引用导航,如果这些对象都完全在内存中的话,你就更可以这么做了。
而在使用QBE并不高效的时候,原生查询和S.O.D.A.就能派上用场了,并且它们伴随着一堆特性和低层次的API。我们还没有展示db4o的加密功能,插入式文件I/O(例如,它允许你添加"写后读(read-after-write)"验证),信号量,客户端/服务器端模式,以及其它难以计数的功能。我们结论性的建议很简单:当你的下一个Java应用需要一个数据库,在最终开始编码之前,你可以去访问一下http://www.db4objects.com/。这是很值得的。
评论
3 楼
zhulongxing_sz
2012-06-14
2楼的,何出此言呢?你不了解就不要乱发炎···
你测试过 db4o 的一些高级特性吗?你知道 db4o 在百万数量级存储和查询复杂对象(带有数组和集合对象引用)时的速度表现吗?那不是一般的快,关键是你要深入了解其特性···
兄弟,建议你回到妈妈身边再喝两年奶吧···
你测试过 db4o 的一些高级特性吗?你知道 db4o 在百万数量级存储和查询复杂对象(带有数组和集合对象引用)时的速度表现吗?那不是一般的快,关键是你要深入了解其特性···
兄弟,建议你回到妈妈身边再喝两年奶吧···
2 楼
学会做人
2009-06-18
这个性能其实不怎么样!
1 楼
天机老人
2009-02-23
哇,全看完了,呵呵确实不错,但是还是没有抵消我心中的念头,内存占用量怎么样!?
相关推荐
这些资源有助于开发者深入研究db4o的内部工作原理,以及如何在实际项目中有效利用db4o。 ### 五、应用领域 db4o广泛应用于移动应用、嵌入式系统、游戏开发、数据分析等领域,尤其是在需要快速存储和检索复杂对象...
**源码包含** 在此版本中,开发者可以深入理解db4o的内部工作原理,这对于进行自定义扩展或者调试是非常有用的。源码的开放性也使得社区能够持续维护和改进这个项目,尽管官方停止了更新。 **jar包** 是db4o的核心...
本教程旨在帮助用户快速掌握db4o的基本用法,并提供丰富的资源以支持开发者深入学习。 在开始之前,请确保从db4o官方网站 (developer.db4o.com) 下载最新版本的db4o发行版。此外,db4o社区鼓励用户加入论坛进行交流...
《db4o 权威指南》是一本深入探讨db4o这一开源面向对象数据库系统的专业书籍,对于Java开发者来说尤其有价值。db4o是Database for Objects的缩写,它允许开发者以自然、直观的方式存储和检索Java对象,无需编写SQL...
这个压缩包包含了关于Db4o的详细资料,适合想要深入学习和研究Db4o的人员。 Db4o的核心概念: 1. **对象持久化**:Db4o将对象模型直接存储到数据库中,对象的状态在数据库和内存之间无缝切换。这使得对象的生命...
**对象数据库db4o详解** ...通过`DrawTools2005`这样的示例程序,开发者可以深入理解如何将db4o集成到实际项目中,提升开发效率。无论是学习还是实际开发,db4o都是一个值得探索的对象数据库解决方案。
【db4o中文指南】 db4o是一个专为Java和.NET开发者设计的开源、轻量级的纯面向对象数据库引擎,提供了一种简单...通过深入理解和熟练使用db4o,开发者可以创建出更符合对象思维的数据库应用,提升开发效率和系统性能。
- 完整的全源码和注解:为了深入理解DB4O的工作原理和最佳实践,开发者通常需要参考完整的示例代码和详细的注释。这些资源可以帮助你更好地了解如何在实际项目中集成和使用DB4O。 在实际开发中,DB4O提供了强大的...
对于想要深入了解db4o特性和高级功能的开发者,API文档和PDF教程提供了详尽的信息。这些资源覆盖了db4o的所有方面,从数据类型和事务管理,到查询语言和索引策略。 ### 四、参与db4o社区 db4o鼓励用户加入其公共...
在本实例中,我们将深入探讨DB4o的使用方法,以及如何结合Visual Studio 2008进行开发。 1. **对象数据库与关系数据库对比**: 对象数据库与传统的关系数据库不同,它直接支持面向对象编程的概念,如类、继承、...
在本文中,我们将深入探讨如何利用DB4o将一个对象保存到文件中,然后进行查询读取。 首先,让我们了解一下DB4o的基本工作原理。DB4o的核心思想是对象持久化,即将内存中的对象实例存储到磁盘上,以便在后续的程序...
【db4o6.4-java】是一个针对Java平台的数据库引擎开发包,它包含了db4o的源代码、测试代码以及...通过深入了解和使用db4o-6.4,你可以掌握对象数据库的基本原理和实践,为你的项目提供一个高效、灵活的数据管理方案。
Java数据库db4o,全称为“Database for Objects”,是一款开源的对象数据库系统,专门设计用于Java...在压缩包中的“db4o”文件可能包含了db4o的库文件、示例代码、文档等资源,可以帮助深入理解和使用这个数据库系统。
通过这份教程,用户可以深入理解DB4O的工作原理,学习如何将对象持久化到数据库中,以及如何利用其强大的查询功能。 在DB4O 8.0中,Object Manager是一个重要的组件,它提供了可视化界面,用于查看和操作数据库中的...
对于那些需要深入理解对象数据库工作原理或希望定制数据库行为的开发团队,DB4O的开源特性更是极具吸引力。然而,随着技术的发展,DB4O的维护已经停止,因此在考虑使用时需要评估其长期支持和社区活跃度。
- **首次接触**:在深入探讨具体的代码示例之前,我们先来熟悉一下db4o的基本概念和组件。了解db4o引擎的工作原理以及如何将对象持久化到数据库中。 - **基本操作**:学习如何使用db4o进行对象的存储、查询、更新和...
db4o(Object Database for ...源码和核心jar文件的提供为开发者提供了深入学习和定制db4o的机会,使其能够在Android开发中发挥重要作用。通过理解和熟练使用这些API,开发者可以创建高效、灵活的数据存储解决方案。
在《db4o帮助文档英文版》中,你可以找到关于db4o全面且深入的信息。这份官方文档是7.4正式版,意味着它包含了该版本的所有最新特性和改进。以下是文档中可能涵盖的一些关键知识点: 1. **对象持久化**:db4o如何将...
在“让文本文档做数据库的东东”这个主题中,我们将深入探讨DB4O如何通过文本文档实现数据存储,以及这种存储方式的优势和应用场景。 DB4O的工作原理是将Java或.NET中的对象序列化为字节流,然后将其存储在一个二...
本指南的第三部分将深入探讨如何在db4o中进行数据库重构,以优化数据结构并提升应用程序性能。 ### 1. 对象数据库的概念 对象数据库与关系型数据库不同,它存储的是对象而非表格数据。db4o将Java对象直接持久化到...