A Persistence Framework

This describes a set of Java classes that comprise a small application server acting as a front-end for an SQL database.

The JDBC interface provides a means of accessing an SQL database by hiding the details of the driver, but otherwise is a thin layer that does nothing to hide the specifics of SQL itself.  This framework adds another layer that allows database accesses to be performed in a transparent manner that is completely independent of the specific database engine used.  It also provides a front-end cache of recently used records to improve performance, and a mechanism whereby multiple users of a record can be notified of changes to the record immediately they occur.

A major design aim for the framework was to avoid the need for any special-purpose user classes to represent specific record structures.  Instead, a field specification object allows a generic record type to dynamically contain any kind of data.  You don't subclass the record type, simply instantiate a standard object and tell it about the data it will be managing.

Using the framework is quite simple.  The following example connects to an InstantDB database and adds a simple customer record to an existing table.  Exception-handling code has been removed in the interests of clarity.

public class MyClass extends LDBClient
{
   connect("MyData");
   
   // Define the field structure
   idFieldSpec=new LDBFieldSpec(LDBRecord.ID,LDBFieldSpec.LONG);
   nameFieldSpec=new LDBFieldSpec("name",LDBFieldSpec.CHAR,20);
   addressFieldSpec=new LDBFieldSpec("address",LDBFieldSpec.CHAR,60);
   telnoFieldSpec=new LDBFieldSpec("telno",LDBFieldSpec.CHAR,20);
   
   // Create a new record
   clear();
   LDBRecord rec=getNewRecord("customer");
   rec.addField(idFieldSpec,45);
   rec.addField(nameFieldSpec,"Fred Bloggs");
   rec.addField(addressFieldSpec,"12, The Drive, Anytown");
   rec.addField(telnoFieldSpec,"016 9250 0328");
// createNewTable(rec,true);  // see the text
   saveRecord(rec);
   commit();
}

The equivalent SQL command is

INSERT INTO customer (id,name,address,telno)
VALUES (45,"Fred Bloggs","12, The Drive, Anytown","016 9250 0328")

which is what is actually generated internally, but the application server hides this from you.

The class extends LDBClient (see below).  It starts by connecting to the database; the argument given will depend on the database being used.  Next it creates four field specifications, which contain everything we need to know about a particular field in a record, namely the field name and its data type.  Some types also require a length argument.  The first specification is special; it will be used as a unique identifier for this type of record.  Its name is special and will be recognized by the rest of the package.

To write to the database, we first issue a clear() command, which removes any pending transactions that have not been committed.  Then we create a record object, giving ourselves as a client and supplying the name of the table in the database that is to be used for this record.  Then we add four fields, giving the field specifications already created and the data to go with them.  Finally we can save the record, and if no further related work needs to be done the transaction can be committed.

To create the table in the first place needs one more line, shown commented-out in the listing.  The two parameters are a record that must contain enough information to determine what fields must be in the table, and a boolean parameter that if true causes any previous table of that name to be dropped (deleted).

Reading the contents of a database is also simple.  There are two options; one is to search for a record whose unique ID is known; the other is to do a generalized search.  First the ID search:

LDBRecord rec=getNewRecord("customer");
rec.addField(idFieldSpec,45);  // the ID we're looking for
rec.addField(nameFieldSpec,null);
rec.addField(addressFieldSpec,null);
rec.addField(telnoFieldSpec,null);
rec=restoreRecord(rec);
String name=(String)rec.getValue("name");
String address=(String)rec.getValue("address");
String email=(String)rec.getValue("email");

and then the search by value.  Here we are interested in all members of the Bloggs family:

LDBRecord rec=getNewRecord("customer");
rec.addField(idFieldSpec,0);
rec.addField(nameFieldSpec,null);
rec.addField(addressFieldSpec,null);
rec.addField(telnoFieldSpec,null);
getRecords(rec,"WHERE name LIKE '%Bloggs' ORDER BY name");
while (hasMoreRecords(rec))
{
   getNextRecord(rec);
   String name=(String)rec.getValue("name");
   String address=(String)rec.getValue("address");
   String email=(String)rec.getValue("email");
   .
   .
}

In both examples we set up a new LDBRecord with the requisite fields.  All fields must be given or the framework will have nowhere to put values read from the database.  You'd typically use a separate subroutine to set up the record.  The first example looks for the record whose ID is 45; the second for any record where the name field contains the word 'Bloggs' .  You can be as creative as you like with the SQL to give yourself less work to do when the data arrives.  The second example may return no records at all, or it may return more than one; hence the loop to deal with them.

The data stored does not have to be Strings as in these examples.  The framework deals with varying sizes of number, with Strings and with arbitrary Serializable Java objects.


The Classes

The following paragraphs describe in outline the major classes that comprise the framework.  There are also a number of exception classes.  For fuller details see the JavaDoc for each class.  The classes were originally developed for the author's "Linguist" scripting language, so the class names are all prepended by LDB (Linguist DataBase).

LDBClient

The primary route into the database system is through LDBClient.  It is convenient to subclass this for a new database application, although you can alternatively create an instance and use that. It provides the system with:

A central point that handles access to the database, with the ability to create a new transaction when asked.

A means by which changes to a record cause a notification to be sent to every other client currently interested in the record, by means of the LDBNotifyListener interface.

Every user of a database is a client of the database.  While in use, every record has one or more clients associated with it, one of which may also be the owner of the record.  Ownership is established either by being the creator of the record or the first client to modify an existing record.

LDBRecord

This is an abstraction of a database record and is where most of the work is done.  It contains a table of fields and another of all the clients currently interested in the record. Methods are provided to create the record, to populate it with fields and to set the values of the fields or to retrieve them from the backing store.  There is no dependence on a given record structure; the same class can be used to represent any kind of record.  When an LDBRecord is constructed it is passed a client instance, which provides a route to the transaction manager, and the name of the table that will be used to hold records of the type it will be managing.

LDBField

This class represents a single field in a record, and holds the current value of the field. It also holds a field specification, allowing the system to know how the field contents should be interpreted.  There is little need to deal with this class directly.

LDBFieldSpec

This class holds the name of a field, its type and its size, but not its current value. Each LDBField object contains an LDBFieldSpec in addition to the current value of the field.  The types currently supported are:

LDBTransaction

This class operates behind the scenes, managing individual transactions and maintaining a cache of objects to avoid excessive database traffic. Its most important property is a LDBPersistent object, which provides the access to and from the database in a non-specific manner.

LDBPersistent

This interface specifies the methods that must be implemented by a database driver in order to save and restore records and to perform other related tasks.

LDBSQL

This abstract class is the base class for any database driver. It contains an implementation of the LDBPersistent interface, plus a set of methods for getting SQL command strings.  A subclass of this is needed for each manufacturer-specific database, but will normally only contain specific code for connecting to the database.



Graham Trott
TechModern Limited
August 21, 2000