Introduction

The Java Grinder Persistence Layer is a framework that makes it easy for developers to integrate Java business applications with a database. JGPL increases developer productivity by handling persistence tasks for the developer and by providing support for managing transactions. The purpose and advantages to using transactional and persistence support are discussed in the ObjectWave JGrinder white paper. This document will deal with the application of this framework.

Each task of this tutorial demonstrates the transactional and persistence aspects of the JGPL. These tasks will build on each other. In task one you will create and store one simple domain object. In task two you will create an associate relationship between domain objects. Finally, in task three you will create an aggregate relationship between domain objects. The code for each task is provided for you although it will be more beneficial to write the code on your own by following the tutorial instructions. In addition, you can further test your code by using the provided test GUIs.

Requirements

JDK 1.2 and JFC 1.0 - This can be downloaded for free from JavaSoft web site.

Relational Database

ODBC must be setup/installed.

How it works

It is not necessary for you to understand the internal details of the framework in order to use it effectively. However, some explanation of what classes are used, the various functions of these classes, and the public interfaces to these classes should help you understand what the framework is providing for you.  With this knowledge you can then use the framework to its greatest advantage. This section provides hierarchy information and interface instructions to the transactional and persistence portions of the JGPL framework.  It is not important that you understand these concepts after the first read. The tutorial tasks are designed to help cement your knowledge of these concepts.

Transactional Support

The transactional support allows us to easily commit or rollback changes. The two main classes used in the transactional support of the JGPL are the transaction log and the object editor. The class diagram for these classes is presented in diagram 1.

Typically a transaction log would be created when a user interface is started. As a domain object is modified the modifications are stored in an ObjectEditor, not the object itself. The changes are not added to the domain object until the transaction log is committed. Typically this would be when the user hits the OK button on the UI.

Persistence Support

The persistence support involves the interface between objects living in the virtual machine and some sort of persistence storage. In the case of a relational database this involves creating a connection to the database and mapping objects to database tables. The main classes for this are the DomainObject and the Broker. The Broker sets up a connection to the database. The DomainObject contains the mapping instructions between the DomainObject and its database table. When changes are committed the broker sets up a connection and sends these changes to the database based on the object-to-table mapping found in the DomainObject. In a relational database these changes would be in the form of SQL commands.

Getting Started

The first thing you need to do is check that your are set up properly. This involves the following steps:

Setup your database. Create a new blank database, and save it.

Setup a data source with the name: tutorial. Then connect that data source to the blank database you just created. On this data source, you should also setup a username and password. Remember this username and password, because you will need it later in this tutorial.

Once you have downloaded the .zip file, you should unzip it into a directory

After you have created the data source, you should create the initialization file. That initialization file shoulb be called 'tutorial.ini'. It should be located in the directory which you unzipped the zip file. Simply create a text file and copy the following information.

ow.persistUser=user
ow.persistPassword=password
ow.connectUrl=jdbc:odbc:tutorial
ow.persistDriver=sun.jdbc.odbc.JdbcOdbcDriver
ow.databaseImpl=com.objectwave.persist.broker.AccessBroker
ow.persistVerbose=true

The format of this file is key=value.

The changes you would need to make in this file are the following:

1. Change the value for the key ow.persistUser to whatever username you entered for the datasource.

2. Change the value for the key ow.persistPassword to whatever password you entered for the datasource.

3. Change the value for the key ow.persistUser to 'tutorial'. (If the value already is tutorial, don't change anything).

Overview -

The general steps involved in creating persistent objects using JGPL are outlined below.Each test will cover each of these steps in order.

Create class

a) subclass DomainObject

b) add mutator and accessor methods

c) add initializeObjectEditor() method

d) add initDescriptor() method

e) add update method

1) Database Table changes

2) Test class changes

Task 1: In this task you will create a simple Domain Object and connect it to your database.It will look like this:

The underlying database table structure will look like this:

Person

databaseIdentifier : Number

colname : Text


In MS Access, the databaseIdentifier should be an Autonumber column. With traditional databases, like Oracle, this is not a requirement.

Step 1: Create a persistent object class definition.

A) Subclass Domain Object and locate the necessary Field objects. .

Create a new file called: Person.java.

The class definition for this Object will look like this[1]:

    1. import com.objectwave.persist.AttributeTypeColumn;
    2. import com.objectwave.persist.examples.DomainObject;
    3. import com.objectwave.transactionalSupport.*;
    4. import java.util.*;
    5. import java.lang.reflect.* ;
    6. 
    7. public class Person extends DomainObject {
    8.    protected String name ;
    9.    static Field _name;
   10.    static Vector classDescriptor ;
   11.   static {
   12.      try {
   13.			_name = Person.class.getDeclaredField("name");
   14.			_name.setAccessible( true );
   15.      }
   16.      catch (NoSuchFieldException e) {
   17.          e.printStackTrace();
   18.      }
   19.   }
   20.}

Now let's talk about what is going on.

Lines 1-4 are fairly obvious. There are the minimum import statements that must be included in your class source.

Line 6 is the start of the actual class definition, and states what class you are going to extend. There are a core set of methods necessary to support persistence. These methods are defined in the com.objectwave.persist.Persistence interface. To facilitate application development we have already created a default implementation of these methods. The DomainObject superclass in this example implements the Persistence interface and should ease your application development.
Of course, you could implement your own 'DomainObject' with your own strategies, but the Persistence interface needs to be supported to leverage the power of this framework.  

