面向 Java 开发人员的 db4o 指南: 第 6 部分:结构化对象和集合

2024-01-26 20:08

本文主要是介绍面向 Java 开发人员的 db4o 指南: 第 6 部分:结构化对象和集合,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

面向对象应用程序大量使用继承,并且它们常常使用继承(或者 “是一个”)关系来分类和组织给定系统中的对象。在关系存储模式中使用继承比较困难,因为这种模式没有内在的继承概念,但它是 OODNBMS 中的一个核心功能。在本期的 面向 Java™ 开发人员的 db4o 指南 中,您将会发现,作为一个核心功能,在 db4o 中创建查询时使用继承竟是如此的简单(而且功能强大)。

 

在本系列文章中,我使用 Person 类型来演示 db4o 的所有基本原理。您已经学会了如何创建完整的 Person 对象图,以细粒度方式(使用 db4o 本身的查询功能来限制返回的实际对象图)对其进行检索,以及更新和删除全部的对象图(设定一些限制条件)等等。实际上,在面向对象的所有特性中,我们只漏掉了其中一个,那就是继承。

关于本系列
信息存储和检索作为同义语伴随 RDBMS 已经有 10 余年了,但现在情况有所改变。Java 开发人员为所谓的对象关系型阻抗失配而沮丧,也不再有耐心去尝试解决这个问题。加上可行替代方案的出现,就导致了人们对对象持久性和检索的兴趣的复苏。 面向 Java 开发人员的 db4o 指南 对开放源码数据库 db4o 进行了介绍,db4o 可以充分利用当前的面向对象的语言、系统和理念。请访问 db4o 主页 并下载 db4o;您需要用它来完成本文中的例子。

我将演示的这个例子的最终目标是一个用于存储雇员数据的数据管理系统,我一直致力于开发我的 Person 类型。我需要这样一个系统:存储某个公司的员工及其配偶和子女的信息,但是此时他们仅仅是该系统的 Person (或者,可以说 Employees 是一个 Person ,但是 Persons 不是一个 Employee )。而且,我不希望 Employee 的行为属于 Person API 的一部分。从对象建模程序的角度公平地讲,按照 is-a 模拟类型的能力就是面向对象的本质。

我会用 Person 类型中的一个字段来模拟雇佣 的概念。这是一种关系方法,而且不太适合用于对象设计。幸运的是,与大多数 OODBMS 系统一样,db4o 系统对继承有一个完整的理解。在存储系统的核心使用继承可以轻松地 “重构” 现有系统,可以在设计系统时更多地使用继承,而不会使查询工具变得复杂。您将会看到,这也使查询特定类型的对象变得更加容易。

 

高度改进的 Person

清单 1 回顾了 Person 类型,该类型在本系列文章中一直作为示例使用:


