So far in this series, I've used the Person type
to reveal almost all of the fundamentals of working with db4o. You've learned
how to create entire graphs of Person objects,
retrieve them in some fairly fine-grained manner (using db4o's native query
functionality to constrain the actual object graph returned), update and
delete entire graphs of objects (subject to some limitations), and so on. In
fact, just one aspect of object-orientation has remained entirely outside of
this discussion, and that is inheritance.
While the end goal of my evolving example is a data management system for
storing employee data, I've really focused on developing my Person type. I may want a system capable of storing
information about people who work for a company, as well as their spouses and
children, but at this point they're all just Persons to the system. (Or, if you'll pardon the poor English, Employees
is-a
Person,
but Persons
isn't-a
Employee.) Furthermore, I may want behavior for Employees
that shouldn't be a part of the Person API. It's
fair to say that from an object modeler's point of view, the ability to
model types in is-a terms is the heart of what object-orientation is about.
While I could model the notion of employment using a field in the
Person type, that's a relational approach and
doesn't fit well within the object-design scenario. Fortunately, the db4o
system, like most OODBMS systems, has an integral understanding of
inheritance. Having inheritance at the core of a storage system makes it
surprisingly easy to "refactor" the existing system into one that takes
greater advantage of inheritance as a design concept, without complicating the
query facilities. As you'll see, it also makes things much easier when
querying for particular kinds of objects.
Just in case you're joining the series now, Listing 1 is a recap of the
Person type, which is the long-running
example-in-progress for the series:
Listing 1. When we last left our hero ...
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 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());
// Make marriage reflexive, if it's not already set that way
if (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 rule
if (this.gender.equals(Gender.MALE))
throw new UnsupportedOperationException("Biological impossibility!");
// Another highly objectionable business rule
if (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;
}
|
As with the other articles in this series, I'm not going to show the
Person class in full for each change I make, but just
the incremental changes I make as I go along. In this case, I won't actually be
making changes to Person at all because I'm going
to extend Person rather than modify it.
The first thing I need to do is make it possible for my employee management
system to recognize the difference between normal
Persons (such as the spouses and/or children of
employees) and Employees. From a pure modeling
standpoint, this change is trivial. I just introduce a new derived class to
Person, which is of the same package as the other
classes thus far. Not surprisingly, I'll call this class
Employee, shown in Listing 2:
Listing 2. Employee extends 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;
}
|
The Employee class doesn't actually have much
more to it than what you see in Listing 2. From the perspective of the OODBMS,
the additional methods on Employee are irrelevant.
All you need to keep in mind for the discussion is that Employee is a subclass of Person. (If you are more interested in the modeling
aspect of the system, imagine methods on Employee
like promote(), demote(), getSalary() and
setSalary(), and workLikeADog().)
Exploration testing the new model is fairly
straightforward. I create a JUnit class called InheritanceTest and set up what is, by now, a rather
complicated set of objects to serve as the initial working contents of the
OODBMS. Just to make the output (which you'll see in Listing 6) more clear, I've
shown the details of the @Before-annotated
prepareDatabase() call in Listing 3:
Listing 3. Welcome to the company (you belong to me now)
@Before public void prepareDatabase()
{
db = Db4o.openFile("persons.data");
// The Newards
Employee 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 Tates
Employee 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, apparently
bruce.setVacationAddress(
new Address("10 Wanahokalugi Way", "Oahu",
"HA", "11223"));
db.set(bruce);
// The Fords
Employee 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 architect
neal.setWorkAddress(null);
db.set(neal);
// The Slettens
Employee 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 Galbraiths
Employee 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 more
db.set(ben);
db.commit();
}
|
As with previous exploration testing examples in this series, I use the
@After-annotated deleteDatabase()
method to delete the database after each test, just to keep things nicely partitioned.
Let's just run a few queries ...
Before actually running that method, I'll check to see what effect, if any,
having Employees in the system will have. It's
natural to want to fetch all the Employees from the
database — perhaps to fire them all as part of the company going bankrupt.
(Yes, I know, it's cruel to think about it, but I'm still a little scarred
from the dot-bomb collapse of 2001.) The initial test looks pretty
straightforward, as you can see in Listing 4:
Listing 4. The Ted says, 'You're fired!'
@Test public void testSimpleInheritanceQueries()
{
ObjectSet employees = db.get(Employee.class);
while (employees.hasNext())
System.out.println("Found " + employees.next());
}
|
When run, the test produces an interesting result: only the Employees in the database (myself, Ben, Neal, Brian, and
Bruce) are returned. Implicitly, the OODBMS has recognized that the query is
constrained explicitly by the subtype Employee, and has only selected
those objects that fit that criteria for return. Because none of the other
objects (the spouses or the children) are of type Employee, they aren't eligible and therefore aren't sent
back.
Things get more interesting when I run a simple query to return all Persons, like so:
Listing 5. Find all humans!
@Test public void testSimpleNonEmployeeQuery()
{
ObjectSet persons = db.get(Person.class);
while (persons.hasNext())
System.out.println("Found " + persons.next());
}
|
When I run this query, every single object — including all the Employees returned earlier — is sent back. At one level,
this makes sense. Because Employee
is-a
Person, by virtue of the
implementation-inheritance relationship established in the Java code, the
criteria necessary for return from this query is met.
Inheritance (and, by extension, polymorphism) in db4o really is that
simple. No complicated IS extensions to a query
language, no introduced notion of "type" beyond what already exists in the
Java type system. I simply indicate the type desired as part of the query, and
those are the types that will form the basis of the query. It's almost like
joining tables in an SQL query, by selecting tables whose data should be part
of the results of the query. The added bonus, in this case, is that "parent
types" are also implicitly "joined" as part of the query. Listing 6 shows the
output from my InheritanceTest back in Listing 3:
Listing 6. Polymorphism at work
.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 = 29
mood = HAPPY spouse = Ben ]
|
It may surprise you that the objects returned are still
objects of the appropriate subtype, despite how they were queried. For example,
in the above query, when toString() is called on each
of the Person objects returned,
Person.toString() is invoked for each
Person, as expected. Because
Employees have an overridden
toString() method, however, the usual rules
regarding dynamic binding kick in. The parts of Person
that are stored in Employee aren't "sliced off," as
can happen in a regular SQL query that fails to join every derived subclass
table in a table-per-class model.
Of course, inheritance criteria extends into native queries just as strongly as it does the simple object queries I've done so far. The query syntax gets just a tad more complicated when the call is made, but fundamentally it follows from the syntax I've already used before, shown in Listing 7:
Listing 7. Are you a company widow?
@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);
}
|
I begin with a query similar, in spirit, to the one I did earlier, looking
through all the Persons in the system, but I
constrain it further by looking for only those Persons who have a spouse that is an Employee
— a call to getSpouse(), with the return value passed to the Java
instanceof operator, and it's done. (Remember that
the match() call only returns true or false,
indicating whether the candidate object should be returned.)
Notice how changing the type of the Predicate
passed in the query() call can change the type
criteria implicitly selected, shown in Listing 8:
Listing 8. Scandalous! Office romance!
@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);
}
|
When executed, this query produces no results because now the query is
looking only for Employees with a spouse also employed by the company. Currently, no employee in the database satisfies that criteria. Should the company decide to hire Charlotte, then two Employees would be
returned, however: Ted and Charlotte. (And they say office romance never
works.)
For the most part, again, that's it. Inheritance doesn't have any effect on updates, deletes, or activation depth, just the query aspect of objects. But recall that the Java platform offers two forms of inheritance: implementation inheritance, via the ubiquitous extends clause, and interface inheritance, via implements. If db4o supports extends, then it also must support implements, which makes for a pretty powerful query capability, as you'll see.
As just about any Java (or C#) programmer learns after
about six weeks of using the language, interfaces are highly useful for modeling. Although not often seen as such, interfaces present a
powerful ability to "segregate" types across traditional implementation
inheritance lines; through the use of an interface, I can declare that certain
types are Comparable, or Serializable, or in this case, Employable. (Yes, this is overkill from a design perspective, but it serves the pedagogical purpose nicely.)
Listing 9. Hey, it's not 2001 anymore! Come work for me!
package com.tedneward.model;
public interface Employable
{
public boolean willYouWorkForUs();
}
|
To see the interface at work, I need a concrete class inheriting from the
Employable interface, and — you probably guessed
it already — that means creating an EmployablePerson subtype that extends Person and implements Employable. I won't show that code here (there's not much worth showing, aside from
adding ** EMPLOYABLE ** to the end of Person's toString() in the
EmployablePerson.toString() method). I will also modify the prepareDatabase() call to return the fact that Charlotte
is an EmployablePerson, instead of just a Person.
Now, I can write a query that iterates through the database, looking for spouses or relatives of our current employees that might be willing to work for the company, shown in Listing 10
Listing 10. There's this job, see ...
@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!");
}
|
Sure enough, Charlotte is returned as being employable. What's better, this means that any interface I introduce becomes a new way to constrain queries, without artificially introducing fields to carry the information; Charlotte fits the query criteria solely because she implements that interface, whereas none of the other spouses do (at least, for now).
If objects and inheritance go together like chocolate and peanut butter, objects and polymorphism are like hand and glove. The two elements fit just as snugly as a manager and his or her high salary. Any system that stores objects has to be able to understand and project this notion of inheritance into the storage media and filter along it when retrieving data. Fortunately, object-oriented DBMSs render this a trivial undertaking, and they let you do it without having to introduce new query-predicate terms. In the long run, that makes the OODBMS much easier to use when inheritance is part of the picture.
Learn
- "
The busy Java developer's guide to db4o: Beyond simple objects" (Ted Neward, developerWorks, July 2007): Get started with structured objects and exploration testing in db4o.
- "Role object" (Dirk Bäumer, Dirk Riehle, Wolf Siberski, and Martina Wulf; Pattern Languages of Program Design 4, Addison-Wesley, 2000): Explains role objects, wherein representing roles as individual objects enables you to keep different contexts separate and thus simplify system configuration.
-
In pursuit of code quality
(Andrew Glover, developerWorks series): Learn more about developer testing techniques like exploration testing.
-
The
busy Java developer's guide to db4o
(Ted Neward, developerWorks series): Introduces db4o, an open source database that leverages today's object-oriented languages, systems, and mindset.
-
The db4o home page: Learn more about db4o.
-
ODBMS.org: An excellent collection of free material on object database technology.
- The developerWorks Java technology zone: Hundreds of articles about every aspect of Java programming.
-
New to IBM® Information Management: Still not sold on OODBMS? Get more information about IBM's powerful family of relational database management system (RDBMS) servers.
Get products and technologies
-
Download db4o: An open source native Java programming and .NET database.
Discuss
-
developerWorks
blogs: Get involved in the developerWorks community.
Comments (Undergoing maintenance)