Line 7 declares an attribute of this object. Typically there will be a table field in the underlying relational database for each of these attributes. We could have the framework look up the field objects instead of declaring static variables. There would be a significant performance penalty if that approach was adopted.

Line 8 is the java.lang.reflect.Field that maps to the attribute.Every attribute should have a corresponding java.lang.Field.  These are necessary because of the way that the altering of objects takes place. For now it is sufficient to say that java.lang.reflect.Field instance variables must be declared for each domain object attribute. [2]

Line 9 is the instance variable for the class descriptor.The class descriptor keeps track of information about the attributes.  More on the classDescriptor later, for now just understand that every Domain Object must have a classDescriptor.

Lines 10 - 18 define the Domain Object's static initializer.This is a block that will initialize the Domain Object's java.lang.reflect.Field whenever this class is referenced. Initialization of the static fields is accomplished as shown. All java.lang.reflect.Field's in the Domain Object should be initialized here in this way.
On line 13 we set the Field to be accessible. THIS IS VERY IMPORTANT. Without this step the JGriner Framework will have fail any attempts to set values on fields that are not declared as public.

That does it for the class definition.Now let's add the methods this class needs.

B) Add Accessor and Mutator Methods

First let's add the accessor method for the name attribute:

 1. public String getName() {
 2. 	return (String)editor.get(_name, name);
 3. }

Line 1 is a typical accessor. It has the method visibility, the return type of the attribute, and the name of the method.  The return type and the method name will change depending on the attribute we are accessing.

Line 2 shows the major difference between a typical accessor and the accessors needed by our Domain Objects.  Instead of accessing the variable directly, we must access it via the Object Editor associated with the Domain Object. The accessors for all of the persistent attributes will be required to delegate the accessor request to the Object Editor.

Now lets add a mutator to the Object.

    1. public void setName(String aValue) {
    2.	editor.set(_name, aValue, name) ;
    3. }

Let's again analyze this method.

Line 1 is pretty typical of a mutator.The method name and the argument type will need to change depending on the attribute being set.

At Line 2 we delegate the set request to the editor. The editor may in turn actually update the object with the new value, or keep it in holding if a transaction is in progress. All of your persistent attributes will need to have mutators that look like this one.

Be sure to use the accessor and mutator methods whenever you need to access the value of an attribute. NEVER access the variable directly.  Accessing variables directly will cause inconsistent results and improper Domain Object states.

C) Add initializeObjectEditor() method

Next add the following method:

    1. public ObjectEditingView initializeObjectEditor() {
    2. 	final RDBPersistentAdapter result = (RDBPersistentAdapter)super.initializeObjectEditor( this );
    3. 	if (classDescriptor == null)
    4. 		initDescriptor();
    5. 	result.setTableName("person");
    6. 	result.setClassDescription(classDescriptor);
    7. 	return result;
    8. }

Let's look a bit more closely at this method.This is the method that is needed to define table name and the description of this class.The only thing that would need to be changed would be in Line 5 where we would change "person" to whatever table in your database was setup to store instances of this Object.

Line 2 might be changed if the user wanted  to use a ObjectEditingView, but for the majority of applications, the default one will do.

Line 3 invokes 'initDescriptor'. In this example we are using a method called 'initDescriptor' to define how our persistent object maps to a database. This is covered in more detail in the next section.

D) Add initDescriptor() method

Next we want to add the following method:

1.  protected void initDescriptor() {
2.  	synchronized (Person.class){
3.  		if (classDescriptor != null) return;
4.  		Vector tmpVector = getSuperDescriptor();
5.  		AttributeTypeColumn attributeMap = AttributeTypeColumn.getAttributeRelation("colname", _name);
6.  		tmpVector.addElement( attributeMap );
7.  		classDescriptor = tmpVector;
8. 		}
9.  }

This method is actually a bit more complex then the others.  The initDescriptor() method is a quite important method.What this method does is set up the classDescriptor and store information about the attributes in the Domain Object. The class descriptor, which was mentioned earlier, holds information pertaining to the different attributes of the Domain Object.The type of attribute each instance variable is stored in the classDescriptor.  More will be said about this method later on in the tutorial.  For now it is sufficient to simply understand what the purpose of the classDescriptor.
Line 5 creates a attribute to column map.The "col_name" argument is actually the column name in the database table which will hold this attribute. The _name argument is the static Field we created earlier that empowers the JGrinder Framework to actually update instances of this persistent object.
Line 6 is where we are actually adding information about the attributes to the classDescriptor.

Your Domain Object is now complete.

Step 2: Setting up the database tables

So let's test it.First thing that must be done is to properly setup the database.It should consist of one table.That table should be called "Person".Your person table will look like this:

Person

DatabaseIdentifier : AutoNumber (for MS Access)

colname : Text

Select the databaseIdentifier field as the primary key for this table.

Step 3: Testing your Domain Objects.

Once the data source is setup, all that we have left is to create a Test class. So let's do that shall we?

Start by creating a file called Test.java (This should be stored in the same directory as your Person.java file).Then type the following class definition:

import com.objectwave.persist.*;
import com.objectwave.transactionalSupport.*;
import java.util.* ;

public class Test {

}

So far it is pretty simple, and there is not much to discuss.  Now, let's add a constructor for the Test class.

