Package com.objectwave.persist

Persistence is the process by which an object lives beyond the life of the currently executing Virtual Machine.

See:
          Description

Interface Summary
Broker The API for persistence.
BrokerPropertyIF Brokers may have properties associated with them.
CollectionAdapter This class enables the generic persistence framework to support multiple collection types.
Persistence A persistent object can either implement all of the necessary support for persistence, or have an adapter who does it.
PersistLogIF A simple interface that will be used as the glue to plug in whatever logging mechanism necessary.
PrimaryKeyStrategy Implement this interface if the code is to assist with the creation with the generation of PrimaryKeyValues.
RDBPersistence In order be used with in the RDB supporting framework, all of the following methods must be implemented.
SQLConvertExceptionIF Instances of this class will convert generic SQL exceptions to specific exceptions.
 

Class Summary
AbstractBroker Can assist with the basic implementation of a broker.
AttributeTypeColumn A class that is used to describe an object's attribute.
BrokerFactory Factory may be an incorrect name.
BrokerTransactionLog Provide some database specific transactional stuff.
GrinderResultSet Private class for use in this package for returning two elements from a method.
JoinField  
ObjectFormatter This class converts data from one type to another.
ObjectFormatter.Test Unit tests.
SQLQuery Used to define dynamic queries at an object level.
SQLQuery.Test Unit test
Type  
 

Exception Summary
QueryException Little more than an exception wrapper class.
 

Package com.objectwave.persist Description

Persistence is the process by which an object lives beyond the life of the currently executing Virtual Machine. There can be many versions of persistence, a text file, a binary file containing serialized objects, or commonly, a relational database. There are benefits and drawbacks to almost any type of persistence implementation, however, this should be a secondary problem when solving a business application need. Our persistence framework isolates the issues associated with persistence and abstracts them from our application code. This allows us to change our persistence with zero impact on existing application code.

Persistence Mapping

To effectively add a new domain object to our system, you need to define it persistence map. This map is how our generic access layer can translate an object and it's relations into a relational database. It is important that the map be correct, or the generic access layer will not be able to store and retrieve objects from the database.

There exists some rules that constrain your code once you've decided to use the generic access layer. You must have a field in the object that is a unique object identifier. This field should map to a column in the database table that represents the primary key field. You should NEVER access a local variable directly. ALWAYS use an accessor method. For example. Example 1

