PersistentObject
instances and
the AbstractDomain
sub-classes that manipulate these instances. However, the architectural changes included
in this release will require application code modifications. The most significant change to JRF architecturally is the elimination
of the JDBCHelper
class from the framework. The functionality and much of the code of JDBCHelper
still exists in smaller, task-specific classes. If an application does not make extensive use of direct JDBCHelper
calls in AbstractDomain
contexts, code modifications will be fairly minimal.
See Modifying Existing Code with JDBCHelper Arguments for complete details on how to modify existing JRF applications.
Code generation is no longer an ancillary piece of JRF, it is now a central part of the framework. While it is certainly
possible to hand-write most of your PersistentObject
s and AbstractDomains
, you no longer have to do this.
Two code generators are provided, one for generating base direct table-to-object mappings and one for generating composite objects
that may include detail tables, join columns or both. These generators are extensible in several ways.
First, since the code generators read XML files for the information, you can add more values in the DTD (Document Type Definition)
files to expand the functionality. You can also sub-class the code generation entity handling classes for table, column
and so forth. Finally, you can sub-class the generators themselves if you want even more control.
A short term "todo" is to write a program that will read the database metadata and generate a skeletal XML file that may be used by the base generator.
SELECT
statement in only
two formats: a single PersistentObject
instance for primary key fetches or a java.util.List
of
PersistentObject
s for multiple row fetches. Under this version, application developers may implement
an interface called ApplicationRowHandler
, which will allow them to store data in any format
they choose and even abort a large multi-row fetch if desired. This interface is basically one level of abstraction
up from the existing protected RowHandler
interface in AbstractDomain
. Whereas
RowHandler
requires a sub-class of AbstractDomain
to process a java.sql.ResultSet
,
ApplicationRowHandler
can be implemented outside the domain and processes a
PersistentObject
instance already constructed by the framework. Some convenient default implementations of
ApplicationRowHandler
are provided in the framework.
ColumnSpec
have been streamlined with the
introduction of class net.sf.jrf.column.ColumnOption
. Introducing this class has eliminated the need
for multiple constructors in the specification class implementations. For example, this following:
this.addSubtypeColumnSpec( new StringColumnSpec( "DepartmentCode", // column name "getDepartmentCode", // getter "setDepartmentCode", // setter ColumnSpec.DEFAULT_TO_EMPTY_STRING, ColumnSpec.REQUIRED));should be replaced with:
this.addSubtypeColumnSpec( new StringColumnSpec( "DepartmentCode", // column name new RequiredColumnOption(), "getDepartmentCode", // getter "setDepartmentCode", // setter ColumnSpec.DEFAULT_TO_EMPTY_STRING);
ColumnOption
is simply a wrapper for the existing options:
public abstract class ColumnOption { protected boolean i_required = false; protected boolean i_primaryKey = false; protected boolean i_sequencedPrimaryKey = false; protected boolean i_naturalPrimaryKey = false; protected boolean i_unique = false; protected boolean i_subtypeIdentifier = false; protected boolean i_optimisticLock = false; . .Sub-classes of
ColumnOption
provide various combinations of the boolean values under one general description.
The hierarchy is:
ColumnOption NullableColumnOption RequiredColumnOption OptimisticLockColumnOption PrimaryKeyColumnOption NaturalPrimaryKeyColumnOption SequencedPrimaryKeyColumnOption UniqueColumnOption SubtypeColumnOption
PersistentObject
attributes. For all practical purposes, however, you will need to use the new code generators to implement primitives.
AbstractStaticDomain
to allow both the caching of all records and the caching of a maximum value of records through an LRU (least-recently-used)
mechanism. The implementation of the LRU cache is provided by the Apache
Collections project; the
common-collections.jar
is included in the release. The most important feature introduced is
the capability of ensuring that the cache is kept up-to-date with database changes. The limitation, of course, is that
the cache will only be valid if all updates are done though the same JVM instance. Caching of composite objects is
supported in a limited way. (Future versions will overcome this limitation).
ColumnSpec
exist:
BinaryStreamColumnSpec
ByteArrayColumnSpec
DateColumnSpec
(Stored as a date in the database, seen as java.util.Date in the Java space)
ClobColumnSpec
BlobColumnSpec
DayOfWeekColumnSpec
(Stored as a locale-specific string in database,
seen as Calendar.SUNDAY
etc. in Java space).
SerializableObjectColumnSpec
BooleanColumnSpecChar1
(Store in database as CHAR(1) "Y/N", "T/F" etc., seen as a boolean
in Java space).
BinaryStreamColumnSpec
, ClobColumnSpec
,
and BlobColumnSpec
must use an ApplicationRowHandler
implementation to
manipulate data from a query. Once java.sql.ResultSet.next()
is called, most JDBC drivers will
close all streams on the current row. Thus applications have to be able to access the current row before next()
is called by the framework.
CREATE TABLE
statements.
For applications that use AbstractDomain.createTable()
, this version now supports optimal column
definition expressions statements for CREATE TABLE
. There were two general changes made in order
to successfully implement this feature:
DatabasePolicy
methods to construct the correct
SQL column expressions for binary, text and numeric columns given an appropriate set of parameters. See
DatabasePolicy
for description and parameter requirements of these methods.
ColumnSpec
implementations.
AbstractColumnSpec
is now:
AbstractColumnSpec
SizedColumnSpec
(Column types that have a maximum size. A zero value denotes no maximum.
Maximum will be restricted to the database vendor's limits)
TextColumnSpec
(Stores text data)
StringColumnSpec
StringArrayColumnSpec
ClobColumnSpec
BinaryColumnSpec
(Stores binary data)
BinaryStreamColumnSpec
ByteArrayColumnSpec
SerializableObjectColumnSpec
BlobColumnSpec
NumericColumnSpec
(Stored numeric data with precision and scale)
IntegerColumnSpec
LongColumnSpec
DoubleColumnSpec
FloatColumnSpec
BooleanColumnSpec
NullableBooleanColumnSpec
org.vmguys.appgen.jrf.GenerateDatabase
).
DatabasePolicy
instance no longer exists in the
framework. It is now possible for applications to construct AbstractDomain
s that connect to
to different database vendors seamlessly. See Architectural Changes for more details. Also please
examine the documentation if jrf.properties
to see how to set up multiple connections.
JTA
compliant, some functionality does exist to handle transactions across databases without using
a JTA
implementation. See Architectural Changes for more details.
AbstractDomain
will handle them. (see Speed Enhancements).
AbstractDomain.validate()
.
ColumnSpec.validateValue()
has been added to check for 1) Strings
that exceed
the maximum column length in the database (always); 2) Values that exceed a given maximum value
(optional); 3) Values that are less than a given minimum value (optional)
and 4) values that do not match a set of mandatory values (optional).
PersistentObject
support in AbstractDomain
. It has always been
possible to construct quite complex PersistentObject
instances that may include embedded
PersistentObject
s. However, application code needed implementations of postFind
,
postSave
and so forth to effectively construct PersistentObject
for a fetch operation or
deconstruct one for a save operation.
In a sense, embedded PersistentObject
support was implicit. Support for embedded objects is now
explicit in this version. A method now exists in AbstractDomain
to support the saving both the
master domain object and all embedded objects. See Architectural Changes for more details.
AbstractDomain
. In the previous
version of JRF, the default behavior was to refetch the saved object. This functionality was definitely a
performance detractor and the default behavior now is to not return the saved object. Modifications
to the framework have been made to make every effort to populate the PersistentObject
instance passed
in for the update with recent values for: 1) sequenced columns; 2) Integer optimistic lock columns. Eventually
the framework may be changed to always return something and never null
.
However, this change has been avoided since it may break the logic of many existing applications.
TimeStampColumnSpec
into two implementions. The time stamp column for JDBC is
a bit confusing and an attempt has been made in the framework to clarify between two separate application
uses of a java.sql.Timestamp
column: one where the application (client or server) determines the value
and one where the database always determines the value through its system timestamp function.
The former use has the current class name and the latter uses the name TimeStampColumnSpecDbGen
.
All timestamp optimistic lock columns should use the new column specification.
DatabasePolicy
and
AbstractDomain
have been considerably clarified. See the javadoc for DatabasePolicy
for more information. Also, see Embedded Persistent Objects section below.
java.sql.ResultSet
accessor methods rather
than the column name methods, aliasing is no longer necessary. The usage of column indexes rather than column names for
java.sql.ResultSet
accessors also provides a slight performance increase.
SQLServerSybaseDatabasePolicy
has been renamed
to SQLServerDatabasePolicy
.
PersistentObject
to return the
encoded key of the object (e.g. the same value that would be returned by AbstractDomain.encodePrimaryKey()
.
This method was added to support application's need of using the keys without instantiating or using an
AbstractDomain
implementation. Code generators provided will generated the code for this method.
Changes to enhance speed include:
PersistentObject
to
SQL constructs and vice versa was a considerable performance detractor. Applications that use this version of JRF
may either use the reflection optimization code or eliminate reflection altogether. An interface called
GetterSetter
contains methods to get a particular attribute value from a PersistentObject
and conversely set an
attribute in the PersistentObject
. Each instance of ColumnSpec
contains an implementation
of GetterSetter
. Manually writing such implementations is of course utterly impractical. A simple
code generator exists to generate implementations for a particular instance of PersisentObject
.
(see net.sf.jrf.codegen.GetterSetterBuilder
).
In fact, existing applications can use the generator to construct implementations and AbstractDomain
will automatically use these implementations without any application code changes.
AbstractDomain
now exclusively uses java.sql.PreparedStatement
s
to save, delete and fetch by primary key value; static SQL is restricted to ad-hoc queries
(e.g. findWhereOrderBy()
). Consistent use of static SQL detracts from
performance on two levels. First, the SQL strings doing standard operations of save and delete must be re-built every time,
iterating through the list of column specifications and making decisions on the requirements of those specifications.
Second, many database engines such as Oracle and Sybase support a 'parse-once-execute-many' concept where
parameterized SQL is parsed only once and executed numerous times. For such databases, the performance boost of
using prepared statements is considerable. Using prepared statements in domains poises the JRF framework to properly
handle the JDBC Version 3.0 functionality of pooled prepared statements (See specification for JDBC Version 3.0 on the
Sun website).
AbstractDomain
instance to use a dedicated
connection thus persisting any prepared statements to the connection. Once JDBC Version 3.0 statement pooling is
in place, using dedicated connections will probably not be necessary.
AbstractDomain
. Some of the more "heavy-weight" objects in
AbstractDomain
are now built only once and reused (for example the SQLSelectBuilder
instance).
From a larger perspective, AbstractDomain
sub-classes themselves have always been rather heavy-weight objects with
considerable constructor overhead cost. With the addition of more features, they are even more heavy-weight.
However, if instantiated once in the constructor of some service class, performance should not suffer. For example the
following:
public class MyService { private DomainOne domain1; private DomainTwo domain2; public MyService() { domain1 = new DomainOne(); domain2 = new DomainTwo(); } public PersistentObject findSomethingOne(String data) { return domain1.find(data); }is preferable to:
public class MyService { public MyService() { } public PersistentObject findSomethingOne(String data) { return new DomainOne().find(data); }
Applications should generally either pool a service with wrapped domains or pool the domains themselves.
AbstractDomain.validate()
).
While this methodology still exists, an alternative faster way of handling duplicate keys has been
introduced. Most commercial database vendors return a specific error code for duplicate key inserts. A
DatabasePolicy
method has been added to return this value. AbstractDomain
will now
check the error code of SQLException
and return the existing DuplicateRowException
for
duplicate key insert operations (e.g. save()
). For database vendors that do not supply error codes,
specific error text can be specified in the DatabasePolicy
to implement duplicate key error handling.
For example, from Postgres:
public String getDuplicateKeyErrorText() { return "Cannot insert a duplicate key into unique index"; }
PersistentObject
support in AbtractDomain
.
JDBCHelper
class. JDBCHelper
is bit of a catch-all, non-object-oriented utility class.
It handled connections, pooling (with JDBCHelperPool
), statement execution and result set management. While useful for
general utilities, it becomes up a bit unwieldy when used with AbstractDomain
leads to such limitations as
lack of handling multiple result sets.
The SQL management and database connection functionality of JDBCHelper
has been broken into smaller classes
and pooling now is reserved only to implementations of javax.sql.DataSource
or
javax.sql.ConnectionPoolDataSource
.
The JRF framework now contains a default implementation of javax.sql.ConnectionPoolDataSource
. Applications may use
this implementation or one of the many commercial or open source versions that exist. The design approach for the
new connection management was to center on the needs of AbstractDomain
.
A class called JRFConnection
encapsulates connection information in JRF in this version.
An AbstractDomain
sub-class will contain a single instance of this class. Unlike previous versions,
applications have no direct control over the setting the connection handling for AbstractDomain
s.
Rather, applications have indirect control over the connection mechanism through
one of three ways (this text is replicated in the javadocs for AbstractDomain
):
JRFConnectionFactory.create()
will instantiate a
JRFConnection
instance based on the JRF system Properties
instance.
This way of handling connections should suit the majority of applications.
Properties
instance to use
for creating a JRFConnection
instance via a call to AbstractDomain.setProperties()
.
The domain will then use the JRFConnectionFactory.create(Properties)
version
to obtain a JRFConnection
instance. This factory method will use whatever
properties exist in the supplied argument and fallback on the JRF system Properties
for any property value it does not find. See
the documentation for JRFConnectionFactory
for more details on this behavior.
DataSource
instance to use via a call to setDataSource
.
This methodology will suit applications where properties to instantiate a DataSource
are
not known at program start up. For example, an application server may receive a message with the requisite
parameters for obtaining a DataSource
through JNDI
.
Transaction Management
The handling of transactions in this version is considerably different from previous versions. For a single
AbstractDomain
instance the transaction handling is automatic (much in the way it was before
except that now transactions including embedded objects are supported - see Embedded Persistent Objects).
For applications that wish to support transactions using multiple domains, the approach has been streamlined.
Previously, a multiple domain manual transaction was implemented rather indirectly:
JDBCHelper helper; ADomain domainA; BDomain domainB; . . domainA.setJDBCHelper(helper); domainB.setJDBCHelper(helper); try { domainA.beginTransaction(); // Both use same JDBCHelper . // set some values. domainA.save(...); //etc. } catch ( .... //etc.The approach for multiple domains for this version is to use a class called
JRFWriteTransacton
:
ADomain domainA ; BDomain domainB; JRFWriteTransaction wt = new JRFWriteTransaction(); . . MyConstructor() { wt.addDomain(domainA); wt.addDomain(domainB); . . } . . . wt.beginTransaction(); try { // set some values. domainA.save(...); //etc. } catch (Exception ex) { wt.abortTransaction(); } wt.endTransaction();In the previous version both domains had to use the same connection. In this version connection management is of no concern to the application; domainA could point to Sybase and domainB could point to Oracle.
PersistentObject
s in
AbstractDomain
was implicit. A user could populate embedded fields and objects by implementing the
suite of protected methods that have no implementation in the base class (
(e.g. preSave(),postValidate(),postFind()
).
This version now explicitly handles the "pieces". The protected methods
are still supported, but an application can move away from these methods to handle embedded PersistentObject
s
by implementing a class called
EmbeddedPersistentObjectHandler
. Since a single save operation may update many embedded objects,
a new PersistentState
of DeletedPersistentState
was added. By using this state,
an application can pass a complex object graph back to the domain to have only portions of the object deleted.
See EmbeddedPersistentObjectHandler
and AbstractDomain
javadocs for more information.
Use the code generator EmbeddedGenerator
to generate the embedded objects.
source/org/vmguys/dtd
.
org.vmguys.appgen.Generator
. Examine
the protected constructor in this file which does the following:
private static final String classTemplate = "public class $classNameObj$ extends PersistentObject $interfaces$ {\n"+ . .
The two tokens enclosed in the "$" would be replaced in the generated code might be:
public class Name extends PersistentObject implements PersonEntity { . .
org.vmguys.appgen.SourceGenXMLEntity
. At a minimum, this class
contains a hash map of all element attributes. Sub-classes may also have some setter methods if desired.
To handle these classes, a meta data class exists called org.vmguys.appgen.SourceGenXMLEntityMetaData
.
It is in this class the all the magic happens. The generators build a hierarchy of these meta classes and
call a single static method to map the contents of the parsed XML document into a tree of
SourceGenXMLEntity
nodes. Examine the static method createEntityTreeFromXML()
.
For each element, the hash map of element attributes is filled, any setter methods are invoked
(XMLReadHandler.elementToObject()
) and finally, an abstract method implementation (resolveImpliedKeys()
)
is called to allow the SourceGenXMLEntity
sub-class to sort out any issues with "implied"
elements.
To see a simple example of the tree creation process, examine the constructor of BaseGenerator.java
. For
a complex example, examine the constructor of EmbeddedGenerator
.java. One of the key concepts here
is that the constructors will instantiate the SourceGenXMLEntity
sub-classes for table, column and so forth
from names specified in the properties files. If no names are specified the default base versions are used. You can
sub-class these base versions to perform all sorts of tricks specific to an application.
BaseGenerator.java
and EmbeddedGenerator.java
.
/** Allows a sub-class to add additional code for embedded objectThe above example suggests a different approach providing implementations of the protectedAbstractDomain
. * @param current currentCompositeObjectXMLEntity
. * @return any additional code to place at the end of generatedAbstractDomain
. */ public String getAdditionalDomainCode(CompositeObjectXMLEntity current) { String interfaces = current.getTransientKeys().get("interfaces"); if (interfaces.indexOf("PersonEntity") != -1) { return " public void preValidate( ... } }
AbstractDomain
methods.
Rather than manually implementing these methods, let the code generator handle this task.
postFind()
. Modify all implementations to the following signature:
protected void postFind(PersistentObject aPO,JRFResultSet baseFindResultSet)
JRFResultSet
has all the methods that JDBCHelper
has to obtain result set data so all you
need to do is change the signature. If you have to execute a query here use:
JRFResultSet other = this.getJRFConnection().getStatementExecuter().executeQuery(" ....");
preValidatate(),preSave()
etc. Modify all with the following signatures:
protected void preValidate(PersistentObject aPO, StatementExecuter stmtExecuter) protected void preSave(PersistentObject aPO, StatementExecuter stmtExecuter) protected void postSave(PersistentObject aPO, StatementExecuter stmtExecuter) protected void preDelete(PersistentObject aPO, StatementExecuter stmtExecuter) protected void postDelete(PersistentObject aPO, StatementExecuter stmtExecuter)You can use
StatementExecuter
to process any SQL statement. Furthermore, you many process as many result sets
as you choose.
getJDBCHelper
and setJDBCHelper
; they will generate runtime exceptions.
protected int executeSQLUpdate(String sql, JDBCHelper aJDBCHelper)
will still work but the helper argument
will be ignored.
protected void executeSQLQuery(String sql, RowHandler aRowHandler, JDBCHelper aJDBCHelper)
will work
but the helper argument will be ignored.
find...(X, JDBCHelper)
methods will work but the helper argument will be ignored.
JDBCHelper
will work but the JDBCHelper
argument will be ignored:
save(),delete(),validate(),findLong(),findInteger(),findTimestamp()
.
beginTransaction
and endTransaction
now do nothing.
RowHandler
interface, the signature for the single method is now:
public void handleRow(JRFResultSet resultSet)
TimestampColumnSpec
to TimestampColumnSpecDbGen
.
AbstractStaticDomain
is now deprecated. The same functionality exists by using AbstractDomain
and
calling setCacheAll(true)
.
DatabasePolicy
, the method
public static DatabasePolicy getDatabasePolicy()
is no longer supported. If you need to obtain the default
DatabasePolicy
instance, do the following:
JRFConnection conn = JRFConnectionFactory.create(); DatabasePolicy policy = conn.getDatabasePolicy();
StreamTokenizer
instead of StringTokenizer to format the encoded string.
If your code manually examines this key, you will have to make changes:
Old version: 1|MFG New version: 1|"MFG"
StreamTokenizer
is used so that quotes and bars ('|') may be embedded in a String
key value.
AbstractColumnSpec
.
GenerateDatabase
has been successfully run on only
Postgres and MySql. Other databases need to be tested. DatabasePolicy methods
for obtaining create index formats may need to be abandoned.