1. public Test() {
2. 	super();
3. 	com.objectwave.appSupport.StartupRoutine.loadDefaults(this,"tutorial.ini");
4. 	BrokerFactory.useDatabase();
5. }

Line 3 is an important line of code here. This line of code reads in the properties needed for the tutorial from an initialization file. Discussion of the initialization file is deferred until later on in the tutorial. Several properties are read from an ".ini" file by this method and set to System.properties. Any ".ini" file can be read from this same line of code, all you would need to change would be the name of the file ("tutorial.ini")[3]

Line 4 tells the broker factory that we are going to be using a database as the underlying data storage. It will look for several configuration parameters from the System properties. You could change line 3 to simply hard code the setting of the System properties instead of reading them from an ini file and the useDatabase method would be none the wiser.

Lets run some code by adding a main method.

 1.  public static void main(String args[]) {
 2.  		Test t = new Test();
 3.		Session session = Session.createAndJoin( "MainThreadSession" );
 4.    	if (args.length > 0) {
 5.      		if (args[0].equals("populate"))
 6.              	t.populate( session );
 7.      		else if (args[0].equals("query"))
 8.              	t.doQueries();
 9.      		else System.err.println("invalid argument: " + args[0];);
10.      		return;
11.   	} else {
12.         	t.populate();
13.         	t.doQueries();
14.   	}
15.}

This method simply calls the methods that will do the work. It checks the arguments passed via command line to decide whether it should populate the database, or query the database.
Line 3 creates a com.objectwave.transactionalSupport.Session object. A session defines a transaction context. In this case I am creating a new Session object and then having the current thread join that session (hence the name createAndJoin). The string value of MainThreadSession is just some arbitrary name that was made up for this example. It could be usefull for debugging activities occurring in multiple sessions.

A) Insert Data

Next let's add the populate method.This is the method that will insert new instances into your database.

 1.  public void populate(Session session) { 
 2.		session.startTransaction("RDB");
 3.		Person p = new Person();
 4.		p.setName("John Smith");
 5.		p = new Person();
 6.		p.setName("James Doe");
 7.		p = new Person();
 8.		p.setName("Tom Jones");
 9.		p = new Person();
10.		p.setName("Michael Jordan");
11.		p = new Person();
12.		p.setName("Sam Sneed");
13.		p = new Person();
14.		p.setName("Nick Fitz");
15.		try {
16.  		session.commit();
17.		}
18.		catch (Throwable e) {
19.  		session.rollback();
20.  		e.printStackTrace();
21.		}
22.	}

Line 2 starts the transaction. Any changes made to a persistent object will be tracked using a com.objectwave.transactionalSupport.TransactionLog. The RDB string argument tells the Transaction support that you want to use the TransactionLog class that has been associated with the String RDB. If no associations exist, the default in memory TransactionLog instance will be used. If the BrokerFactory.useDatabase call has been made, then a subclass of TransactionLog called BrokerTransactionLog has been associated with the RDB String. This is very important if you actually want a commit to interact with a persistent broker.
(tip: If you create a new persistent object, but don't make any changes, then the transaction log will not know about your new persistent object. This means it will not be inserted into your database. So, eiter always make a change or use the 'insert' method defined in the Persistence interface to force a persistent object to be a part of the current transaction)

The changes made by the mutator method will be stored in the ObjectEditor until we commit the changes at line 16.

Lines 3-4,5-6, 7-8, etc. create several persistent objects and set some values. As mentioned earilier, the act of setting the values will inform the current transaction that some database activity will be needed on this persistent object.

Line 16 terminates the current transaction. In this example, it will result in six new person records being inserted into the database table. The  commit() method throws an com.objectwave.persist.UpdateException that must be caught, hence lines 24 and 26-29.If any errors occur you should rollback any changes -- Line 28. Failing to either commit or rollback a transaction would be a grave mistake. Since the JGrinder framework is not omnipotent, it can not determine when your application should commit or rollback code. So, if a neither a commit or rollback occurs you'll have a dangling 'startTransaction' without a corresponding commit. The next time this method is called, the subsequent startTransaction call will result in a new nested transaction. A nested transaction does not actually write to the database upon commit. A nested transaction will simply pass any changes made up to the outer transaction. If the outer transaction is now dangling we will never succeed in getting any records to the database.

B) Query Data

Now lets add the query method.This is the method that will query the database and return any Domain Objects that fit the attributes you specify.

 1.	public void doQueries() {
 2.
 3.		Person p = new Person();
 4.		SQLQuery query = new SQLQuery(p);
 5.		p.setName("Michael Jordan");
 6.		System.out.println("List of all emplyees with the name:" + p.getName());
 7.		try {
 8.			Vector v = query.find();
 9.			for (int i =0; i < v.size(); i++) {
10.				Person result = (Person) v.elementAt(i);
11.				System.out.println("Name: " + result.getName() + "Id: " + result.getObjectIdentifier());
12.			}
13.		}
14.		catch ( QueryException e) {}
15.		Person p2 = new Person();
16.		query = new SQLQuery(p2);
17.		p2.setName("%J%");
18.		query.setAsLike(true);
19.		System.out.println("List of all people with the letter 'J' in their name :");
20.		try {
21.			Vector v = query.find();
22.			for(int i = 0; i < v.size();i++){
23.				Person result = (Person) v.elementAt(i);
24.				System.out.println("Name: " + result.getName() + "Id: " + result.getObjectIdentifier());
25.			}
26.		}
27.		catch (QueryException e) { e.printStackTrace();}
28.	}

