Object Persistence

Previous Next

Use of BinaryLongObjects

It is easy to persist Objects in an InfinityDB by using the existing serialization system. The Object can just be written to a BinaryLongObject using obvious code.
	// db is an existing ItemSpace, such as the InfinityDB itself
	cu.clear().append("any prefix that identifies the serialized object");
	BinaryLongObjectOutputStream bos = new BinaryLongObjectOutputStream(db, cu);
	ObjectOutputStream oos = new ObjectOutputStream(bos);
	oos.writeObject(myObject);
	oos.close();
This will handle recursive structure enumerations and deal with looping structures and so on.

Re-use of BinaryLongObject Streams

The BinaryLongObjectOutputStream contains a Cu, which is not something we want to garbage collect frequently (it is about 3KB) so we want to re-use it. We can set keepResourcesOnClose() on the BinaryLongObjectOutputStream, and then change its ItemSpace and prefix:

	bos.setKeepResourcesOnClose(true);
	...
	oos.close();
	..
	cu.clear().append(objectPrefix2);
	bos.setSpaceAndPrefix(db, cu);
	ObjectOutputStream oos = new ObjectOutputStream(bos); // can we reuse this too?
	oos.writeObject(myObject2);
	oos.close();
The BLOB method is efficient because each serialized Object takes only the space it really needs (see BLOB efficiency). Individual Objects can be stored, retrieved, and deleted easily in any order, with relatively good performance, basically limited by the space and time efficiency of Object serialization.

Implementing ItemHolder for Ultralight Objects

Another way to persist small Objects that is very fast and simple is to implement ItemHolder. Only small Objects can be persisted this way, because the 'serialized' form must fit in a single Item, so it cannot be longer than 1.6KChars long (note that the representations of the primitives in an Item is variable-length). The appendTo(Cu) method and getItem(Cu) methods are for getting the serialized form out of an ItemHolder, and setItem(Cu) and setItem(Object) are for putting a serialized Item into an ItemHolder. An Example 'ultralight' class:
	class Person implements ItemHolder {
		long id;
		String name;
		
		// get Object state into a Cu: implements CuAppendable
		public void appendTo(Cu cu) {
			cu.append(id).append(name);
		}
		// get Object state into a Cu: implements ItemHolder
		public void getItem(Cu cu) {
			// this simple default always works
			cu.clear().append(this);
		}
		
		// put Cu contents into an Object: implements ItemHolder
		public void setItem(Cu) {
			id = cu.longAt(0);
			name = cu.stringAt(cu.skipLong(0));
		}
		// OPTIONAL:
		// Here is the worst-case code for speed plus
		// generality. This is optional. Iimplements ItemHolder
		public void setItem(Object o) {
			if (o instanceof Person) {
				// This special case is optional, for speed. Probably not needed.
				Person person2 = (Person)o;
				id = person2.id;
				name = person2.name;
			} else if (o instanceof Cu) {
				// This special case is also optional, for speed. Probably not needed.
				// We just save alloc/dispose time for the Cu.
				setItem((Cu)o);
			} else {
				// This case is as general as possible.
				// We can append anything that is stored as a
				// long followed by a string, whether it
				// is a Person or not
				Cu cu = Cu.alloc().append(o);
				try {
					setItem(cu);
				} finally { 
					cu.dispose(); 
				}
			}
		}
	}
Now we can append a Person to a Cu anywhere, as if it were a java primitive, such as in this code that persists it and inverts it:
	Person person = ... // get some Person who holds a card
	// Append the person to any prefix, like this EAV prefix
	cu.append(CREDIT_CARD).append(cardId).append(CARD_HOLDER).append(person);
	db.insert(cu); // insert into db in one operation
	// and we can use a Person as an Entity too in an inverse Item:
	cu.append(PERSON).append(person).append(HOLDS_CARD).append(cardId);
	db.insert(cu);
And to get it back we use the typical cut-and-paste code:
	// cuCard contains an Item that identifies a card
	// cuPerson is empty
	Person person = new Person();
	if (CREDIT_CARD.next(db, cuCard, CARD_HOLDER, cuPerson)) {
		person.setItem(cuPerson);
		// do something with person
	}

Encapsulation