清单 1. 改进之前的示例……

                
package com.tedneward.model;import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;public class Person
{public Person(){ }public Person(String firstName, String lastName, Gender gender, int age, Mood mood){this.firstName = firstName;this.lastName = lastName;this.gender = gender;this.age = age;this.mood = mood;}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 Gender getGender() { return gender; }public int getAge() { return age; }public void setAge(int value) { age = value; }public Mood getMood() { return mood; }public void setMood(Mood value) { mood = value; }public Person getSpouse() { return spouse; }public void setSpouse(Person value) { // A few business rulesif (spouse != null)throw new IllegalArgumentException("Already married!");if (value.getSpouse() != null && value.getSpouse() != this)throw new IllegalArgumentException("Already married!");spouse = value; // Highly sexist business ruleif (gender == Gender.FEMALE)this.setLastName(value.getLastName());// Make marriage reflexive, if it's not already set that wayif (value.getSpouse() != this)value.setSpouse(this);}public Address getHomeAddress() { return addresses[0]; }public void setHomeAddress(Address value) { addresses[0] = value; }public Address getWorkAddress() { return addresses[1]; }public void setWorkAddress(Address value) { addresses[1] = value; }public Address getVacationAddress() { return addresses[2]; }public void setVacationAddress(Address value) { addresses[2] = value; }public Iterator<Person> getChildren() { return children.iterator(); }public Person haveBaby(String name, Gender gender) {// Business ruleif (this.gender.equals(Gender.MALE))throw new UnsupportedOperationException("Biological impossibility!");// Another highly objectionable business ruleif (getSpouse() == null)throw new UnsupportedOperationException("Ethical impossibility!");// Welcome to the world, little one!Person child = new Person(name, this.lastName, gender, 0, Mood.CRANKY);// Well, wouldn't YOU be cranky if you'd just been pushed out of// a nice warm place?!?// These are your parents...            child.father = this.getSpouse();child.mother = this;// ... and you're their new baby.// (Everybody say "Awwww....")children.add(child);this.getSpouse().children.add(child);return child;}public Person getFather() { return this.father; }public Person getMother() { return this.mother; }public String toString(){return "[Person: " +"firstName = " + firstName + " " +"lastName = " + lastName + " " +"gender = " + gender + " " +"age = " + age + " " + "mood = " + mood + " " +(spouse != null ? "spouse = " + spouse.getFirstName() + " " : "") +"]";}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.gender.equals(other.gender) &&this.age == other.age);}private String firstName;private String lastName;private Gender gender;private int age;private Mood mood;private Person spouse;private Address[] addresses = new Address[3];private List<Person> children = new ArrayList<Person>();private Person mother;private Person father;
}

 

跟本系列的其他文章一样,我不会在每次更改时都展示完整的 Person 类,只逐步展示每次更改。在这个例子中,我实际上并没有更改 Person ,因为我将要扩展 Person ,而不是修改它。

 



 

区别雇员

需要做的第一件事是使我的雇员管理系统能够区别普通的 Person (例如雇员的配偶和/或子女)和 Employee 。从纯粹建模的立场来说,这个更改很简单。我只是向 Person 引入了一个新的派生类,这个类和目前涉及到的其他类都在同一个包中。毫无疑问,我将会调用这个类 Employee ,如清单 2 所示:


Listing 2. Employee 扩展 Person

                
package com.tedneward.model;public class Employee extends Person
{public Employee(){ }public Employee(String firstName, String lastName, String title,Gender gender, int age, Mood mood){super(firstName, lastName, gender, age, mood);this.title = title;}public String getTitle() { return title; }public void setTitle(String value) { title = value; }public String toString(){return "[Employee: " + getFirstName() + " " + getLastName() + " " +"(" + getTitle() + ")]";}private String title;
}

 

Employee 类的全部代码都在清单 2 中。从 OODBMS 的角度看, Employee 中的其他方法意义不大。在本讨论中需要记住的是 EmployeePerson 的一个子类(如果更加关心系统的建模过程,可以设想 Employee 中的其他方法,例如 promote()demote()getSalary()setSalary()workLikeADog() )。

 




 

测试新模型

对新模型的探察测试简单明了。我创建一个叫做 InheritanceTest 的 JUnit 类,目前为止,它是第一个较为复杂的对象集,充当 OODBMS 最初的工作内容。为了使输出(将会在清单 6 中见到)更加清晰,我在清单 3 中展示了带有 @Before 注释的 prepareDatabase() 调用:


清单 3. 欢迎加入本公司(您现在为我服务)

                @Before public void prepareDatabase(){db = Db4o.openFile("persons.data");// The NewardsEmployee ted = new Employee("Ted", "Neward", "President and CEO",Gender.MALE, 36, Mood.HAPPY);Person charlotte = new Person("Charlotte", "Neward",Gender.FEMALE, 35, Mood.HAPPY);ted.setSpouse(charlotte);Person michael = charlotte.haveBaby("Michael", Gender.MALE);michael.setAge(14);Person matthew = charlotte.haveBaby("Matthew", Gender.MALE);matthew.setAge(8);Address tedsHomeOffice = new Address("12 Redmond Rd", "Redmond", "WA", "98053");ted.setHomeAddress(tedsHomeOffice);ted.setWorkAddress(tedsHomeOffice);ted.setVacationAddress(new Address("10 Wannahokalugi Way", "Oahu", "HA", "11223"));db.set(ted);// The TatesEmployee bruce = new Employee("Bruce", "Tate", "Chief Technical Officer", Gender.MALE, 29, Mood.HAPPY);Person maggie = new Person("Maggie", "Tate", Gender.FEMALE, 29, Mood.HAPPY);bruce.setSpouse(maggie);Person kayla = maggie.haveBaby("Kayla", Gender.FEMALE);Person julia = maggie.haveBaby("Julia", Gender.FEMALE);bruce.setHomeAddress(new Address("5 Maple Drive", "Austin","TX", "12345"));bruce.setWorkAddress(new Address("5701 Downtown St", "Austin","TX", "12345"));// Ted and Bruce both use the same timeshare, apparentlybruce.setVacationAddress(new Address("10 Wanahokalugi Way", "Oahu","HA", "11223"));db.set(bruce);// The FordsEmployee neal = new Employee("Neal", "Ford", "Meme Wrangler",Gender.MALE, 29, Mood.HAPPY);Person candi = new Person("Candi", "Ford",Gender.FEMALE, 29, Mood.HAPPY);neal.setSpouse(candi);neal.setHomeAddress(new Address("22 Gritsngravy Way", "Atlanta", "GA", "32145"));// Neal is the roving architectneal.setWorkAddress(null);db.set(neal);// The SlettensEmployee brians = new Employee("Brian", "Sletten", "Bosatsu Master",Gender.MALE, 29, Mood.HAPPY);Person kristen = new Person("Kristen", "Sletten",Gender.FEMALE, 29, Mood.HAPPY);brians.setSpouse(kristen);brians.setHomeAddress(new Address("57 Classified Drive", "Fairfax", "VA", "55555"));brians.setWorkAddress(new Address("1 CIAWasNeverHere Street", "Fairfax", "VA", "55555"));db.set(brians);// The GalbraithsEmployee ben = new Employee("Ben", "Galbraith", "Chief UI Director",Gender.MALE, 29, Mood.HAPPY);Person jessica = new Person("Jessica", "Galbraith",Gender.FEMALE, 29, Mood.HAPPY);ben.setSpouse(jessica);ben.setHomeAddress(new Address("5500 North 2700 East Rd", "Salt Lake City", "UT", "12121"));ben.setWorkAddress(new Address("5600 North 2700 East Rd", "Salt Lake City","UT", "12121"));ben.setVacationAddress(new Address("2700 East 5500 North Rd", "Salt Lake City","UT", "12121"));// Ben really needs to get out moredb.set(ben);db.commit();}

 

跟本系列早先的探察测试示例一样,在每次测试完成后,我使用带 @After 注释的 deleteDatabase() 方法来删除数据库,以使各部分能够很好地分隔开。

让我们运行几个查询……

在实际运行这个方法之前,我将会检查在系统中使用 Employee 会有哪些效果(如果有的话)。希望从数据库获取所有 Employee 信息,这很正常 — 或许当公司破产时他们将会全部被解雇(是的,我知道,这样想很残酷,但我只是对 2001 年的 dot-bomb 事故还有点心有余悸)。最初的测试看起来很简单,正如清单 4 所示:


清单 4. Ted 说,“你被解雇了!”

                
@Test public void testSimpleInheritanceQueries()
{ObjectSet employees = db.get(Employee.class);while (employees.hasNext())System.out.println("Found " + employees.next());
}

 

当进行测试时,将会产生一个有趣的结果:数据库(我自己、Ben、Neal、Brian 和 Bruce)中只返回了 Employee 。OODBMS 识别出查询受到子类型 Employee 的显式约束,并且只选择了符合返回条件的对象。因为其他对象(配偶或者孩子)不属于 Employee 类型,他们不符合条件,所以没有被返回。

当运行一个返回所有 Person 的查询时,将会更加有趣,如下所示:


清单 5. 找到所有人!

                
@Test public void testSimpleNonEmployeeQuery()
{ObjectSet persons = db.get(Person.class);while (persons.hasNext())System.out.println("Found " + persons.next());
}

 

当运行这个查询时,每个单一对象 — 包括以前返回的所有 Employee — 都被返回了。从某种程度上说,这是有意义的。因为 Employee 是一个 Person ,由于建立在 Java 代码中的实现继承关系,因此满足返回查询的必须条件。

db4o 中的继承(以及多态)其实就是这么简单。没有用于查询语言的复杂的 IS 扩展,就不会引入不同于 Java 类型系统中现有概念的 “类型” 概念。我所指的只是期望作为查询的一部分的类型,而且这些是构成查询的主要成分。这跟在 SQL 查询中加入表格很相似,方法就是选择其数据应为查询结果一部分的表格。额外的好处是,“父类型” 也是作为查询的一部分隐式地 “加入” 的。清单 6 显示了 清单 3 中 InheritanceTest 的输出:


清单 6. 多态发挥作用

                
.Found [Employee: Ted Neward (President and CEO)]
Found [Person: firstName = Charlotte lastName = Neward gender = FEMALE age = 35
mood = HAPPY spouse = Ted ]
Found [Person: firstName = Michael lastName = Neward gender = MALE age = 14 mood= CRANKY ]
Found [Person: firstName = Matthew lastName = Neward gender = MALE age = 8 mood
= CRANKY ]
Found [Employee: Bruce Tate (Chief Technical Officer)]
Found [Person: firstName = Maggie lastName = Tate gender = FEMALE age = 29 mood
= HAPPY spouse = Bruce ]
Found [Person: firstName = Kayla lastName = Tate gender = FEMALE age = 0 mood =
CRANKY ]
Found [Person: firstName = Julia lastName = Tate gender = FEMALE age = 0 mood =
CRANKY ]
Found [Employee: Neal Ford (Meme Wrangler)]
Found [Person: firstName = Candi lastName = Ford gender = FEMALE age = 29 mood =HAPPY spouse = Neal ]
Found [Employee: Brian Sletten (Bosatsu Master)]
Found [Person: firstName = Kristen lastName = Sletten gender = FEMALE age = 29 m
ood = HAPPY spouse = Brian ]
Found [Employee: Ben Galbraith (Chief UI Director)]
Found [Person: firstName = Jessica lastName = Galbraith gender = FEMALE age = 29mood = HAPPY spouse = Ben ]

 

您可能会感到奇怪,不管如何查询,返回的对象仍然是适当的子类型对象。例如,跟预期的一样,在上面的查询中当 toString() 被每个返回的 Person 对象调用时,Person.toString() 也正被每个 Person 调用。然而,因为 Employee 有一个重写的 toString() 方法,因此关于动态绑定的常用规则就不适用了。存储在 Employee 中的 Person 的各部分不会被 “切掉”,而当定期 SQL 查询未能成功地将派生子类表加入到 table-per-class 模型中时,这种被 “切掉” 的现象就会发生。

 





 

原生继承

当然,当继承条件扩展到原生查询中时,其功能就跟我所做过的简单对象查询一样强大。进行调用时,查询语法将会更加复杂,但是基本上遵循我以前所使用的语法,如清单 7 所示:


清单 7. 你是单身吗?

                
@Test public void testNativeQuery()
{List<Person> spouses =db.query(new Predicate<Person>() {public boolean match(Person candidate) {return (candidate.getSpouse() instanceof Employee);}});for (Person spouse : spouses)System.out.println(spouse);
}

 

下面的查询与我以前做过的查询在思想上类似,考虑系统中所有的 Person ,但是设置一个约束条件,只查找配偶也是一个 EmployeePerson — 调用 getSpouse() ,将返回值传递给 Java instanceof 运算符,这样就完成了查询(记住 match() 调用只返回 true 或者 false,表示候选对象是否应该返回)。

请注意如何通过更改在 query() 调用中传递的 Predicate 来更改隐式选择的类型条件,如清单 8 所示:


清单 8. 哇!办公室恋情!

                
@Test public void testEmployeeNativeQuery()
{List<Employee> spouses =db.query(new Predicate<Person>() {public boolean match(Person candidate) {return (candidate.getSpouse() instanceof Employee);}});for (Person spouse : spouses)System.out.println(spouse);
}

 

当执行此查询时,不会产生什么结果,因为现在此查询只查找配偶也在公司工作的 Employee 。目前,数据库中的雇员都不满足这个条件。如果公司雇佣 Charlotte,那么会返回两个 Employee :Ted 和 Charlotte(但是人家说办公司恋情永远不会发生)。

在很大程度上,就是这样。继承不会对更新、删除和激活深度产生任何影响,只会影响到对象的查询方面。但是回想起 Java 平台提供的两种形式的继承:实现继承和接口继承。前者通过各种 extends 子句来实现,而后者通过 implements 来实现。如果 db4o 支持 extends ,那么它也一定支持 implements ,您将会看到,这有利于实现强大的查询功能。

 





 

都是关于接口的

就像任何 Java (或 C#)编程人员使用了一段时间这种语言后认识到的,接口对于建模非常有用。尽管不会经常看到,接口具有强大的 “隔离” 交叉在传统实现继承行中的对象的能力;通过使用接口,我可以声明某些类型为 Comparable 或者 Serializable ,或者在本例中,Employable (是的,从设计的角度说这是大材小用了,但是用于教学还是很不错的)。


清单 9. 嘿,不再是 2001 年了!来为我工作吧!

                
package com.tedneward.model;public interface Employable
{public boolean willYouWorkForUs();
}

 

角色和对象

一些对象模型将不适合我用接口和继承来模拟 Person 扮演的角色。例如,假设一个 Employee 的配偶决定也来此公司工作,有必要将他们从系统的 Person 中删除,然后重新插入到 Employee 中吗? 随着时间的流逝,角色也可能并且经常变化。我们不要期望更改对象的基类和接口类型,以适应角色的转变。

这是一个很普通的争议,如果能够从根本上解决的话,不属于本文讨论的范围。目前我们只能说,我对继承和接口的使用纯粹是出于演示和教学的目的。要想进一步学习,请参考 参考资料 中的 “Role object” 一节。

要看接口是如何工作的,我需要 Employable 接口的一个实体类继承,并且 — 或许您已经猜测到 — 这意味着创建一个 EmployablePerson 子类型来扩展 Person 和实现 Employable 。我不会再次演示这些代码(没有必要演示,除了将 ** EMPLOYABLE ** 添加到 PersontoString() 末尾以外, PersonEmployablePerson.toString() 方法中)。我也会修改 prepareDatabase() 调用以返回 “Charlotte 是一个 EmployablePerson ,而不只是一个 Person ” 的事实。

现在,我会编写一个遍历数据库的查询,查找愿意为本公司工作的雇员的配偶或亲人,如清单 10 所示。


清单 10. 有工作了,来看看吧……

                
@Test public void testEmployableQuery()
{List<Employable> potentialEmployees =db.query(new Predicate<Employable>() {public boolean match(Employable candidate) {return (candidate.willYouWorkForUs());}});for (Employable e : potentialEmployees)System.out.println("Eureka! " + e + " has said they'll work for us!");
}    

 

毫无疑问,Charlotte 被返回了,说明她可能为本公司工作。更好的是,这意味着我引入的任何接口都变成了一种限制查询的新方式,不需要人工添加包含此信息的字段;只有 Charlotte 符合查询条件,因为她实现了这个接口,而其他配偶都没有实现(至少到目前为止)。

结束语

如 果说对象和继承就像巧克力和花生酱的话,那对象和多态就好比手和手套。这两个元素就像经理和他/她的高薪一样般配。检索数据时,任何存储对象的系统都不得 不将继承的概念引入它的存储媒介和过滤器中。幸运的是,面向对象的 DBMS 使得这很容易实现,而且不必引入新的 query-predicate 术语。从长远看来,引入继承会使 OODBMS 容易使用 得多

这篇关于面向 Java 开发人员的 db4o 指南: 第 6 部分:结构化对象和集合的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/647923

相关文章

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

一文详解如何在Python中从字符串中提取部分内容

《一文详解如何在Python中从字符串中提取部分内容》:本文主要介绍如何在Python中从字符串中提取部分内容的相关资料,包括使用正则表达式、Pyparsing库、AST(抽象语法树)、字符串操作... 目录前言解决方案方法一:使用正则表达式方法二:使用 Pyparsing方法三:使用 AST方法四:使用字

Java字符串处理全解析(String、StringBuilder与StringBuffer)

《Java字符串处理全解析(String、StringBuilder与StringBuffer)》:本文主要介绍Java字符串处理全解析(String、StringBuilder与StringBu... 目录Java字符串处理全解析:String、StringBuilder与StringBuffer一、St

springboot整合阿里云百炼DeepSeek实现sse流式打印的操作方法

《springboot整合阿里云百炼DeepSeek实现sse流式打印的操作方法》:本文主要介绍springboot整合阿里云百炼DeepSeek实现sse流式打印,本文给大家介绍的非常详细,对大... 目录1.开通阿里云百炼,获取到key2.新建SpringBoot项目3.工具类4.启动类5.测试类6.测

Python列表去重的4种核心方法与实战指南详解

《Python列表去重的4种核心方法与实战指南详解》在Python开发中,处理列表数据时经常需要去除重复元素,本文将详细介绍4种最实用的列表去重方法,有需要的小伙伴可以根据自己的需要进行选择... 目录方法1:集合(set)去重法(最快速)方法2:顺序遍历法(保持顺序)方法3:副本删除法(原地修改)方法4:

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

Python中判断对象是否为空的方法

《Python中判断对象是否为空的方法》在Python开发中,判断对象是否为“空”是高频操作,但看似简单的需求却暗藏玄机,从None到空容器,从零值到自定义对象的“假值”状态,不同场景下的“空”需要精... 目录一、python中的“空”值体系二、精准判定方法对比三、常见误区解析四、进阶处理技巧五、性能优化

在Spring Boot中浅尝内存泄漏的实战记录

《在SpringBoot中浅尝内存泄漏的实战记录》本文给大家分享在SpringBoot中浅尝内存泄漏的实战记录,结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录使用静态集合持有对象引用,阻止GC回收关键点:可执行代码:验证:1,运行程序(启动时添加JVM参数限制堆大小):2,访问 htt

SpringBoot集成Milvus实现数据增删改查功能

《SpringBoot集成Milvus实现数据增删改查功能》milvus支持的语言比较多,支持python,Java,Go,node等开发语言,本文主要介绍如何使用Java语言,采用springboo... 目录1、Milvus基本概念2、添加maven依赖3、配置yml文件4、创建MilvusClient

浅析Java中如何优雅地处理null值

《浅析Java中如何优雅地处理null值》这篇文章主要为大家详细介绍了如何结合Lambda表达式和Optional,让Java更优雅地处理null值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录场景 1:不为 null 则执行场景 2:不为 null 则返回,为 null 则返回特定值或抛出异常场景