Lines 6 -8 create a new query, and sets the value to match.In this case the name being matched. This Query will return all people with the name "Michael Jordan".

Line 6 creates a blank Person Object.  This is done so that the SQLQuery object knows what kind of objects we will be looking up.

Line 7 creates the SQLQuery object.  The constructor takes a Domain Object as an argument in order to know which object (or table in this case) it will be looking up.

Line 8 sets the value of name in this person.  This way the SQLQuery will search for all people with the name "Michael Jordan".

Line 11 is the line that actually searches in the data storage.To initiate a search, we simply call the SQLQuery.find() method.  This method returns all the Domain Objects that match the criteria in a java.util.Vector.

Lines 18-21 demonstrate a more complex query.  Lines 18 and 19 are the same as the previous example, but in Line 20, we are setting the name to the value "%J%".  In JGrinder, the character % is a wildcard.  So in this case we are searching for any Person with the letter J in his/her name.

Line 21 is also important.  When doing a wildcard search we must tell the SQLQuery to use a LIKE comparison instead of an = comparison.  So we setAsLike(true) in order for SQLQuery to handle the wildcard character properly and return the correct results.

Now we are ready to run this part. First, we must compile the classes we just created.  To do this we must change to the directory where thesefiles are stored.  Then execute the command :

                javac Person.java Test.java

If any errors are displayed, check the source code again and make sure it is correct.

Once compilation executes successfully we can run the test.  We want to run the test twice.  The first time will be to populate the database, the second to query.

To populate the database, execute the following command from the directory in which the Person.class and Test.class files are located:

                java Test populate

Once it is finished executing, you should open the database and verify that the entries were indeed added to the proper tables.  If it indeed did populate properly, then go we should execute the query.To do this, execute the following command from the same directory :

                java Test query

This should run properly and you should the results of the queries that we created printed to System.out.

If for some reason you can not execute or compile, and it is not a syntactical error, check the classpath.Ensure that both the .jar file that was downloaded and the directory that contains that .jar file are both in the classpath.Also, ensure that the tutorial.ini file is in the same directory as the jar file.  If your classpath is setup properly there should not be any problem compiling or executing, but if a problem persists, you can always execute a copy of this example that we provide by issuing the following command (from any directory):

java com.objectwave.tutorial.part1.Test populate

And then execute:

java com.objectwave.tutorial.part1.Test query

Task 2 In this task we will expand our original Domain Object (DO) and then we will create an associative relationship with a new Domain Object that we will create.It will look something like this:

 

The underlying database structure will look like this :

Person

databaseIdentifier : AutoNumber

colname : String

department : Number (or LongInteger)

Department

databaseIdentifier : AutoNumber

budget : LongInteger

name : String

 

In a relational database, associative relationships are accomplished with the use of Foreign Keys.In this example, Person would have a foreign key to Department.

Step 1: Alter our existing Person class.

A) Subclass Domain Object

The new class definition will look like this :

 1. import com.objectwave.persist.AttributeTypeColumn;
 2. import com.objectwave.persist.examples.DomainObject;
 3. import com.objectwave.transactionalSupport.*;
 4. import java.util.*;
 5. import java.lang.reflect.* ;
 6. 
 7.  public class Person extends DomainObject {
 8.  	protected String name ;
 9.  	protected Department department ;
10.  	static Field _name;
11.  	static Field _department ;
12.  	static Vector classDescriptor ;
13.  	static {
14.  	try {
15.  		_name = Person.class.getDeclaredField("name");
16.			_name.setAccessible( true );
17.  		_department = Person.class.getDeclaredField("department");
18.			_department.setAccessible( true );
19.  	}
20.  	catch (NoSuchFieldException e) {
21.  		e.printStackTrace();
22.  	}
23.  }
24.
}

As you can see, on line 9, we added a new attribute: The department attribute.The department attribute will be an instance of Department, which we will create shortly. Department will also be a Persistent object.

Notice that on line 11 we also added a java.lang.reflect.Field instance that corresponds to the new attribute.

Then on line 17, we added the initialization of that field into the static initializer.  This would have to be done on any new attributes added to a Persistent Object. The rest of the class definition is the same.

Next we need to add accessors and mutators for the new attribute. The code for these methods will be very similar to the existing accessors and mutators. Let's take a look at the accessor first:

 1.  public Department getDepartment() {
 2.  	return (Department)editor.get(_department, department);
 3.  }

and the mutator:

 1.  public void setDepartment(Department aValue) {
 2.  	editor.set(_department, aValue, department);
 3.  }

No additional explanation is necessary here.They are in the same format as the existing accessors and mutators.  Only the types and field names have changed.

The interesting changes occur in the initDescriptor() method :

 1.  protected void initDescriptor() {
 2.  	synchronized (Person.class) {
 3.  		if (classDescriptor != null) return;
 4.  		Vector tmpVector = getSuperDescriptor();
 5.  		AttributeTypeColumn nameColumn = AttributeTypeColumn.getAttributeRelation("name", _name);
 6.  		tmpVector.addElement( nameColumn );
 7.  		AttributeTypeColumn foreign = AttributeTypeColumn.getForeignRelation(Department.class, "department", _department);
 8.  		tmpVector.addElement(foreign);
 9.  		classDescriptor = tmpVector;
10.  	}
11.  }