The Serialization-into-a-BLOB approach is simple, but it does not allow us to do queries or navigation on the objects inside a serialized, stored network of related Objects. It is merely a persistence mechanism. We may want type-safety and isolation of concerns or encapsulation while keeping the EAV model for queries and so on. It is not difficult in principle to write a Java class for each EntityClass. There can be accessors corresponding to each Attribute. The Entity is just an instance field.

It is difficult to use this approach to persist networks of Objects that refer to each other with Java references. The standard Object serialization is probably best for this, and many problems have been solved in that system, like handling looping structures and general graphs. There are also other Object persistence systems like JDO to be looked at. Some systems provide "swizzling", which is a means for making Objects come into memory only as needed as references to them are followed - a kind of virtual memory for Objects. These systems have solved many kinds of problems, and we will not try to solve them again.

Encapsulation - Fields as Cursors

But we do want type safety and encapsulation. Below is one possible simple way to do this. The basic idea is that fields that would normally be collections are instead stored as single-valued instance fields in the Object, and they serve as 'cursors' to scan over the EAV Values:
	class Person {
		static final EntityClass PERSON = new EntityClass(3);
		static final Attribute NAME = new Attribute(32);
		static final Attribute CHILD = new Attribute(33);
		static final long NULL_CHILD = -1;
		ItemSpace db;		
		long id; // the Entity
		String name; // the NAME Attribute
		long child = NULL_CHILD; // the CHILD Attribute. Note this is not a collection! It acts like a cursor.
		..
		Person(ItemSpace db) { this.db = db; }
		.. 
		// the usual accessors like long getChild()
		..
		// for each field, a loadX() and a storeX(),
		// and maybe a store() that invokes all of them
		// This can all use simplifing helper methods
		
		void loadChild() {
			..  allocate, dispose cu etc
			cu.append(PERSON).append(id).append(CHILD);
			int pl = cu.length();
			child = db.next(cu, pl) ? cu.longAt(pl) : NULL_CHILD;
		}
		void storeChild() {
			..  allocate, dispose cu etc
			.. ignored here: for a single-valued Attribute, must delete old value
			cu.append(PERSON).append(id).append(CHILD).append(child);
			db.insert(cu);
		}
		
		// Move the current value of child forwards one place in
		// the set of children. Start at NULL_CHILD. Helper code may simplify this.
		boolean nextChild() {
			..  allocate cu etc
			cu.append(PERSON).append(id).append(CHILD);
			int pl = cu.length();
			if (child != NULL_CHILD) cu.append(child);
			child = db.next(cu, pl) ? cu.longAt(pl) : NULL_CHILD;
			return child != NULL_CHILD;
		}
	}
The above code is very simple, but may be possible to simplify further, and might eventually be possible to generate automatically. First the obvious part: to use it to get a person's name:
	// we could re-use a Person instance easily
	Person person = new Person(db);
	person.setId(personId);
	person.loadName();
	System.out.println("person " + personId + " name: " + person.getName());
Now to scan the children:
	person.setId(personId);
	person.setChild(Person.NULL_CHILD); // in case child wasn't already null
	while (person.nextChild()) {
		System.out.println("personId: " + personId + " childId: " + person.getChild());
	}
This code is obviously simple and clear. The Person Object does not represent an Entity by being a different instance, but by having a different current id. Each field can potentially become multi-valued merely by acquiring a new nextX() method.

The above technique is only a concept and example, and various encapsulations have been used. This code uses each multi-valued field as a cursor over a virtual collection associated with it that is actually an EAV multi-value Attribute. There are no Collections, Enumerators, Iterators, EntrySets, Entry's, KeySets, instanceof, casting, etc. etc. There is not even any construction, except when a String Value is returned (although even in that case, it would be possible to avoid construction by keeping the string component in a Cu.)

We have not shown the code to maintain inversions - this code would simply require a second insert()/delete() on each storeX() method that would use the Inverse Item of the Item being stored. See Inversion. We also need code to delete an entire Entity, and possibly code to scan over Entities, like a boolean nextPerson(). All of these can be easily handled by helper methods in the EntityClass and Inverse classes.

Previous Next


Copyright © 1997-2006 Boiler Bay.