一段时间以来,在 面向 Java 开发人员的 db4o 指南 中,我查看了各种使用 db4o 存储 Java 对象的方法,这些方法都不依赖映射文件。使用原生对象数据库的其中一个优点就是可以避免对象关系映射(也许这不是重点),但我曾用于阐述这种优点的对象模型过于简单,绝大多数企业系统要求创建并操作相当复杂的对象,也称为结构化对象,因此本文将讨论结构化对象的创建。
结构化对象 基本上可以看成是一个引用其他对象的对象。尽管 db4o 允许对结构化对象执行所有常用的 CRUD 操作,但是用户却必须承受一定的复杂性。本文将探究一些主要的复杂情况(比如无限递归、层叠行为和引用一致性),以后的文章还将深入探讨更加高级的结构化对象处理问题。作为补充,我还将介绍探察测试(exploration test):一种少为人知的可测试类库及 db4o API 的测试技术。
从简单到结构化
清单 1 重述了我在介绍 db4o 时一直使用的一个简单类 Person
:
清单 1. Person
package
com.tedneward.model;
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
public
class
Person
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
public Person()
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{ }
public Person(String firstName, String lastName, int age, Mood mood)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.mood = mood;
}
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public String getFirstName() ...{ return firstName; }
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public void setFirstName(String value) ...{ firstName = value; }
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public String getLastName() ...{ return lastName; }
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public void setLastName(String value) ...{ lastName = value; }
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public int getAge() ...{ return age; }
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public void setAge(int value) ...{ age = value; }
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public Mood getMood() ...{ return mood; }
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public void setMood(Mood value) ...{ mood = value; }
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
public String toString()
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
return
"[Person: " +
"firstName = " + firstName + " " +
"lastName = " + lastName + " " +
"age = " + age + " " +
"mood = " + mood +
"]";
}
public boolean equals(Object rhs)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
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;
private Mood mood;
}
这个简单的 Person
类在用于介绍基本 db4o 存储、查询和检索数据操作时行之有效,但它无法满足真实世界中企业编程的复杂性。举例而言,数据库中的 Person
有家庭地址是很正常的。有些情况下,还可能需要配偶以及子女。
若要在数据库里加一个 “Spouse” 字段,这意味着要扩展 Person
,使它能够引用 Spouse
对象。假设按照某些业务规则,还需要添加一个 Gender
枚举类型及其对应的修改方法,并在构造函数里添加一个 equals()
方法。在清单 2 中,Person
类型有了配偶字段和对应的 get/set 方法对,此时还附带了某些业务规则:
清单 2. 这个人到了结婚年龄吗?
package
com.tedneward.model;
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
public
class
Person
...
{
// . . .
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public Person getSpouse() ...{ return spouse; }
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public void setSpouse(Person value) ...{
// A few business rules
if (spouse != null)
throw new IllegalArgumentException("Already married!");
if (value.getSpouse() != null && value.getSpouse() != this)
throw new IllegalArgumentException("Already married!");
spouse = value;
// Highly sexist business rule
if (gender == Gender.FEMALE)
this.setLastName(value.getLastName());
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
// Make marriage reflexive, if it's not already set that way
if (value.getSpouse() != this)
value.setSpouse(this);
}
private Person spouse;
}
清单 3 中的代码创建了两个到达婚龄的 Person
,代码和您预想的很接近:
清单 3. 去礼堂,要结婚了……
import
java.util.
*
;
import
com.db4o.
*
;
import
com.db4o.query.
*
;
import
com.tedneward.model.
*
;
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
public
class
App
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
public static void main(String[] args)
throws Exception
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
ObjectContainer db = null;
try
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
db = Db4o.openFile("persons.data");
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
Person ben = new Person("Ben", "Galbraith",
Gender.MALE, 29, Mood.HAPPY);
Person jess = new Person("Jessica", "Smith",
Gender.FEMALE, 29, Mood.HAPPY);
ben.setSpouse(jess);
System.out.println(ben);
System.out.println(jess);
db.set(ben);
db.commit();
List<Person> maleGalbraiths =
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
db.query(new Predicate<Person>() ...{
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public boolean match(Person candidate) ...{
return candidate.getLastName().equals("Galbraith") &&
candidate.getGender().equals(Gender.MALE);
}
});
for (Person p : maleGalbraiths)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
System.out.println("Found " + p);
}
}
finally
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
if (db != null)
db.close();
}
}
}
开始变得复杂了
除了讨厌的业务规则之外,有几个重要的情况出现了。首先,当对象 ben
存储到数据库后,OODBMS 除了存储一个对象外,显然还做了其他一些事情。再次检索 ben
对象时,与之相关的配偶信息不仅已经存储而且还被自动检索。
![](https://i-blog.csdnimg.cn/blog_migrate/c6ccbc96c20bebb0d73e8e6368158ef5.gif) |
关于本系列 信息存储和检索作为同义语伴随 RDBMS 已经有 10 余年了,但现在情况有所改变。Java 开发人员为所谓的对象关系型阻抗失配而沮丧,也不再有耐心去尝试解决这个问题。加上可行替代方案的出现,就导致了人们对对象持久性和检索的兴趣的复苏。 面向 Java 开发人员的 db4o 指南 对开放源码数据库 db4o 进行了介绍,db4o 可以充分利用当前的面向对象的语言、系统和理念。访问 db4o 主页面,立即下载 db4o。您需要用它来完成本文中的例子。 | |
思考一下,这包含了可怕的暗示。尽管可以想见 OODBMS 是如何避免无限递归 的场景,更恐怖的问题在于,设想一个对象有着对其他几十个、成百上千个对象的引用,每个引用对象又都有着其自身对其他对象的引用。不妨考虑一下模型表示子女、双亲等的情景。仅仅是从数据库中取出一个 Person
就会导致追溯到所有人类的源头。这意味着在网络上传输大量对象!
幸运的是,除了那些最原始的 OODBMS,几乎所有的 OODBMS 都已解决了这个问题,db4o 也不例外。
db4o 的探察测试
考察 db4o 的这个领域是一项棘手的任务,也给了我一个机会展示一位好友教给我的策略:探察测试。(感谢 Stu Halloway,据我所知,他是第一个拟定该说法的人。) 探察测试,简要而言,是一系列单元测试,不仅测试待查的库,还可探究 API 以确保库行为与预期一致。该方法具有一个有用的副作用,未来的库版本可以放到探察测试代码中,编译并且测试。如果代码不能编译或者无法通过所有的探察测试,则显然意味着库没有做到向后兼容,您就可以在用于生产系统之前发现这个问题。
对 db4o API 的探察测试使我能够使用一种 “before” 方法来创建数据库并使用 Person
填充数据库,并使用 “after” 方法来删除数据库并消除测试过程中发生的误判(false positive)。若非如此,我将不得不记得每次手工删除 persons.data 文件。坦白说,我并不相信自己在探索 API 的时候还能每次都记得住。
我在进行 db4o 探察测试时,在控制台模式使用 JUnit 4 测试库。写任何测试代码前,StructuredObjectTest
类如清单 4 所示:
清单 4. 影响 db4o API 的测试
import
java.io.
*
;
import
java.util.
*
;
import
com.db4o.
*
;
import
com.db4o.query.
*
;
import
com.tedneward.model.
*
;
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
import
org.junit.Before;
import
org.junit.After;
import
org.junit.Ignore;
import
org.junit.Test;
import
static
org.junit.Assert.
*
;
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
public
class
StructuredObjectsTest
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
ObjectContainer db;
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
@Before public void prepareDatabase()
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
db = Db4o.openFile("persons.data");
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
Person ben = new Person("Ben", "Galbraith",
Gender.MALE, 29, Mood.HAPPY);
Person jess = new Person("Jessica", "Smith",
Gender.FEMALE, 29, Mood.HAPPY);
ben.setSpouse(jess);
db.set(ben);
db.commit();
}
@After public void deleteDatabase()
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
db.close();
new File("persons.data").delete();
}
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
@Test public void testSimpleRetrieval()
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
List<Person> maleGalbraiths =
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
db.query(new Predicate<Person>() ...{
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public boolean match(Person candidate) ...{
return candidate.getLastName().equals("Galbraith") &&
candidate.getGender().equals(Gender.MALE);
}
});
// Should only have one in the returned set
assertEquals(maleGalbraiths.size(), 1);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
// (Shouldn't display to the console in a unit test, but this is an
// exploration test, not a real unit test)
for (Person p : maleGalbraiths)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
...{
System.out.println("Found " + p);
}
}
}
自然,针对这套测试运行 JUnit 测试运行器会生成预计输出:要么是“.”,要么是绿条,这与所选择的测试运行器有关(控制台或 GUI)。注意,一般不赞成向控制台写数据 —— 应该用断言进行验证,而不是用眼球 —— 不过在探察测试里,做断言之前看看得到的数据是个好办法。如果有什么没通过,我总是可以注释掉 System.out.println
调用。(可以自由地添加,以测试您想测试的其他 db4o API 特性。)
从这里开始,假定清单 4 中的测试套件包含了代码示例和测试方法(由方法签名中的 @Test
注释指明。)。
存取结构化对象
存储结构化对象很大程度上和以前大部分做法一样:对对象调用 db.set()
,OODBMS 负责其余的工作。对哪个对象调用 set()
并不重要,因为 OODBMS 通过对象标识符(OID)对对象进行了跟踪(参阅 “ 面向 Java 开发人员的 db4o 指南:查询、更新以及一致性”),因此不会对同一对象进行两次存储。
Retrieving 结构化对象则令我不寒而栗。如果要检索的对象(无论是通过 QBE 或原生查询)拥有大量对象引用,而每个被引用的对象也有着大量的对象引用,以此类推。这有一点像糟糕的 Ponzi 模式,不是吗?
避免无限递归
不管大多数开发者的最初反应(一般是 “不可能是这样的吧,是吗?”)如何,无限递归在某种意义上正是 db4o 处理结构化对象的真正方式。事实上,这种方式是绝大多数程序员希望的,因为我们都希望在寻找所创建的对象时,它们正好 “就在那里”。同时,我们也显然不想通过一根线缆获得整个世界的信息,至少不要一次就得到。
db4o 对此采用了折衷的办法,限制所检索的对象数量,使用称为激活深度(activation depth)的方法,它指明在对象图中进行检索的最低层。换句话说,激活深度表示从根对象中标识的引用总数,db4o 将在查询中遍历根对象并返回结果。在前面的例子中,当检索 Ben
时,默认的激活深度 5 足够用于检索 Jessica
,因为它只需要仅仅一个引用遍历。任何距离 Ben
超过 5 个引用的对象将无法 被检索到,它们的引用将置为空。我的工作就是显式地从数据库激活那些对象,在 ObjectContainer
使用 activate()
方法。
如果要改变默认激活深度,需要以一种精密的方式,在 Configuration
类(从 db.configure()
返回)中使用 db4o 的 activationDepth()
方法修改默认值。还有一种方式,可以对每个类配置激活深度。在清单 5 中,使用 ObjectClass
为 Person
类型配置默认激活深度:
清单 5. 使用 ObjectClass 配置激活深度
// See ObjectClass for more info
Configuration config = Db4o.configure();
ObjectClass oc = config.objectClass("com.tedneward.model.Person");
oc.minimumActivationDepth(10);
|
更新结构化对象
更新所关注的是另外一个问题:如果在对象图中更新一个对象,但并没有做显式设置,那么会发生什么?正如最初调用 set()
时,将存储引用了其他存储对象的相关对象,与之相似,当一个对象传递到 ObjectContainer
,db4o 遍历所有引用,将发现的对象存储到数据库中,如清单 6 所示:
清单 6. 更新被引用的对象
@Test
public
void
testDependentUpdate()
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
List<Person> maleGalbraiths =
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
db.query(new Predicate<Person>() ...{
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public boolean match(Person candidate) ...{
return candidate.getLastName().equals("Galbraith") &&
candidate.getGender().equals(Gender.MALE);
}
});
Person ben = maleGalbraiths.get(0);
// Happy birthday, Jessica!
ben.getSpouse().setAge(ben.getSpouse().getAge() + 1);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
// We only have a reference to Ben, so store that and commit
db.set(ben);
db.commit();
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
// Find Jess, make sure she's 30
Person jess = (Person)db.get(
new Person("Jessica", "Galbraith", null, 0, null)).next();
assertTrue(jess.getAge() == 30);
}
尽管已经对 jess
对象做了变动, ben
对象还拥有对 jess
的引用。因此内存中 jess Person
的更新会保存在数据库中。
其实不是这样。好的,我刚才是在撒谎。
测试误判
事实是,探察测试在某个地方出问题了,产生了一个误判。尽管从文档来看并不明显, ObjectContainer
保持着已激活对象的缓存,所以当清单 6 中的测试从容器中检索 Jessica
对象时,返回的是包含变动的内存对象,而不是写到磁盘上真正数据。这掩盖了一个事实,某类型的默认更新深度 是 1,意味着只有原语值(包括 String
)才会在调用 set()
时被存储。为了使该行为生效,我必须稍微修改一下测试,如清单 7 所示:
清单 7. 测试误判
@Test(expected
=
AssertionError.
class
)
public
void
testDependentUpdate()
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
List<Person> maleGalbraiths =
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
db.query(new Predicate<Person>() ...{
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public boolean match(Person candidate) ...{
return candidate.getLastName().equals("Galbraith") &&
candidate.getGender().equals(Gender.MALE);
}
});
Person ben = maleGalbraiths.get(0);
assertTrue(ben.getSpouse().getAge() == 29);
// Happy Birthday, Jessica!
ben.getSpouse().setAge(ben.getSpouse().getAge() + 1);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
// We only have a reference to Ben, so store that and commit
db.set(ben);
db.commit();
// Close the ObjectContainer, then re-open it
db.close();
db = Db4o.openFile("persons.data");
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
// Find Jess, make sure she's 30
Person jess = (Person)db.get(
new Person("Jessica", "Galbraith", null, 0, null)).next();
assertTrue(jess.getAge() == 30);
}
测试时,得到 AssertionFailure
,说明此前有关对象图中层叠展开的对象更新的论断是错误的。(通过将您希望抛出异常的类类型的 @Test
注释的值设置为 expected,可以使 JUit 提前预测到这种错误。)
设置层叠行为
Db4o 仅仅返回缓存对象,而不对其更多地进行隐式处理,这是一个有争议的话题。很多编程人员认为要么这种行为是有害的并且违反直觉,要么这种行为正是 OODBMS 应该做的。不要去管这两种观点优劣如何,重要的是理解数据库的默认行为并且知道如何修正。在清单 8 中,使用 ObjectClass.setCascadeOnUpdate()
方法为一特定类型改变 db4o 的默认更新动作。不过要注意,在打开 ObjectContainer
之前,必须设定该方法为 true。清单 8 展示了修改后的正确的层叠测试。
清单 8. 设置层叠行为为 true
@Test
public
void
testWorkingDependentUpdate()
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
// the cascadeOnUpdate() call must be done while the ObjectContainer
// isn't open, so close() it, setCascadeOnUpdate, then open() it again
db.close();
Db4o.configure().objectClass(Person.class).cascadeOnUpdate(true);
db = Db4o.openFile("persons.data");
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
List<Person> maleGalbraiths =
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
db.query(new Predicate<Person>() ...{
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
public boolean match(Person candidate) ...{
return candidate.getLastName().equals("Galbraith") &&
candidate.getGender().equals(Gender.MALE);
}
});
Person ben = maleGalbraiths.get(0);
assertTrue(ben.getSpouse().getAge() == 29);
// Happy Birthday, Jessica!
ben.getSpouse().setAge(ben.getSpouse().getAge() + 1);
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
// We only have a reference to Ben, so store that and commit
db.set(ben);
db.commit();
// Close the ObjectContainer, then re-open it
db.close();
db = Db4o.openFile("persons.data");
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
// Find Jess, make sure she's 30
Person jess = (Person)db.get(
new Person("Jessica", "Galbraith", null, 0, null)).next();
assertTrue(jess.getAge() == 30);
}
不仅可以为更新设置层叠行为,也可以对检索(创建值为 “unlimited” 的激活深度)和删除设置层叠行为 —— 这是我最新琢磨的 Person
对象的最后一个应用 。
删除结构化对象
从数据库中删除对象与检索和更新对象类似:默认情况下,删除一个对象时,不删除它引用的对象。一般而言,这也是理想的行为。如清单 9 所示:
清单 9. 删除结构化对象
@Test
public
void
simpleDeletion()
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
Person ben = (Person)db.get(new Person("Ben", "Galbraith", null, 0, null)).next();
db.delete(ben);
Person jess = (Person)db.get(new Person("Jessica", "Galbraith", null, 0, null)).next();
assertNotNull(jess);
}
但是,有些时候在删除对象时,希望强制删除其引用的对象。与激活和更新一样,可以通过调用 Configuration
类触发此行为。如清单 10 所示:
清单 10. Configuration.setCascadeOnDelete()
@Test
public
void
cascadingDeletion()
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
...
{
// the cascadeOnUpdate() call must be done while the ObjectContainer
// isn't open, so close() it, setCascadeOnUpdate, then open() it again
db.close();
Db4o.configure().objectClass(Person.class).cascadeOnDelete(true);
db = Db4o.openFile("persons.data");
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
Person ben =
(Person)db.get(new Person("Ben", "Galbraith", null, 0, null)).next();
db.delete(ben);
ObjectSet<Person> results =
db.get(new Person("Jessica", "Galbraith", null, 0, null));
assertFalse(results.hasNext());
}
执行该操作时要小心,因为它意味着其他引用了被消除层叠的对象的对象将拥有一个对 null 的引用 —— db4o 对象数据库在防止删除被引用对象上使用的引用一致性 在这里没有什么作用。(引用一致性是 db4o 普遍需要的特性,据说开发团队正在考虑在未来某个版本中加入这一特性。对于使用 db4o 的开发人员来说,关键在于要以一种不违反最少意外原则 的方式实现,甚至某些时候,即使是在关系数据库中,打破一致性规则实际上也是一种理想的实践。)
结束语
本文是该系列文章的分水岭:在此之前,我使用的所有示例都基于非常简单的对象,从应用角度来讲,那些例子都不现实,其主要作用只是为了使您理解 OODBMS,而不是被存储的对象。理解像 db4o 这样的 OODBMS 是如何通过引用存储相关对象,是比较复杂的事情。幸运的是,一旦您掌握了这些行为(通过解释和理解),您所要做的就只是开始调整代码来实现这些行为。
在本文中,您看到了一些基本例子,通过调整复杂代码来实现 db4o 对象模型。学习了如何对结构化对象执行一些简单 CRUD 操作,同时,也看到了一些不可避免的问题和解决方法。
其实,目前的结构化对象例子仍然比较简单,对象之间还只是直接引用关系。许多夫妻都知道,结婚一段时间后,孩子将会出现。本系列的下一文章中,我将继续探索 db4o 中的结构化对象的创建与操作,看看在引入若干子对象后, ben
和 jess
对象将发生什么。
参考资料
学习