Line 7 introduces a new concept.  We are introducing a new attribute type: the Foreign Key. When a Persistent object holds on to a reference to another Persistent object, they are said to have a foreign relation. That relation must be reflected in the classDescriptor. We do this by creating a ForeignRelation AttributeTypeColumn object (like line 7).

Creation of the Department persistent object begins with the creation of a file called Department.java.

Now let's add the class definition:

 1. import com.objectwave.persist.AttributeTypeColumn;
 2. import com.objectwave.persist.examples.DomainObject;
 3. import com.objectwave.transactionalSupport.*;
 4. import java.util.*;
 5. import java.lang.reflect.* ;
 6. 
 7.  public class Department extends DomainObject {
 8.  	static Field _budget;
 9.  	static Field _name;
10.  	static Vector classDescriptor;
11.  	protected String name;
12.  	protected int budget ;
13.  	static { 
14.  		try{
15.  		_name = Department.class.getDeclaredField("name");
16.  		_name.setAccessible( true );
17.  		_budget = Department.class.getDeclaredField("budget");
18.  		_budget.setAccessible( true );
19.  		}
20.  		catch (NoSuchFieldException ex) { 
21.  			System.err.println(ex); 
22.  		}
23.  	}
24.  }

This follows the format laid out by the Person object.  All Persistent objects will have the same format.  Only the specifics of each will be different.

B) Add mutator and accessor methods

Have a look at the accessor/mutator pairs. There is not much to discuss about it, just looking at the code should explain enough.

//Mutator for budget attribute
public void setBudget( int newValue ){
	editor.set(_budget, newValue, budget);
}
// Accessor for budget attribute
public int getBudget(){
	return (int)editor.get(_budget, budget);
}
//Accessor for name attribute
public String getName() {
	return (String)editor.get(_name, name);
}
// Mutator for the name attribute
public void setName(String aValue){
	editor.set(_name, aValue, name);
}

C) Add initializeObjectEditor method

And then the initializeObjectEditor() method must be added.

1.  public ObjectEditingView initializeObjectEditor(){
2.  	final RDBPersistentAdapter result = (RDBPersistentAdapter)super.initializeObjectEditor();
3.  	if(classDescriptor == null) initDescriptor();
4.  	result.setTableName("department");
5.  	result.setClassDescription(classDescriptor);
6.  	return result;
7.  }

Make sure that in line 4 you have the correct table name once again.

D) Add initDescriptor method

And finally we add the initDescriptor() method.

1.  protected void initDescriptor() {
2.  	synchronized (Department.class) {
3.  		if (classDescriptor != null) return;
4.  		Vector tmpVector = getSuperDescriptor();
5.  		tmpVector.addElement( AttributeTypeColumn.getAttributeRelation( "name",_name));
6.  		tmpVector.addElement( AttributeTypeColumn.getAttributeRelation( "budget",_budget));
7.  		classDescriptor = tmpVector;
8.  	}
9.  }

This method is also quite simple.Lines 5 and 6 mark the attributes as simple attributes.

So now we have created a new Persistent object and altered an existing one.  So we have our new Person object with a foreign reference to a Department, and we have the Department object.

Step 2: Setting up the tables in the database.

Now we must setup the database tables to hold on to our new Objects.  So first, let us create the new Department table.The department Object will look like this :

Department

databaseIdentifier : AutoNumber

budget : LongInteger

name : String

So add a table with the preceding attributes to your existing database.  The databaseIdentifier attribute should be the primary key.

Next let's alter the Person table. Your new person Object will look like this:

Person

databaseIdentifier: AutoNumber

colname : String

department : LongInteger

The only difference between the existing person table and the new one will be the department attribute.So go ahead and add the department attribute and set it to be a foreign key that references department.

Once you have added the tables and attributes, you should then save the new database schema.

After the schema has been altered at any time, you must go through and clear our any data that already exists in your database. We are now ready to test the new Domain Objects.

Step 3: Testing the new Domain Objects.

In order to test the new Domain Objects, we need to create a new Test class.  So instead of re-writing the whole Test class, let's just alter the existing one.  The changes to be made are to the populate() and the doQueries() methods.  Lets look at the populate method first :

A) Insert Data

public void populate(Session session) {
	session.startTransaction( "RDB" );
	Department d1 = new Department();
	d1.setName("Accounting");
	d1.setBudget(23000);
	Department d2 = new Department();
	d2.setName("Marketing");
	d2.setBudget(67000);
	Department d3 = new Department();
	d3.setName("Shipping");
	d3.setBudget(54000);
	Department d4 = new Department();
	d4.setName("Sales");
	d4.setBudget(43000);
	Person p = new Person();
	p.setName("John Smith");
	p.setDepartment(d1);
	p = new Person();
	p.setName("James Doe");
	p.setDepartment(d2);
	p = new Person();
	p.setName("Tom Jones");
	p.setDepartment(d3);
	p = new Person();
	p.setName("Michael Jordan");
	p.setDepartment(d4);
	p = new Person();
	p.setName("Sam Sneed");
	p.setDepartment(d1);
	p = new Person();
	p.setName("Nick Fitz");
	p.setDepartment(d2);
	try {
	    session.commit();
	}
	catch (Throwable e) {
	    tl.rollback();            
	    e.printStackTrace();
	}
}

B) Query Data