public class Employee
{
String name;
public String getName(){ return name; }
public void badMethod() { System.out.println(name == null); }
public void goodMethod() { System.out.println(getName() == null);
}

In this example our Employee class has a 'name' attribute. The 'badMethod' directly accesses this variable. This would be incorrect in our application if Employee is a persistent object. The 'goodMethod' uses the accessor method to obtain the value for 'name', thus insuring accurate results. It is not clear in this example the importance of this, since this Employee is not persistent, but it illustrates my intention about directly accessing variables.

Let's expand this example to include two new objects. An Phone and an Office.

public class Office
{
Integer objectIdentifier;
String name;
Vector phoneList;
}

public class Phone
{
Integer objectIdentifier;
String number;
Office office;
}
public class Employee
{
Integer objectIdentifier;
String name;
Office office;
}

Examining the classes reveals that and Employee has an Office, an office has a list of Phones, and a Phone has an Office. Lets expand this to say that our Employee may or may not have an Office, while the Phone requires and Office. You could say that the Employee-Office relation is an association and that the Office-Phone relation is an aggregate collection.

A Database that would represent this could have three tables, an "office", "phone", and an "employee" table. The "phone" table has a foreign key to the office table. The "employee" table has a foreign key to the office table. The Office table has no foreign keys. Every table has a primary key field of databaseIdentifier.

How would we map our objects to the database? Any RDBPersistent object will need to implement a method called, getClassDefinition(). This method should return a Vector of com.objectwave.persist.AttributeTypeColumn(s) for each attribute in the business object.

There are 6 types of AttributeTypeColumns. PRIMARYATT, ATTRIBUTE, TYPEATT, FOREIGN, INSTANCE, and COLLECTION. There can only be one, and at least one, AttributeTypeColumn of the PRIMARYATT type in the Vector. There can only be one or zero TYPEATT. There may be 0..n of any of the other types.
An ATTRIBUTE type represents any scalar datatype. Strings, int, boolean, Double, etc.... Additionally, you can use an ATTRIBUTE type to represent a native collection of Strings or ints (String [] or int []). These native arrays are converted into a large string using an escape sequence to separate each entry. Note, you could easily overrun the length of a VARCHAR field and you should be careful when using this functionality. The method 'AttributeTypeColumn>>getAttributeRelation(String columnName, Field fd)' is used for creating new instances of these objects.
A TYPEATT is used when a map is going to represent objects of different types. This is necessary when multiple objects reside in one table. The method 'AttributeTypeColumn>>getTypeAttributeRelation(String columnName, Field fd)' is used for creating new instances of these objects.
A COLLECTION type is useful for when you have a collection of Domain Objects. This can either be a Vector or a native array. The method 'AttributeTypeColumn>>getCollectionRelation(Class c, Field fd)' is used for creating new instances of these objects.
The FOREIGN type indicates a strong relation from one object to the next. This is the type you would use if the underlying database table had a mandatory foreign key. The method 'AttributeTypeColumn>>getForeignRelation(Class c, String columnName, Field fd)' is used for creating new instances of these objects.
An INSTANCE type indicates more of an associative relation. It may, or may not, have any column on the underlying database table. Either 'AttributeTypeColumn>>getInstanceRelation(Class c, Field fd)' or 'AttributeTypeColumn>>getInstanceRelation(Class c, String columnName, Field fd)' can be used to create instances of this object.

Lets look at the maps for our example classes.

Employee

Office

Phone

Hopefully, you can look at these maps and understand each one. The "string" at the start of each map entry represents the database column name. The only map entry without a "string" is the collection. Why is that? Well, there is no entry in the office database table for the collection of phones. The collection is actually created by finding all of the phones with the foreign key to the Office.

Persistence Execution

This section will discuss how the Persistence layer does the magic that it does. It is intended to be an overview the persistence process. We'll explore the collaboration between the com.objectwave.persist.RDBPersistentAdapter , the object that implements the com.objectwave.persist.Persistence interface, the com.objectwave.persist.BrokerFactory and the com.objectwave.persist.RDBBroker.

Every persistent object needs to implement the Persistence interface. A persistent object is by nature a TransactionalObject. It needs to support commit and rollback functionality, additionally, there are five functions specific to Persistence not found in a TransactionalObject. The ability to save, or make persistent, the persistent object. The ability to delete, or remove from persistence, the persistent object. The ability to determine if an object is currently persistent, or, if it is a new object (not yet made persistent). The ability to get the object's unique identifier. Every persistent object must have some unique identifier that distinguishes it from all of the other objects found in the persistent store. Finally, the ability to delegate all of these functions to another persistent object. We allow this, so that most of the difficult functionality associated with performing these objects can placed on another object. This allowed us use interfaces when developing the persistence support. By supporting delegation it is not required that a persistent object subclass from any other specific object. Every persistent object could implement everything over and over again, but, the delegation model is there if you wish to use it.

That last little bit leads me to the RDBPersistentAdapter. This class does most of the work necessary to successfully implement persistence. This class subclasses from ObjectEditor and can therefore acts as an ObjectEditor for any of our persistent objects. As an ObjectEditor, every accessor and mutator call is delegated to the RDBPersistentAdapter. As a Persistent object, the save, delete, and other functions necessary for persistence are also delegated the the RDBPersistentAdapter. When any of the Persistent related functions occur, the Persistent adapter in turn passes the call onto the current default Persistence Broker. This is obtained via the BrokerFactory.getDefaultBroker().

The BrokerFactory may be misnamed, but it is with this object that one would obtain a handle to the current Persistent broker. At the time of this writing there are two possible brokers. The RDBBroker providing support for a relational database and an ObjectPoolBroker providing support for working with a serialized collection of persistent objects. When an application begins, it can set whatever broker is necessary. All brokers implement the com.objectwave.persist.Broker interface.

The RDBBroker is designed to work with a Persistent object, and in particular, an RDBPersistence interface, to provide persistence. The RDBPersistentAdapter implements the RDBPersistence interface and it is intended that you use this class, rather than roll your own, to provide persistence to your object.

When an RDBBroker class receives a command, it will always take a persistent object (or a collection of persistent objects) as the argument. The RDBBroker uses the RDBPersistence object to inquire about the mappings between the Persistent object and the database. With knowledge provided by the RDBPersistence object, the appropriate SQL is generated to perform the appropriate actions. If this happens to be a query, then the process is completed by the RDBBroker by processing the result set and building the correct Persistent objects.

It is necessary for a persistent object to initialize the ObjectEditor (RDBPersistentAdapter) with the map information the RDBPersistence object is to provide. An example:

public RDBPersistentAdapter initializeObjectEditor()
{
0 final RDBPersistentAdapter result = super.initializeObjectEditor();
1 if(classDescriptor == null) initDescriptor();
2 result.setClassDescription(classDescriptor);
3 result.setTableName("employee");
4 return result;
}

The line 0 is just creating a new instance of a RDBPersistentApater.
Line 1 is checking to see if we have already defined this persistent object's mapping. The variable 'classDescriptor' is java.util.Vector containing AttributeTypeColumn objects.
Line 2 is setting the description on the RDBPersistentAdapter.
Line 3 is telling the RDBPersistentAdapter what table name to use.
Line 4 is returning this new initialized RDBPersistentAdapter. This new instance will be used as the ObjectEditor for this persistent object.

Queries for Existing Persistent Objects

Building queries will require absolutely NO understanding of underlying persistence store. Our query is currently called com.objectwave.persist.SQLQuery, but that really is a misnomer. The best way to learn queries is through examples. We'll start simple and work are way up.

{
Employee search = new Employee();
SQLQuery q = new SQLQuery(search);
return q.find();
}

This preceding example will return a Vector of all Employee objects found in the database. It is actually quite simple.

{
Employee search = new Employee();
SQLQuery q = new SQLQuery(search);
search.setName("dave");
return q.find();
}

Similarly to the last one, this will return a Vector of employee objects. However, it will only find those whose name.equals("dave"). If there was another attribute, say lastName, and we said 'search.setLastName("hoag")' we would find all Employee's whom have a first name of dave and a last name of hoag.

{
Employee search = new Employee();
SQLQuery q = new SQLQuery(search);
search.setFirstName("d%");
q.setAsLike(true);
return q.find();
}

Once again our result set would consist of Employee objects. This time it has found only those employees who have a name starting with 'd'. The '%' is a wildcard character and the 'setAsLike' method call is telling our query to use the term LIKE instead of '=' for comparison.

{
Employee search = new Employee();
SQLQuery q = new SQLQuery(search);
search.setObjectIdentifier(new Integer("100"));
q.setFieldConstraint(search, "objectIdentifier", ">");
q.setIsNull(search, "name");
return q.find();
}

Surprisingly enough, this collection contains Employee objects. The selected employees are limited to those employee's who have an ObjectIdentifier > 100 and a name == null.

{
Employee search = new Employee();
SQLQuery q = new SQLQuery(search);
search.setOffice(new Office());
search.getOffice().setName("ObjectWave");
return q.find();
}

You might have guessed by now that this would return a collection of Employee objects. The only employees in the collection are those that have an Office and that Office name equals "ObjectWave". Of course, I could have set additional attributes or constraints and it would probably work as you expect.

Object Pool

An object pool can basically be thought of as a just a collection of Objects. In the context of our generic persistence support our com.objectwave.persist.ObjectPool object will contain all of the Objects that are retrieved from the database. When a subsequent request for a particular object is made, we first look for the object in the ObjectPool. This will create instance integrity (every instance of object 1 will be the same exact same instance), however, this comes with significant performance overhead. In general, our system should execute with out any ObjectPool functionality.

To use object pooling requires a simple method call RDBBroker>>setUsingObjectPool(boolean). If the value of the boolean is true, object pooling is enabled, false, it is disabled. If you already have an object pool that you wish to use, just call setObjectPool(ObjectPool) prior to calling the setUsingObjectPool(boolean) method. When object pooling is enabled, the broker first checks to see if there is an existing pool it should use. If there is none, a new one is created. You can always obtain the current ObjectPool via the getObjectPool() method.

Once we have an ObjectPool, we can exploit it with an com.objectwave.persist.ObjectPoolBroker. The ObjectPoolBroker is another broker that implements the Broker interface. Hence, it to can be used in our TransactionFramework, our SQLQueries, and any other subsystems that use Brokers. If you have an ObjectPoolBroker, you essentially have another Persistent store. This time, however, the persistent store is a serialized file instead of a relational database.

Using object pooling in the RDBBroker in combination with the ObjectPoolBroker can enable a database dependent application to run without the database! This can be done by turning on object pooling when you have a database connection. Any object that is 'found' during that connection will placed into the ObjectPool. When you complete the application, you could write the ObjectPool and an associated ObjectPoolBroker to a local serialized file. When next you start the application, and you fail to obtain a database connection, you can use the ObjectPoolBroker. Once the application begins using the ObjectPool, you can issue queries, make updates to objects, and add new objects, all without changing a line of application code.

Custom Collections

Out of the box ObjectWave JavaGrinder supports two collection types: Native arrays and Vectors. When defining an attribute as a collection it is required that the field is either a native Array or a Vector. While this may address ninety percent of application needs, there exists a mechanism to provide support for custom collections.

The needs of every application are unique to that application. It is impossible for the developers of Java Grinder to provide support for building any possible collection a user may need. Instead Java Grinder uses an adapter class to create the correct collection types. The com.objectwave.persist.CollectionAdapter interface defines the methods necessary to integrate with Java Grinder. When retrieving data from the database, the addElement method will be invoked for each element that is to be added to the collection. Once the getCollection method is invoked, Java Grinder is done processing the collection (if you are using a single instance of a collection adapter, this would be the point at which you would know that the next 'addElement' request is for a new collection).

There are two ways to use collection adapters. The first is to create an instance of a collection adapter and associate it with an AttributeTypeColumn. Use the setCollectionAdapter method after defining a collection attribute to set the instance of the collection adapter you wish to use. The single instance associated with the AttributeTypeColumn will be reused for every query involving the collection attribute. The getNewInstance() method will be invoked when creating the proxy SQLQuery object.

Secondly you can register your custom CollectionAdapter with the list of known collection adapters. The com.objectwave.persist.SQLQuery contains a list of known collection adapters. To add your collection adapter to this list, use the SQLQuery.registerCollectionAdapter(Class collectionClass, Class adapterClass) method. When finding a collection of a type that is an instance of 'collectionClass', a new instance of 'adapterClass' will be created and used for the collection. This will be overridden if the AttributeTypeColumn technique has been used.