16-Jul-04 (Created: 16-Jul-04) | More in 'OSCON-2004'

Declarative Relational Datasets: Example code

Disclaimer

Please note that these are only examples to demonstrate the pattern of usage. If you were to try the code with Aspire minor changes may be needed such as classnames or some anticipated features

A thousand mile journey starts with a simple first step

Consider the following properties file


request.getRows.classname=com.ai.db.DBRequestExecutor2
request.getRows.db=my-database
request.getRows.stmt=\
  select * from my-table \
  where 1=1 \
     and my-col-1={arg1} \
   and my-col-2={arg2.quote} \
   and my-col-3=16

This section defines a transaction called "getRows". Two parameters "arg1" and "arg2" are required to retrieve data for this transaction. The reusable part responsible for retrieving the data in this case is DBRequestExecutor2. The data is to be retrieved from a database called "my-database" and using the select statement listed. Once this definition is available, a programmer can do the following in the code


Map args = new HashMap();
args.put("arg1","some-int-value");
args.put("arg2","some-string-value");
IDataCollection col = Data.ExecuteQueryTransaction("getRows",args);
IIterator itr = col.getIIterator();
for(itr.moveToFirst();itr.isNotAtTheEnd();itr.moveToNext())
{
    IDataRow dr = (IDataRow)col.getCurrentElement();
}   
col.close();

Typed relational data sets

Change the configuration slightly


request.getRows.classname=com.ai.db.DBRequestExecutor2
request.getRows.db=my-database
request.getRows.stmt=\
  select * from my-table \
  where 1=1 \
     and my-col-1={arg1} \
   and my-col-2={arg2.quote} \
   and my-col-3=16
request.getRows.domain-object=com.mypkg.MyDomainObject

See how the domain object is used in code


Map args = new HashMap();
args.put("arg1","some-int-value");
args.put("arg2","some-string-value");
IDataCollection col = Data.ExecuteQueryTransaction("getRows",args);
IIterator itr = col.getIIterator();
for(itr.moveToFirst();itr.isNotAtTheEnd();itr.moveToNext())
{
    MyDomainObject typedDomainObject = (MyDomainObject)col.getCurrentElement();
}   
col.close();

Same client code but the data comes from a simulated local file

Only change is to the properties file, client won't know the difference


request.getRows.classname=com.ai.data.FileReader
request.getRows.filename=c:\some-root\{arg1}\{arg2}.txt

Same client code but the data comes from a stored procedure

Only change is to the configuration file


request.getRows.classname=com.ai.db.StoredProcExecutor2
request.getRows.db=my-database
request.getRows.stmt=\
  call pkg1.sp_getRows({arg1},{arg2.quote});

Let us do some updates

Let us start with a definition of our transaction


request.updateRow.classname=com.ai.db.DBRequestExecutor2
request.updateRow.db=my-database
request.updateRow.stmt=some-update-statement-with-args

Let us see what happens to the client code


Map args = new HashMap();
args.put("arg1","some-int-value");
args.put("arg2","some-string-value");

Data.ExecuteUpdateTransaction("updateRow",args);

If anything goes wrong an exception will result. The result is automatically wrapped in a transaction

Let us raise the bar with updates

Let us define one more transaction


request.updateRow1.classname=com.ai.db.DBRequestExecutor2
request.updateRow1.db=my-database
request.updateRow1.stmt=some-update-statement-with-args.

Let us combine the two updates into a single composite transaction:


request.compositeUpdate.classname=com.ai.db.MultiRequestExecutor
request.compositeUpdate.db=my-database
request.compositeUpdate.query_type=update
request.compositeUpdate.request.1=updateRow
request.compositeUpdate.request.2=updateRow1

Both updates will take place with in a single transaction context. And the client code to invoke the "compositeUpdate" will look very similar to the first client code.


Map args = new HashMap();
args.put("arg1","some-int-value");
args.put("arg2","some-string-value");

Data.ExecuteUpdateTransaction("compositeUpdate",args);

Selects in the middle of an update


request.compositeUpdate.classname=com.ai.db.MultiRequestExecutor
request.compositeUpdate.db=my-database
request.compositeUpdate.query_type=update
request.compositeUpdate.request.1=getRows
request.compositeUpdate.request.2=updateRow1
request.compositeUpdate.request.3=updateRow1

Assuming getRows will retrieve only one row, the values are available as arguments to the components down the pipeline

Writing one of these parts is quite simple


public class MyDataCollectionProducer 
            extends AbstractDataCollectionProducer
{
   
   IDataCollection execute(String requestName, Map args)
   {
      //Read any additiona configuration parameters for this class
      String param1 = 
         config.getValue(requestName + ".param1","default-value");

      //Some how produce a data collection
   }
}

Here is how you specify this part in the config file


request.getRows.classname=MyDataCollectionProducer
request.getRows.param1=some-value

Same is true for an update part.