Now let's take a look at the new doQueries() method :

 1.  public void doQueries() {
 2.  
 3.  	Department d = new Department();
 4.  	SQLQuery q = new SQLQuery(d);
 5.  	d.setName("Accounting");
 6.  	String dName = d.getName();
 7.  	System.out.println("Querying for departments with name " + dName);
 8.  	try {
 9.  		Vector v = q.find();
10.  		System.out.println("Found " + v.size() + " department(s) with the name : " + dName);
11.  		for (int i = 0; i < v.size(); i++) {
12.  			d = (Department) v.elementAt(i);
13.  		}
14.  	}
15.  	catch (QueryException e) { e.printStackTrace();}
16.  	Person p = new Person();
17.  	SQLQuery query = new SQLQuery(p);
18.  	p.setDepartment(d);
19.  	System.out.println("List of all employees in the " + dName + " department");
20.  	try {
21.  		Vector v = query.find();
22.  		System.out.println("Found " + v.size() + " employees in the " + dName + " department");
23.  		for (int i =0; i < v.size(); i++) {
24.  			Person result = (Person) v.elementAt(i);
25.  			System.out.println("Name: " + result.getName() + "Id: " + result.getObjectIdentifier());
26.  		}
27.  	}
28.  	catch ( QueryException e) {}
29. }

The goal of this query is twofold. First, we want to find all the departments with the name "Accounting" and then we want to find all the employees in those departments.  Lines 3-15 accomplish the first half of the query.  

Line 3: Creates a blank department.

Line 4: Creates a new SQLQuery on that department.

Line 5: Sets the value of name (the attribute we will be using to search through the tables)

Line 9: Performs the query and stores the results in a Vector.

Line 10 : Prints out the results.

Lines 11-13 loop through the results.

Then once the first half of the query is complete, the next part of the query will find all the employees within the set of departments returned by the first query.

Line 16 creates a blank person. 

Line 17 creates a new Query on that person.

Line 18 sets the value of the department we want to match against.

Line 21 performs the query and returns the set of matching instances.

Line 23-26 loops through the results and prints them out.

You now have a complete Test class for Task 2.To execute the class, follow the instructions from Task 1.

If problems arise, and you can not run the class after checking for syntax, then try the following command :

java com.objectwave.tutorial.step2.Test populate

or

java com.objectwave.tutorial.step2.Test query

Task 3 - In Task 3 we will expand further on our existing Domain Objects in order to create an aggregate relationship. This relationship will look like this :

 


The underlying database will have three tables.The two will be the same as in Task 2, but the new table (the Phone table) will look like this.

Phone

databaseIdentifier : AutoNumber

num : String

type : String

person : LongInteger

Once this new table is added to the database, you should set number to be the primary key and person to be a foreign key to the Person table.

Notice that there is no direct connection from Person to Phones in the database model, even though in the object model, person is hold onto a vector of Phones. Based on the attribute type you will set in the initDescriptor() method (this will be shown later) JGPL will know to update or query this separate table for phones based on the person foreign key in the Phone table.

Step 1 Create Class

A) Subclass Domain Object

Let's start by creating our new Persistent object. The new Persistent object will be a Phone object. This will represent a person's telephone number.  A Phone object will have 4 attributes : databaseIdentifier, number, a type and the person to which this number belongs

Let's take a look at the class definition.

1. import com.objectwave.persist.AttributeTypeColumn;
2. import com.objectwave.persist.examples.DomainObject;
3. import com.objectwave.transactionalSupport.*;
4. import java.util.*;
5. import java.lang.reflect.* ;
6. 
7.  public class Phone extends DomainObject {
8.  	static Field _num;
9.  	static Field _type;
10.  	static Field _person;
11.  	static Vector classDescriptor;
12.  	protected Person person;
13.  	protected String type ;
14.  	protected String num ;
15.  	static { 
16.  		try{
17.  			_person = Phone.class.getDeclaredField("person");
18.  			_person.setAccessible( true );
19.  			_type = Phone.class.getDeclaredField("type");
20.  			_type.setAccessible( true );
21.  			_num = Phone.class.getDeclaredField("num");
22.  			_num.setAccessible( true );
23.  		}
24.  		catch (NoSuchFieldException ex) { System.out.println(ex); }
25.  	}
26.  }

The class definition is pretty simple.It has the attributes and the Fields, and the initialization of the Fields.  pretty standard

The constructor for this class is just as simple.

public Phone() 
{
}

B) Add accessor and mutator methods

And the accessor/mutator pairs need no explanation.

public String getNum() {
	return (String)editor.get(_num, num);
}
public void setNum(String aValue) {
	editor.set(_num, aValue, num);
}
public Person getPerson(){
	return (Person)editor.get(_person, person);
}
public void setPerson(Person aValue){
	editor.set(_person, aValue, person);
}
public String getType(){
	return (String)editor.get(_type, type);
}
public void setType(String aValue){
	editor.set(_type, aValue, type);
}

C) Add initializeObjectEditor method

And then the initializeObjectEditor() method (all that needs to be changed here is the table name which is reflected below.

public ObjectEditingView initializeObjectEditor()
{
	final RDBPersistentAdapter result = super.initializeObjectEditor();
	if(classDescriptor == null) initDescriptor();
	result.setTableName("phone");
	result.setClassDescription(classDescriptor);
	return result;
}

D) Add initDescriptor method

And finally, the initDescriptor() method.

1.  public void initDescriptor() {
2.  	synchronized (Phone.class) {
3.  		if (classDescriptor != null)return;
4.  		Vector tmpVector = getSuperDescriptor();
5.  		tmpVector.addElement( AttributeTypeColumn.getForeignRelation(Person.class, "person", _person));
6.  		tmpVector.addElement( AttributeTypeColumn.getAttributeRelation("type", _type));
7.  		tmpVector.addElement( AttributeTypeColumn.getAttributeRelation("num", _num));
8.  		classDescriptor = tmpVector;
9.  	}
10.}

The main thing to understand here is in Line 5. The Person attribute is a foreign key. It refers to the Person table.

And there you have a new Phone persistent object.

The Department object from the previous examples will remain the same. There are no changes to be made.So let's move on to the Person object. The new class definition will look like this:

 1.  import com.objectwave.persist.examples.*;
 2.  import com.objectwave.transactionalSupport.*;
 3.  import java.util.*;
 4.  import java.lang.reflect.* ;
 5.  public class Person extends DomainObject {
 6.  	protected String name ;
 7.  	protected Department department ;
 8.  	protected Vector phones = new Vector() ;
 9.  	static Field _name;
10.  	static Field _department;
11.  	static Field _phones;
12.  	static Vector classDescriptor;
13.  	static {
14.  		try {
15.  			_name = Person.class.getDeclaredField("name");
15.  			_name.setAccessible( true );
16.  			_department = Person.class.getDeclaredField("department");
16.  			_department.setAccessible( true );
17.  			_phones = Person.class.getDeclaredField("phones");
17.  			_phones.setAccessible( true );
18.  		}
19.  		catch (NoSuchFieldException e) {
20.  			e.printStackTrace();
21.  		}
22.  	}
23.  }

 The Person object now contains a new attribute.This new attribute is the phones Vector. This kind of attribute is known as a collection.It is a collection because it can contain multiple values (or instances of a Persistent object). In this particular example, the Person object has a collection of phones because any person may have 0 or more phone numbers.  A Person may have a home number and a fax number and a pager, etc. Since one attribute can not hold on to many different instances of a Domain Object, we instead add a collection attribute (in this case a Vector) that can hold many different instances of a Persistent object.[4]  

Once the class definition is modified, we must then add accessors and mutators for the new attributes.So lets take a look at those.

public Vector getPhones(){
	return (Vector)editor.get(_phones, phones);
}
public void setPhones(Vector aValue) {
	editor.set(_phones, aValue, phones);
}

Nothing to discuss about these methods. They conform to previous standards.

The next method that has changed is the initDescriptor() method.

1.  public void initDescriptor() {
2.  	synchronized (Person.class) { 
3.  		if (classDescriptor != null) return;
4.  		Vector tmpVector = getSuperDescriptor();
5.  		tmpVector.addElement(
AttributeTypeColumn.getAttributeRelation("col_name", _name)); 6. tmpVector.addElement(AttributeTypeColumn.getForeignRelation(Department.class, "department", _department)); 7. tmpVector.addElement(AttributeTypeColumn.getCollectionRelation(Phone.class, _phones)); 8. classDescriptor = tmpVector; 9. } 10. }

Line 7 is the only change in this method from the previous.  If we have a collection relation, we need to specify that in the classDescriptor.  The way to do that is shown in line 7.  The convenience method used is AttributeTypeColumn.getCollectionRelation(Class c, Field f);

The arguments of this method are :
c - the Class of the object that will be contained in this collection.
f - the java.lang.reflect.Field which corresponds to the attribute variable.

That is all that is necessary to designate an attribute as a collection.  The rest of the method is the same as in the past.

The final method that we should add to our new Person Object is a convenience method.  We will call it the addPhone() method.It will implement the addition of a Phone to a Person's collection of Phones. Let's take a look at the method:

1.  public void addPhone(Phone p) {
2.  	p.setPerson(this);
3.  	getPhones().addElement(p);
4.  }

Line 2 sets the current Person as the owner of the Phone passed to the method.This must happen in order for JGrinder to properly understand the collection relation.  If this step is not performed, the Person attribute of the Phone table in the database will not be filled out properly and thus any queries will produce improper results.

Line 3 adds the Phone to the collection of phones.  This Line of code would vary depending on the structure of the Collection class and the API that the collection class uses.

It is recommend to add a convenience method to any Persistent object that contains a collection relation. The reason for this is to hide the underlying structure.  Anyone who uses your Persistent objects should not know or care about what the underlying collection class is.

And that is your new Person Object.  So now all we have left to do is to test it.

Step 2- Database table changes

So before we talk about the test class, we should go thorough and clear out any entries in the existing tables in our database.

Then we need to add the new Phone table. This table will look like this:

Phone

databaseIdentifier : AutoNumber

num : String

type : String

person : LongInteger

The databaseIdentifier should be the primary key, and person should be a foreign key to the Person Table.

No other tables need to be altered. On the database side, the department and and the Person tables remain the same.[5]

Step 3 - Test class changes

A) Insert data

Now lets alter the Test class.The only method that will change, obviously will be the populate()and the doQueries() methods.

Let's look at the populate() method first.

 1.  public void populate( Session session) {
 2.  	session.startTransaction("RDB");
 3.  	Department d1 = new Department();
 4.  	d1.setName("Accounting");
 5.  	d1.setBudget(23000);
 6.  	Department d2 = new Department();
 7.  	d2.setName("Marketing");
 8.  	d2.setBudget(67000);
 9.  	Department d3 = new Department();
10.  	d3.setName("Shipping");
11.  	d3.setBudget(54000);
12.  	Department d4 = new Department();
13.  	d4.setName("Sales");
14.  	d4.setBudget(43000);
15.  	Phone p1 = new Phone();
16.  	p1.setType("fax");
17.  	p1.setNum("708-413-3124");
18.  	Phone p2 = new Phone();
19.  	p2.setType("home");
20.  	p2.setNum("773-890-0012");
21.  	Phone p3 = new Phone();
22.  	p3.setType("home");
23.  	p3.setNum("312-459-0899");
24.  	Phone p4 = new Phone() ;
25.  	p4.setType("home");
26.  	p4.setNum("312-413-0098");
27.  	Person p = new Person();
28.  	p.setName("John Smith");
29.  	p.setDepartment(d1);
30.  	p.addPhone(p1);
31.  	p.addPhone(p2);
32.  	p = new Person();
33.  	p.setName("James Doe");
34.  	p.setDepartment(d2);
35.  	p.addPhone(p3);
36.  	p = new Person();
37.  	p.setName("Tom Jones");
38.  	p.setDepartment(d3);
39.  	p.addPhone(p4);
40.  	try {
41.  		session.commit();
42.  	}
43.  	catch (Throwable e) {
44.  		session.rollback(); 
45.  		e.printStackTrace();
46.  	}
47.  } 

Line 2 actually creates a new Transaction (this is review).

Lines 3-14, create new Departments

Line 15-26, etc. create new Phones

Lines 32-35 create a new Person.   Let's examine these lines more closely.

Line 32 : Create a new blank person.

Line 33 sets the name of the person.

Line 34 sets the department of the person (one of the departments we created earlier in the method).

Line 35 adds a Phone (one that was created earlier in the method) to the collection of phones via the convenience addPhone() method.

Line 41 : We commit the changes and it all gets written to the database.

And that is the new populate method.

B) Query data

Now let's see the doQueries() method.

 1. public void doQueries() {
 2. 
 3. 	Department d = new Department();
 4. 	SQLQuery q = new SQLQuery(d);
 5. 	d.setName("Accounting");
 6. 	String dName = d.getName();
 7. 	System.out.println("Querying for departments with name " + dName);
 8. 	try {
 9. 		Vector v = q.find();
10. 		System.out.println("Found " + v.size() + " department(s) with the name : " + dName);
11. 		for (int i = 0; i < v.size(); i++) {
12. 			d = (Department) v.elementAt(i);
13. 		}
14. 	}
15. 	catch (QueryException e) { e.printStackTrace();}
16. 	Person p = new Person();
17. 	SQLQuery query = new SQLQuery(p);
18. 	p.setDepartment(d);
19. 	System.out.println("List of all employees in the " + dName + " department");
20. 	try {
21. 		Vector v = query.find();
22. 		System.out.println("Found " + v.size() + " employees in the " + dName + " department :");
23. 		for (int i =0; i < v.size(); i++) {
24. 			Person result = (Person) v.elementAt(i);
25. 			System.out.println("Name: " + result.getName() + "Id: " + result.getObjectIdentifier());
26. 			Vector newVector = result.getPhones();
27. 			System.out.println(result.getName() + "'s Phone Numbers :" );
28. 			for (int j = 0; j < newVector. size(); j++ ) {
29. 				System.out.println("\t" + ((Phone)newVector.elementAt(j)).getNum());
30. 			}
31. 		}
32. 	}
33. 	catch ( QueryException e) {}
34. }

This is a 3-part query.It is meant to find every department with the name "Accounting", then find everyone in that department, and then print out the phone numbers of everyone in the account department.

The first two parts are the same as the previous example query.  What has been added is in lines 23-30.

Line 23 and 24 cycles through the people in the accounting department.

Line 25 prints out the name.

Line 26 gets the collection of phones that this person has. Notice you don’t have to manually query the phones.That is because of the Collection Relation that we specified in the classDescriptor.  Once we specify a collection in the classDescriptor, JGrinder will then go through and query the collection values on its own.

Line28then traverses through the phone numbers while line 29 prints them out.

So now you have written 3 examples using Persistent objects.  You are now well on your way to writing persistent objects using the JGrinder Persistence Framework.

Common Exceptions



[1]

Although the attribute databaseIdentifier was not added to the Domain Object in this tutorial, it is an inherited attribute from com.objectwave.persist.examples.DomainObject. It is the most important attribute as well. Every instance of a Domain Object must have a unique databaseIdentifier. It can be thought of as a primary key, except that in the table setup, you need not select databaseIdentifier as the primary key. (Any field in the table which will also be unique may be chosen as the primary key)

[2] Not all instance variables are considered attributes.When the term "attribute" is used it implies that this variable holds information about the state of Domain Object.Other variables such as temporary, swap, or counter variables that do not contain information about the state of the Object should be marked as transient and should not have a corresponding java.lang.reflet.Field variable.

[3] Although I state that any .ini file can be read, the file must be located somewhere in the classpath or in a jar file

[4] In this case we are using a Vector for our collection.But we are not tied in to Vector.We could use an array or some other type of collection class.As of right now though Vector and array are the only supported collections.

[5] Even though we added an attribute we still do not need to alter the database table.The reason for this is that we added a collection attribute that can not be displayed in the database table.Therefore the only indication of the assosciation between Person and Phone will be the foreign key in Phone