Chapter 3. Castor JDO

3.1. Castor JDO - An introduction

Authors

Werner Guttmann ()

3.1.1. What is Castor JDO

Castor JDO is an Object-Relational Mapping and Data-Binding Framework, which is written in 100% pure Java[tm]. Castor can map relational database data into Java Data Object based on user-defined mapping schema. In the other point-of-view, it provides java data objects a persistence layer.

It frees programmer from dealing with database directly and replacing the entire burden of composing updating the database. Proper SQL statements are automatically generated for loading, updating, creating and deleting. Changes to objects in transaction are automatically done to data source at commit time. Thus programmer can stay in pure-Java without remember all the details in the backing database, after the creation of database tables and the mapping schema. The separation of persistence and programming logic also enable much clearer object-oriented design, which is important in larger projects.

3.1.2. Features

JDO is transactional. Data objects loaded in Castor are properly locked and isolated from other transactions. Castor supports full 2-phase commit via xa.Synchronzation. Castor supports several locking modes, including "shared", "exclusive", "database locked", and "read-only".

  • Shared access, the default, is useful for situations in which it is common for two or more transactions to read the same objects, and/or update different objects.

  • Exclusive access uses in-memory locks implemented by Castor to force competing transactions to serialize access to an object. This is useful for applications in which it is common for more than one transaction to attempt to update the same object, and for which most, if not all access to the database is performed through Castor.

  • Database-Locked access is often used for applications in which exclusive locking is required, but in which the database is frequently accessed from applications outside of Castor, bypassing Castor's memory-based locking mechanism used by "exclusive access" locking.

  • Read-Only access performs no locking at all. Objects read using this access mode are not locked, and those objects do not participate in transaction commit/rollback.

In addition, Castor supports "long transactions", whichs allow objects to be read in one transaction, modified, and then committed in a second transaction, with built-in dirty-checking to prevent data that has been changed since the initial transaction from being overwritten.

Through automatic dirty-checking and deadlock detection, Castor can be used to ensure data integrity and reduce unnecessary database updates.

A subset of OQL, defined in the Object Management Group (OMG) 3.0 Object Query Language Specification, is supported for interacting with the database. OQL is similar to SQL, though operations are performed directly on Java objects instead of database tables, making the language more appropriate for use within a Java-based application.

Castor implements a data cache to reduce database accesses, providing several alternative LRU-based caching strategies.

Castor supports different cardinalities of relationship, including one-to-one, one-to-many and many-to-many. It also supports both object and database record inheritance. It distinguishes between related (i.e. association) and dependent (i.e. aggregation) relationships during an object's life cycle, automatically creating and deleting dependent objects at appropriate times in the independent object's life cycle.

Multiple-column primary keys, and a variety of key generators are supported.

Castor automatically manages persistence for objects that contain Java collection types, including Vector, Hashtable, Collection, Set, and Map. Lazy loading (of collections as well as simple 1:1 relations) is implemented to reduce unnecessary database loading. Lazy loading can be turned on or off for each individual field (of any supported collection type for 1-to-many and many-to-many relations).

Other features include a type converter for all Java primitive types (see the info on supported types).

No pre-processor (aka pre-compiler), class enhancer (bytecodes modification) is needed or used for data-binding and object persistence.

Castor JDO works in an application that uses multiple ClassLoaders, making it possible to use in an EJB container or servlet, for example. A Castor-defined callback interface, "Persistent", can be implemented if the objects wants to be notified on Castor events: jdoLoad(), jdoCreate(), jdoRemove() and jdoTransient(). This makes it possible to create user-defined actions to take at various times in an object's life cycle.

The Java-XML Data-Binding Framework (Castor XML) has been merged with Castor JDO for users who need both O/R Mapping and Java-XML Data-Binding together.

The following relational databases are supported:

  • DB2

  • Derby

  • Generic DBMS

  • Hypersonic SQL

  • Informix

  • InstantDB

  • Interbase

  • MySQL

  • Oracle

  • PostgreSQL

  • Progress

  • SAP DB / MaxDB

  • SQLServer

  • Sybase

Database support includes Oracle 8.1.x and different versions of Sybase Enterprise and Anywhere. Users can implement the Driver interface to adapt Castor to the differences in SQL syntax supported by different relational DBMS's, as long as the DBMS supports JDBC 2.0, multiple ResultSet, and transactions. Thanks to many individual open source contributors, drivers for different database are available.

3.2. Castor JDO - First steps

3.2.1. Introduction

This guide assumes that you do not have any experience with CASTOR JDO, but would like to make your first steps into the world of persistence and object/relation mapping tools. The following sections show how to setup and configure Castor JDO so that it is possible to perform persistence operations on the domain objects presented.

3.2.2. Sample domain objects

The sample domain objects used in here basically define a Catalogue, which is a collection of Products.

public class Catalogue {

   private long id;
   private List products = new ArrayList();
   
   public long getId() { ... }
   public void setId(long id) { ... }

   public String getProducts() { ... }
   public void setProducts(List products) { ... }
   
}

public class Product {

   private long id;
   
   private String description;

   public long getId() { ... }
   public void setId(long id) { ... }
   
   public String getDescription() { ... }
   public void setDescription(String description) { ... }
   
}

In order to be able to perform any persistence operation (such as loading products, deleting products from a catalogue, ...) on these domain objects through Castor JDO, a Castor JDO mapping has to be provided, defining class and field level mappings for the Java classes given and their members:

<class name="org.castor.sample.Catalogue">
   <map-to table="catalogue"/>
   <field name="id" type="long">
      <sql name="id" type="integer" />
   </field>
   <field name="products" type="org.castor.sample.Product" collection="arraylist">
      <sql many-key="c_id" />
   </field>
</class>

<class name="org.castor.sample.Product">
   <map-to table="product"/>
   <field name="id" type="long">
      <sql name="id" type="integer" />
   </field>
   <field name="description" type="string">
      <sql name="desc" type="varchar" />
   </field>
</class>

3.2.3. Using Castor JDO for the first time

To e.g. load a given Catalogue instance as defined by its identity, and all its associated Product instances, the following code could be used, based upon the Castor-specific interfaces JDOManager and Database.

JDOManager.loadConfiguration("jdo-conf.xml");
JDOManager jdoManager = JDOmanager.createInstance("sample");

Database database = jdoManager.getDatabase();       
database.begin();
Catalogue catalogue = database.load(catalogue.class, new Long(1));
database.commit();
database.close();

For brevity, exception handling has been ommitted completely. But is is quite obvious that - when using such code fragments again and again, to e.g. implement various methods of a DAO - there's a lot of redundant code that needed to be written again and again - and exception handling is adding some additional complexity here as well.

3.2.4. JDO configuration

As shown in above code example, before you can perform any persistence operations on your domain objects, Castor JDO has to be configured by the means of a JDO configuration file. as part of this JDO configuration, the user defines one or more databases and everything required to connect to this database (user credentials, JDBC connection string, ....).

A valid JDO configuration file for HSQL looks as follows:

<?xml version="1.0" ?>
<!DOCTYPE jdo-conf PUBLIC "-//EXOLAB/Castor JDO Configuration DTD Version 1.0//EN" "http://castor.org/jdo-conf.dtd">
<jdo-conf>
  <database name="hsqldb" engine="hsql">
    <driver class-name="org.hsqldb.jdbcDriver" url="jdbc:hsqldb:hsql://localhost:9002/dbname">
      <param name="user" value="sa"/>
      <param name="password" value=""/>
    </driver>
    <mapping href="mapping.xml"/>
  </database>
  <transaction-demarcation mode="local"/>
</jdo-conf>
        

3.3. Using Castor JDO

3.3.1. Opening A JDO Database

Castor JDO supports two type of environments, client applications and J2EE servers. Client applications are responsible for configuring the database connection and managing transactions explicitly. J2EE applications use JNDI to obtain a pre-configured database connection, and use UserTransaction or container managed transactions (CMT) to manage transactions. If you have been using JDBC in these two environments, you will be readily familiar with the two models and the differences between them.

3.3.1.1. Stand-alone application

Client applications are responsible for defining the JDO configuration, and managing the transaction explicitly. The database is by default configured through a separate XML file which links to the mapping file. Alternatively, it can be configured using the utility class org.exolab.castor.jdo.util.JDOConfFactory.

In the example code I refer to the JDO configuration file as jdo-conf.xml, but any name can be used. See Castor JDO Configuration for more information.

As of release 0.9.6, a new JDOManager class is provided, that replaces the former JDO class. Any new features will be implemented against the new JDOManager class only.

As with its predecessor, org.exolab.castor.jdo.JDOManager defines the database name and properties and is used to open a database connection. An instance of this class is constructed with a two-step approach:

  1. Statically load the JDO configuration file through one of the loadConfiguration() methods, e.g. org.exolab.castor.jdo.JDOManager.loadConfiguration(java.lang.String).

  2. Create an instance of the JDO engine using the factory method org.exolab.castor.jdo.JDOManager.createInstance(java.lang.String) where you supply one of the database names defined in the configuration file loaded in step 1).

The org.exolab.castor.jdo.Database object represents an open connection to the database. By definition the database object is not thread safe and should not be used from concurrent threads. There is little overhead involved in opening multiple Database objects, and a JDBC connection is acquired only per open transaction.

The following code fragment creates an instance of JDOManager for a database 'mybd', opens a database, performs a transaction, and closes the database - as it will typically appear in client applications (for brevity, we have ommitted any required exception handling):

 
JDOManager jdoManager;
Database db;
 
// load the JDO configuration file and construct a new JDOManager for the database 'mydb'
JDOManager.loadConfiguration("jdo-conf.xml");
jdoManager = JDOManager.createInstance("mydb");

// Obtain a new database
Database db = jdoManager.getDatabase();
    
// Begin a transaction
db.begin();

// Do something
. . .

// Commit the transaction and close the database
db.commit();
db.close();
        

For an example showing how to set up a database configuration on the fly without the need of a preconfigured XML configuration file) see JdoConfFactory.

3.3.1.2. J2EE Application

J2EE applications depend on the J2EE container (Servlet, EJB, etc) to configure the database connection and use JNDI to look it up. This model allows the application deployer to configure the database properties from a central place, and gives the J2EE container the ability to manage distributed transactions across multiple data sources.

Instead of constructing a org.exolab.castor.jdo.JDOManager the application uses the JNDI namespace to look it up. We recommend enlisting the JDOManager object under the java:comp/env/jdo namespace, compatible with the convention for listing JDBC resources.

The following code fragment uses JNDI to lookup a database, and uses the JTA UserTransaction interface to manage the transaction:

InitialContext  ctx;
UserTransaction ut;
Database        db;

// Lookup databse in JNDI
ctx = new InitialContext();
db = (Database) ctx.lookup( "java:comp/env/jdo/mydb" );

// Begin a transaction
ut = (UserTransaction) ctx.lookup( "java:comp/UserTransaction" );
ut.begin();
// Do something
. . .
// Commit the transaction, close database
ut.commit();
db.close();
        

If the transaction is managed by the container, a common case with EJB beans and in particular entity beans, there is no need to begin/commit the transaction explicitly. Instead the application server takes care of enlisting the database in the ongoing transaction and executes commit/rollback at the proper time.

The following code snippet relies on the container to manage the transaction

InitialContext  ctx;
UserTransaction ut;
Database        db;

// Lookup database in JNDI
ctx = new InitialContext();
db = (Database) ctx.lookup( "java:comp/env/jdo/mydb" );

// Do something
. . .
// Close the database
db.close();
        

3.3.2. Using A JDO Database to perform persistence operations

3.3.2.1. Transient And Persistent Objects

All JDO operations occur within the context of a transaction. JDO works by loading data from the database into an object in memory, allowing the application to modify the object, and then storing the object's new state when the transaction commits. All objects can be in one of two states: transient or persistent.

Transient: Any object whose state will not be saved to the database when the transaction commits. Changes to transient objects will not be reflected in the database.

Persistent: Any object whose state will be saved to the database when the transaction commits. Changes to persistent objects will be reflected in the database.

An object becomes persistent in one of two ways: it is the result of a query, (and the query is not performed in read-only mode) or it is added to the database using org.exolab.castor.jdo.Database.create(java.lang.Object) or org.exolab.castor.jdo.Database.update(java.lang.Object). All objects that are not persistent are transient. When the transaction commits or rolls back, all persistent objects become transient.

In a client application, use org.exolab.castor.jdo.Database.begin(), org.exolab.castor.jdo.Database.commit() and org.exolab.castor.jdo.Database.rollback() to manage transactions. In a J2EE application, JDO relies on the container to manage transactions either implicitly (based on the transaction attribute of a bean) or explicitly using the javax.transaction.UserTransaction interface.

If a persistent object was modified during the transaction, at commit time the modifications are stored back to the database. If the transaction rolls back, no modifications will be made to the database. Once the transaction completes, the object is once again transient. To use the same object in two different transactions, you must query it again.

An object is transient or persistent from the view point of the database to which the transaction belongs. An object is generally persistent in a single database, and calling org.exolab.castor.jdo.Database">isPersistent(java.lang.Object) from another database will return false. It is possible to make an object persistent in two database, e.g. by querying it in one, and creating it in the other.

3.3.2.2. Running an OQL Query

OQL queries are used to lookup and query objects from the database. OQL queries are similar to SQL queries, but use object names instead of SQL names and do not require join clauses. For example, if the object being loaded is of type TestObject, the OQL query will load FROM TestObject, whether the actual table name in the database is test, test_object, or any other name. If a join is required to load related objects, Castor will automatically perform the join.

The following code snippet uses an OQL query to load all the objects in a given group. Note that product and group are related objects, the JDBC query involves a join:

OQLQuery     oql;
QueryResults results;

// Explicitly begin transaction
db.begin();

// Construct a new query and bind its parameters
oql = db.getOQLQuery("SELECT p FROM Product p WHERE Group=$1");
oql.bind(groupId);

// Retrieve results and print each one
results = oql.execute();
while (results.hasMore()) {
  System.out.println(results.next());
}

// Explicitly close the QueryResults
results.close();

// Explicitly close the OQLQuery
oql.close();

// Explicitly commit transaction
db.commit();
db.close();
        

The following code snippet uses the previous query to obtain products, mark down their price by 25%, and store them back to the database (in this case using a client application transaction):

OQLQuery     oql;
QueryResults results;

// Explicitly begin transaction
db.begin();

// Construct a new query and bind its parameters
oql = db.getOQLQuery("SELECT p FROM Product p WHERE Group=$1");
oql.bind(groupId);

// Retrieve results and mark up each one by 25%
Product prod;
while (results.hasMore()) {
  prod = (Product) results.next();
  prod.markDown(0.25);
  prod.setOnSale(true);
}

// Explicitly close the QueryResults
results.close();

// Explicitly close the OQLQuery
oql.close();

// Explicitly commit transaction
db.commit();
db.close();
        

As illustrated above, a query is executed in three steps. First a query object is created from the database using an OQL statement. If there are any parameters, the second step involves binding these parameters. Numbered parameters are bound using the order specified in their names. (e.g. first $1, after that $2, and so on...) The third step involves executing the query and obtaining a result set of type org.exolab.castor.jdo.QueryResults.

A query can be created once and executed multiple times. Each time it is executed the bound parameters are lost, and must be supplied a second time. The result of a query can be used while the query is being executed a second time.

There is also a special form of query that gives a possibility to call stored procedures:

oql = db.getOQLQuery("CALL sp_something($) AS myapp.Product");

Here sp_something is a stored procedure returning one or more ResultSets with the same sequence of fields as Castor-generated SELECT for the OQL query "SELECT p FROM myapp.Product p" (for objects without relations the sequence is: identity, then all other fields in the same order as in mapping.xml).

3.3.2.3. Creating a persistent object

The method org.exolab.castor.jdo.Database.create(java.lang.Object) creates a new object in the database, or in JDO terminology makes a transient object persistent. An object created with the create method will remain in the database if the transaction commits; if the transaction rolls back the object will be removed from the database. An exception is thrown if an object with the same identity already exists in the database.

The following code snippet creates a new product with a group that was previously queried:

Database db = ...;
db.begin();        

//load product group
ProductGroup furnitures = db.load(...);

// Create the Product object
Product prod;
prod = new Product();
prod.setSku(5678);
prod.setName("Plastic Chair");
prod.setPrice(55.0 );
prod.setGroup(furnitures);

// Make it persistent
db.create(prod);

db.commit();
        

3.3.2.4. Removing a persistent object

The method org.exolab.castor.jdo.Database.remove(java.lang.Object) has the reverse effect, deleting a persistent object. Once removed the object is no longer visible to any transaction. If the transaction commits, the object will be removed from the database, however, if the transaction rolls back the object will remain in the database. An exception is thrown when attempting to remove an object that is not persistent.

The following code snippet deletes the previously created Product instance:

Database db = ...;
db.begin();        

// load the Product instance with sku = 5678
Product prod = db.load (Product.class, new Integer(5678);

// delete the Product instance
db.remove(prod);

db.commit();
        

3.3.2.5. Updating a persistent object

There's no special method offering on the org.exolab.castor.jdo.Database to update an existing persistent object. Simply load the object to be updated, change one or more of its properties, and commit the transaction. Castor JDO will automatically figure that that the object in question has changed and will persist these changes to the underlying database.

The following code snippet loads a previously created Product instance, changes its description property and commits the transaction.

Database db = ...;
db.begin();        

// load the Product instance with sku = 5678
Product prod = db.load (Product.class, new Integer(5678);

// change the object properties
prod.setDescription("New plastic chair");

//commit the transaction
db.commit();
        

3.3.3. Using JDO And XML

Castor JDO and Castor XML can be combined to perform transactional database operations that use XML as the form of input and output. The following code snippet uses a combination of persistent and transient objects to describe a financial operation.

This example retrieves two account objects and moves an amount from one account to the other. The transfer is described using a transient object (i.e. no record in the database), which is then used to generate an XML document describing the transfer. An extra step (not shown here), uses XSLT to transform the XML document into an HTML page.

Transfer tran;
Account  from;
Account  to;
OQLQuery oql;

tran = new Transfer();

// Construct a query and load the two accounts
oql = db.getOQLQuery("SELECT a FROM Account a WHERE Id=$");
oql.bind(fromId);
from = oql.execute().nextElement();
oql.bind(toId);
to = oql.execute().nextElement();

// Move money from one account to the other
if (from.getBalance() >= amount) {
  from.decBalance(amount);
  to.incBalance(amount);
  trans.setStatus(Transfer.COMPLETE);
  trans.setAccount(from);
  trans.setAmount(amount);
} else {
  // Report an overdraft
  trans.setStatus( Transfer.OVERDRAFT );
}

// Produce an XML describing the transfer
Marshaller.marshal(trans, outputStream);
      

The XML produced by the above code might look like:

<?xml version="1.0"?>
<report>
  <status>Completed</status>
  <account id="1234-5678-90" balance="50"/>
  <transfer amount="49.99"/>
</report>

3.4. Castor JDO - Configuration

Castor JDO allows for two simple ways of specifying its required configuration, e.g. by the means of supplying Castor JDO with an XML-based configuration file, and by specifying its configuration programmatically via the org.exoalb.castor.util.jdo.JDOConfFactory class.

3.4.1. The Castor configuration file

The default way to configure how Castor interacts with a specific database system is by using a configuration file. It specifies the means to obtain a connection to the database server, the mapping between Java classes and tables in that database server, and the service provider to use for talking to that server (For a more flexible, programmatic way without configuration files see section JDOConfFactory).

The application will access the database(s) by its given name (database/name) and will be able to persist all objects specified in the included mapping file(s).

The engine attribute specifies the persistence engine for this database server. Different database servers vary in the SQL syntax and capabilites they support, and this attribute names the service provider to use.

The following names are supported in Castor:

Table 3.1. Supported engine names

engine nameRDBMS
db2DB/2
derbyDerby
genericGeneric JDBC support
hsqlHypersonic SQL
informixInformix
instantdbInstantDB
interbaseInterbase
mysqlMySQL
oracleOracle 7 - Oracle 9i
postgresqlPostgreSQL 7.1
sapdbSAP DB / MaxDB
sql-serverMicrosoft SQL Server
sybaseSybase 11
pointbaseBorland Pointbase
progressProgress RDBMS

[Note]Note

Castor doesn't work with JDBC-ODBC bridge from Sun. In particular, MS Access is not supported.

The means to acquire a database connection is specified in one of three ways: as a JDBC driver URL, as a JDBC DataSource, or as a DataSource to lookup through JNDI. When Castor is used inside a J2EE application server it is recommended to use JNDI lookup (see the jndi element), allowing the application server to manage connection pooling and distributed transactions.

The class mapping is included from an external mapping file, allowing multiple mappings to be included in the same database configuration, or two databases to share the same mappings. For concurrency and integrity reasons, two database configurations should never attempt to use overlapping mappings. It is recommended to use one database configuration per database server.

The mapping file is specified using a URL, typically a file: URL. If the database configuration file and mapping file reside in the same directory, use a relative URL. Relative URLs also work if the database configuration and mapping files are obtained from the application JAR and reside in the same classpath.

The driver element specifies the JDBC driver for obtaining new connections to the database server. The driver is obtained from the JDBC DriverManager and must be located in the class path. The JDBC URL locates the driver and provides the access properties. Additional properties may be specified using param elements (e.g. buffer size, network protocol, etc).

Use the class-name attribute to specify the driver class for automatic registration with the JDBC DriverManager. If missing, the driver must be registered in any other means, including properties file, Class.forName(), etc.

For example, to configure an Oracle 8 thin driver, use:

  <jdo-conf>         
     <database name="ebiz" engine="oracle">
        <driver class-name="oracle.jdbc.driver.OracleDriver"
                url="jdbc:oracle:thin:@host:port:SID">
           <param name="user" value="scott" />
           <param name="password" value="tiger" />
        </driver>
        ...
     </database>
     ...
  </jdo-conf>

The data-source element specifies the JDBC DataSource for obtaining new connections to the database server. DataSources are defined in the JDBC 2.0 standard extension API which is included with Castor, and implement the interface javax.sql.DataSource.

The DataSource implementation class name is specified by the class-name attribute and configured through Bean-like accessor methods specified for the param element. The DTD for the param element is undefined and depends on the DataSource being used.

For example, to configure a PostgreSQL 7.1 DataSource, use:

  <jdo-conf>
     <database name="ebiz" engine="oracle">
        <data-source class-name="org.postgresql.PostgresqlDataSource">
           <param name="serverName" value="host" />
           <param name="portNumber" value="5432" />
           <param name="databaseName" value="db" />
           <param name="user" value="user" />
           <param name="password=" value="secret" />
        </data-source>
        ...
     </database>
     ...
  </jdo-conf>

The jndi element specifies the JDBC DataSource for obtaining new connections to the database server through a JNDI lookup. The JNDI environment naming context (ENC) is used to obtain a suitable DataSource..

When running inside a J2EE application server, this is the preferred method for obtaining database connections. It enables the J2EE application server to configure the connection, maintain a connection pool, and manage distributed transactions.

For example, to specify a J2EE DataSource, use:

  <jdo-conf>
     <database name="ebiz" engine="oracle">
        <jndi name="java:comp/env/jdbc/mydb" />
     </database>
     ...
  </jdo-conf>

3.4.1.1. Transaction demarcation

As opposed to release pre 0.9.6, transaction demarcation is now configured in the JDO configuration file. As such, the user has to specify which transaction demarcation to use. Transactions when used with Castor JDO can either be local or global, and you instruct Castor to use a specific mode by supplying a <transaction-demarcation> element.

3.4.1.1.1. Local Mode

When using Castor JDO stand-alone and you want Castor to control transaction demarcation, please use the <transaction-demarcation> element as follows:

<transaction-demarcation mode="local" />
           
3.4.1.1.2. Global Mode

When running inside a J2EE application server, and you want to use global (XA) transactions, please make use the <transaction-demarcation> element as follows:

<transaction-demarcation mode="global">
   <transaction-manager name="jndi" />
</transaction-demarcation>
           

In this mode, the <transaction-manager> element specifies the transaction manager that is used by your J2EE container to control these transactions.

The following transaction managers are supported in Castor:

Table 3.2. Supported transaction managers

NameDescription
jndiTM looked up in the JNDI ENC
websphereIBM WebSphere 4 and previous releases
websphere5IBM WebSphere 5
websphere51IBM WebSphere 5.1
jotmJOTM
atomikosAtomikos

In addition to specifying the transaction manager name, it is possible to pass arbitrary name/value pairs to the transaction manager instance.

[Note]Note

At the moment, only the JNDI transaction manager factory supports such an attribute. In this context, the jndiEnc attribute can be used to specify what JNDI ENC to use to lookup the transaction manager as shown below:

  <transaction-demarcation mode="global">
     <transaction-manager name="jndi">
        <param name="jndiEnc" value="java:comp/env/TransactionManager"/>
     </transaction-manager>
  </transaction-demarcation>

3.4.1.2. Sample Configuration File

The following configuration file instructs Castor JDO to execute against an Oracle RDBMS using the thin (type 4) JDBC driver, and refers to three mapping files that define mappings for product, order and customer related data.

  <?xml version="1.0"?>
  <jdo-conf name="order-system">
     <database name="ebiz" engine="oracle">
        <driver class-name="oracle.jdbc.driver.OracleDriver"
                url="jdbc:oracle:thin:@machine:post:SID">
           <param name="user" value="scott"/>
           <param name="password" value="tiger"/>
        </driver>
        <mapping href="products.xml"/>
        <mapping href="orders.xml"/>
        <mapping href="customers.xml"/>
     </database>
     <transaction-demarcation mode="local"/>
  </jdo-conf>

The following configuration file uses a connection obtained from the J2EE application server and a single mapping file:

  <?xml version="1.0"?>
  <jdo-conf>
     <database name="ebiz" engine="oracle">
        <jndi name="java:comp/env/jdbc/mydb"/>
        <mapping href="ebiz.xml"/>
     </database>
     <transaction-demarcation mode="global">
        <transaction-manager name="jndi">
           <param name="jndiEnc" value="java:comp/env/TransactionManager"/>
        </transaction-manager>
     </transaction-demarcation>
  </jdo-conf>

3.4.1.3. Prepared statement pooling

Castor JDO uses JDBC prepared statements to execute SQL statements against the specified RDBMS of your choice. Per definition, Castor JDO does not provide any prepared statement pooling. As such, Castor relies on prepared statement pooling being provided by different means.

One such way is to use Jakarta's Commons DBCP as database connection pool, and to turn prepared statement pooling on by configuring DBCP accordingly.

Please check with Using Pooled Database Connections for general information about hot to use DBCP with Castor.

3.4.1.4. Sample configurations for various databases

Besides the examples listed above, more configuraton examples can be found in the configuration files for the Castor JDO tests, which can be found in src/tests/jdo once you have downloaded and expanded the Castor source package. For each database (vendor) supported, you are going to find a database-specific JDO configuration file in this directory, e.g. src/tests/jdo/mysql.xml for mySQL™ or src/tests/jdo/oracle.xml for Oracle™.

3.4.1.4.1. Sybase JConnect (JDBC data source)
  ...
  <!-- JDBC data source for Sybase using jConnect -->
  <data-source class-name="com.sybase.jdbc2.jdbc.SybDataSource">
    <param name="user" value="user" />
    <param name="password value="secret" />
    <param name="portNumber" value="4100" />
    <param name="serverName" value="host" />
  </data-source>
  ...
3.4.1.4.2. PostgreSQL (JDBC data source)
  ...
  <!-- JDBC data source for PostgreSQL -->
  <data-source class-name="org.postgresql.PostgresqlDataSource">
     <param name="serverName" value="host" />
     <param name="portNumber" value="5432" />
     <param name="databaseName" value="db" />
     <param name="user" value="user" />
     <param name="password" value="secret" />
   </data-source>
   ...
          
3.4.1.4.3. Oracle (JDBC Driver)
  ...
  <!-- JDBC driver definition for Oracle -->
  <driver class-name="oracle.jdbc.driver.OracleDriver"
          url="jdbc:oracle:thin:@host:post:SID">
    <param name="user" value="scott" />
    <param name="password" value="tiger" />
  </driver>
  ...
          
3.4.1.4.4. mySQL (JDBC Driver)
  ...
  <!-- JDBC data source for mySQL -->
  <driver class-name="com.mysql.jdbc.Driver"
          url="jdbc:mysql:/localhost:2206/test">
    <param name="user" value="scott" />
    <param name="password" value="tiger" />
  </driver>
  ...
          
3.4.1.4.5. InstantDB
  ...
  <!-- JDBC data source for InstantDB -->
  <driver class-name="org.enhydra.instantdb.jdbc.idbDriver"
          url="jdbc:idb:C:\\castor-1.0\\db\\test\\test.prp">
    <param name="user" value="" />
    <param name="password" value="" />
  </driver>
  ...
          

3.4.2. JDOConfFactory - A programmatic way of configuring Castor JDO

Many applications need to connect to a database using varying user accounts or database instances. To accomplish this, the utility class org.exolab.castor.jdo.util.JDOConfFactory">JDOConfFactory and a JDOManager.loadConfiguration(org.exolab.castor.jdo.conf.JdoConf) method has been added to Castor.

The following code snippet shows an example how to create a JDO configuration without the use of a default XML-based database configuration file:

  private static final String DRIVER = "oracle.jdbc.driver.OracleDriver";
  private static final String CONNECT = "jdbc:oracle:thin:localhost:1521:SID";
  private static final String USERNAME = "scott";
  private static final String PASSWORD = "tiger";
  private static final String MAPPING = "mapping.xml";
  private static final String DATABASE = "mydb";
  private static final String ENGINE = "oracle";
  
  // create driver configuration
  org.castor.jdo.conf.Driver driverConf =
      JDOConfFactory.createDriver(DRIVER, CONNECT, USERNAME, PASSWORD);
      
  // create mapping configuration
  org.castor.jdo.conf.Mapping mappingConf =
      JDOConfFactory.createMapping(getClass().getResource(MAPPING).toString());

  // create database configuration
  org.castor.jdo.conf.Database dbConf =
      JDOConfFactory.createDatabase(DATABASE, ENGINE, driverConf, mappingConf);
     
  // create and load jdo configuration
  JDOManager.loadConfiguration(JDOConfFactory.createJdoConf(dbConf));
  
  // Construct a new JDOManager for the database
  jdoManager = JDOManager.createInstance(DATABASE);
  
  // Obtain a new database
  Database db = jdoManager.getDatabase();
       

As an alternative to using a org.exolab.castor.jdo.conf.Driver, you can also configure Castor to use a JDBC 2.0 DataSource:

  private static final String DS = "oracle.jdbc.pool.OracleConnectionCacheImpl";
  private static final String CONNECT = "jdbc:oracle:thin:localhost:1521:SID";
  private static final String USERNAME = "scott";
  private static final String PASSWORD = "tiger";
  private static final String MAPPING = "mapping.xml";
  private static final String DATABASE = "mydb";
  private static final String ENGINE = "oracle";
  
  // setup properties for datasource configuration
  Properties props = new Properties();
  props.put("URL", CONNECT);
  props.put("user", USERNAME);
  props.put("password", PASSWORD);
            
  // create datasource configuration
  org.castor.jdo.conf.DataSource dsConf =
      JDOConfFactory.createDataSource(DS, props);
              
  // create mapping configuration
  org.castor.jdo.conf.Mapping mappingConf =
      JDOConfFactory.createMapping(getClass().getResource(MAPPING).toString());
        
  // create database configuration
  org.castor.jdo.conf.Database dbConf =
      JDOConfFactory.createDatabase(DATABASE, ENGINE, dsConf, mappingConf);
  
  // create and load jdo configuration
  JDOManager.loadConfiguration(JDOConfFactory.createJdoConf(dbConf));
  
  // Construct a new JDOManager for the database
  jdoManager = JDOManager.createInstance(DATABASE);
  
  // Obtain a new database
  Database db = jdoManager.getDatabase();
       

3.4.3. References

3.4.3.1. The JDO Configuration DTD

For validation, the configuration file should include the following document type definition. For DTD validation use:

  <!DOCTYPE jdo-conf PUBLIC "-//EXOLAB/Castor JDO Configuration DTD Version 1.0//EN"
                            "http://castor.org/jdo-conf.dtd">

             

For XML Schema validation use:

  <!DOCTYPE jdo-conf PUBLIC "-//EXOLAB/Castor JDO Configuration Schema Version 1.0//EN"
                             "http://castor.org/jdo-conf.xsd">

             

The Castor namespace URI is http://castor.org/.

The Castor JDO database configuration DTD is:

  <!ELEMENT jdo-conf ( database+, transaction-demarcation )>

  <!ATTLIST jdo-conf
            name CDATA "jdo-conf">

  <!ELEMENT database ( ( driver | data-source | jndi )?, mapping+ )>
                       
  <!ATTLIST database
            name ID      #REQUIRED
            engine CDATA "generic">

  <!ELEMENT mapping EMPTY>
  <!ATTLIST mapping
            href CDATA #REQUIRED>

  <!ELEMENT driver ( param* )>
  <!ATTLIST driver
            url        CDATA #REQUIRED
            class-name CDATA #REQUIRED>

  <!ELEMENT data-source ( param* )>
  <!ATTLIST data-source
            class-name CDATA #REQUIRED>

  <!ELEMENT jndi ANY>
  <!ATTLIST jndi
            name CDATA #REQUIRED>

  <!ELEMENT transaction-demarcation ( transaction-manager? )>
  <!ATTLIST transaction-demarcation
            mode CDATA #REQUIRED>

  <!ELEMENT transaction-manager ( param* )>
  <!ATTLIST transaction-manager
            name CDATA #REQUIRED>
            
  <!ELEMENT param EMPTY>
  <!ATTLIST param
            name  CDATA #REQUIRED
            value CDATA #REQUIRED>


             

3.5. Type Support

3.5.1. Types

The Castor type mechanism assures proper conversion between Java types and external types.

3.5.1.1. Castor XML

Castor XML converts all Java fields into XML element and attribute values.

3.5.1.2. Castor JDO

Castor JDO converts Java fields into SQL columns which are persisted through the JDBC driver. Due to implementation details, the field type expected by the JDBC driver is not always the field type defined for the mapped object.

The most common occurrences of mistyping is when using fields of type FLOAT, DOUBLE, NUMERIC, and DECIMAL. SQL type FLOAT actually maps to Java type java.lang.Double. SQL types NUMERIC and DECIMAL map to Java type java.math.BigDecimal.

When such an inconsistency occurs, Castor JDO will throw an IllegalArgumentException during object persistence with a message indicating the two conflicting types.

In order to avoid runtime exceptions, we recommend explicitly specifying types in the mapping file using the SQL typing convention. See SQL Type Conversion.

3.5.1.3. Castor DAX

Castor DAX converts all Java fields into LDAP attribute values. LDAP attribute values are always textual and are represented as the string value of the field, e.g. "5" or "true".

LDAP attributes may also contain binary values. When storing byte arrays or serialized Java objects, DAX will store them as byte arrays.

3.5.2. The Field Mapping

The field element includes an optional attribute called type which can be used to specify the Java type of the field. This attribute is optional since Castor can always derive the exact Java type from the class definition.

We highly recommend that developers use the type field in their mapping file as a means to provide static type checking. When loading a mapping file, Castor will compare the actual Java type with the type specified in the mapping and will complain about inconsistencies.

The field type can be specified either given the full class name (e.g. java.lang.Integer) or using a short name. The following table lists all the acceptable short names and the Java types they represent:

Table 3.3. Acceptable short names

short namePrimitive type?Java Class
big-decimalNjava.math.BigDecimal
booleanYjava.lang.Boolean.TYPE
byteYjava.lang.Byte.TYPE
bytesNbyte[]
charYjava.lang.Character.TYPE
charsNchar[]
clobNjava.sql.Clob
dateNjava.util.Date
doubleYjava.lang.Double.TYPE
floatYjava.lang.Float.TYPE
integerYjava.lang.Integer.TYPE
localeNjava.util.Locale
longYjava.lang.Long.TYPE
otherNjava.lang.Object
shortYjava.lang.Short.TYPE
stringNjava.lang.String
stringsNString[]
streamNjava.io.InputStream

In addition, support for the following Castor-internal field types has been added:

Table 3.4. Castor-internal field types

short namePrimitive type?Java Class
durationNorg.exolab.castor.types.Duration

3.5.3. SQL Dates and Default Timezones

Castor will use the JDBC ResultSet.getDate(int, Calendar) and related methods which take a Calendar object to specify the timezone of the data retrieved from the database when the timezone information is not already specified in the data; this ensures that the "current" timezone is applied.

The default time zone can be configured in the castor.properties file; see the configuration section for details on how to configure Castor with information about your default time zone.

To change the timezone to a different timezone than the default, please set a (different) value on the org.exolab.castor.jdo.defaultTimeZone property:

# Default time zone to apply to dates/times fetched from database fields,
# if not already part of the data.  Specify same format as in
# java.util.TimeZone.getTimeZone, or an empty string to use the computer's
# local time zone.
org.exolab.castor.jdo.defaultTimeZone=
#org.exolab.castor.jdo.defaultTimeZone=GMT+8:00
      

3.5.4. SQL Type Conversion

Castor JDO uses the JDBC getObject/setObject methods in order to retrieve and set fields. These methods do not perform automatic type conversion, often resulting in unexpected behavior. For example, when using a NUMERIC field with direct JDBC access, application developers tend to call getInteger() or getFloat(), but the Java object returned from a call to getObject is often a java.math.BigDecimal.

Castor JDO implements automatic type conversion between Java and SQL. For this mechanism to work, the mapping file must specify the SQL type being used for Castor to employ the proper convertor. If no SQL type is specified, no conversion will occur, possibly resulting in an IllegalArgumentException being thrown.

SQL types are specified with the sql-type attribute using either the default Java type returned by the JDBC driver (e.g. java.lang.Integer or the proper SQL type name (without precision). The following table lists the supported SQL type names and the corresponding Java types:

Table 3.5. Supported SQL type names

SQL TypeJava Type
bigintjava.lang.Long
binarybyte[]
bitjava.lang.Boolean
blobjava.io.InputStream
charjava.lang.String
clobjava.sql.Clob
decimaljava.math.BigDecimal
doublejava.lang.Double
floatjava.lang.Double
integerjava.lang.Integer
longvarbinarybyte[]
longvarcharjava.lang.String
numericjava.math.BigDecimal
realjava.lang.Float
smallintjava.lang.Short
timejava.sql.Time
timestampjava.sql.Timestamp
tinyintjava.lang.Byte
varbinarybyte[]
varcharjava.lang.String
otherjava.lang.Object
javaobjectjava.lang.Object

The following example illustrates how to specify SQL type in field mapping:

  <field name="prodId" type="integer">
    <sql name="prod_id" type="numeric"/>
  </field>

Please note that java.util.Date is not automatically converted into a java.sql.Date object; while it is in theory possible to do so, there are three different possible storage formats for date information: as a java.sql.Date, java.sql.Time, and java.sql.Timestamp. Rather than impose a possibly inappropriate data mapping on an entry, no automatic transformation will take place.

JDBC drivers which do not, themselves, perform a mapping between java.util.Date and the sql format specified on the database will throw an error when java.util.Date is passed to them on the prepared statement. Moreover, auto-conversion of java.util.Date is outside of the JDBC specification; it is not a supported auto-convert format.

Users wishing to store date information into the database should ensure that they set date, time, or timestamp as the sql type on the sql-type attribute.

3.5.5. Parameterized Type Convertors

Some of the type convertors may have a string parameter, which changes the conversion algorithm. The parameter is specified in square brackets after the SQL type, for example:

  <field name="active" type="boolean">
    <sql name="acc_active" type="char[01]"/>
  </field>

where "0" is the character value for false and "1" is the character value for true.

In the above example the first of a bunch of parameterized type convertors is used, "boolean --> char" convertor. The parameter must have length 2, the first character is the value for false, the second character is the value for true. The default value is "FT". The actual SQL type should be char(1).

The second and third convertors are "boolean --> integer" and "boolean --> numeric". Its parameter must be + for +1 or - for -1 representing true. False is always converted to 0. For example:

  <field name="flagWithMinusOneForTrue" type="boolean">
    <sql name="flag" type="integer[-]"/>
  </field>

If the parameter is not specified, true is converted to +1.

The fourth convertor is "date --> char". Its parameter must be a correct pattern for SimpleDateFormat. For example:

  <field name="dateOfBirth" type="date">
    <sql name="pers_dob" type="char[MMM d, yyyy]"/>
  </field>

If the parameter is not specified, the conversion is performed using toString() method of the Date class.

The fifth and the sixth convertors are "date --> integer" and "date --> numeric". Their parameters are also patterns having syntax based on the SimpleDateFormat syntax, but repeated characters are eliminated. The following table shows the substitution rules that are used to obtain the SimpleDateFormat pattern from the parameter.

Table 3.6. Substitution rules

Y,yyyyyyear
MMMmonth in year
D,dddday in month
h,HHHhour in day (0~23)
mmmminute in hour
ssssecond in minute
SSSSmillisecond

For example, "YMD" parameter is expanded to "yyyyMMdd" SimpleDateFormat pattern, "YMDhmsS" parameter is expanded to "yyyyMMddHHmmssSSS" SimpleDateFormat pattern. The length of the expanded parameter gives the minimal number of decimal digits that the actual SQL type must support. The default value of the parameter is "YMD".

The date and time types of org.exolab.castor.types package support 2 timelines as defined by XML schema specification. One for timezoned values and one for non-timezoned values which are treated to be local. When converting such types to long the timezone information is lost. In most cases it is no problem to loose for which timezone the value was specified if the value get converted to UTC time before. But we also loose if the value had a timezone or not. This causes that we do not know to which timeline the value belongs. If we just treat it as non-timezoned value while it has been a timezoned one we have changed the value.

Therefore we have added support for another parameterized type converter. This one allows you to specify if the date and time values created out of a persisted long value are meant to be timezoned or not. By default, without a parameter, local date or time instances are created without a timezone. If you specify the parameter utc in mapping the date or time values are created based on UTC timezone. It need to be noted that such a mapping can only handle date and time values of one of the 2 timelines defined by XML schema specification. Having said that this only applies to the conversion of such values to long and does not cause issues when converting to string and back.

  <field name="timeOfBirth" type="org.exolab.castor.types.Time">
    <sql name="pers_tob" type="bigint[utc]"/>
  </field>

3.5.6. BLOB and CLOB Types

BLOB and CLOB stand for binary and character large objects (or in Sybase, IMAGE and TEXT types, respectively). This means that most likely you don't want to load the whole objects into memory, but instead want to read and write them as streams. Usually these types are not comparable via the WHERE clause of a SQL statement. That is why you should disable dirty checking for such fields, e.g.

  <field name="text" type="string">
    <sql name="text" type="clob" dirty="ignore" />
  </field>

In this example CLOB field will be read as a String. This may cause OutOfMemoryError if the text is really large, but in many cases mapping CLOB to String is acceptable. The advantage of mapping to String is that we obtain a Serializable value that can be passed via RMI. Similarly you can map BLOB and CLOB to byte[] and char[] types, respectively:

  <field name="photo" type="bytes">
    <sql name="photo" type="blob" dirty="ignore" />
  </field>
  <field name="resume" type="chars">
    <sql name="resume" type="clob" dirty="ignore" />
  </field>

Now, assume that mapping to String is not acceptable. The natural Java type mapping for the BLOB type is java.io.InputStream, and this mapping is supported by Castor:

  <field name="cdImage" type="stream">
    <sql name="cd_image" type="blob" dirty="ignore" />
  </field>

The natural Java type mapping for the CLOB type is java.io.Reader, but this mapping is not supported by Castor because java.io.Reader doesn't provide information about the length of the stream and this information is necessary for JDBC driver (at least for the Oracle driver) to write the value to the database. This is why the CLOB type is mapped to java.sql.Clob:

  <field name="novel" type="clob">
    <sql name="novel" type="clob" dirty="ignore" />
  </field>

When you read data from the database, you can use the getCharacterStream() method to obtain a java.io.Reader from java.sql.Clob. When you write data to the database, you can either use the helper class org.exolab.castor.jdo.engine.ClobImpl to construct java.sql.Clob from java.io.Reader and the length:

  object.setClob(new ClobImpl(new FileReader(file), file.length());

or implement the java.sql.Clob interface yourself.

But be aware of the followng restriction: if you map BLOB to java.io.InputStream or CLOB to java.sql.Clob, then you should turn caching off for the Java class containing those values, e.g.:

  <class ...>
    <cache-type type="none"/>
      ...
      <field name="novel" type="clob">
        <sql name="novel" type="clob" dirty="ignore" />
      </field>
  </class>

Blob and Clob values cannot be cached, because they are alive only while the ResultSet that produced them is open. In particular, this means that you cannot use dirty checking for long transactions with such classes.

3.6. Castor JDO Mapping

Authors

Bruce Snyder () , Werner Guttmann ()

3.6.1. News

Release 1.0 M3:

  • Added collection type 'iterator'.

  • Added collection type 'enumerate'.

  • Added additional syntax for specifying the identity of a class.

3.6.2. Introduction

The Castor mapping file also provides a mechanism for binding a Java object model to a relational database model. This is usually referred to as object-to-relational mapping (O/R mapping). O/R mapping bridges the gap between an object model and a relational model.

The mapping file provides a description of the Java object model to Castor JDO. Via Castor XML, Castor JDO uses the information in the mapping file to map Java objects to relational database tables. The following is a high-level example of a mapping file:

  <mapping>
      <class ... >
          <map-to ... />
          <field ... >
              <sql ... />
          </field>
          ...
      </class>
  </mapping>

Each Java object is represented by a <class> element composed of attributes, a <map-to> element and <field> elements. The <map-to> element contains a reference to the relational table to which the Java object maps. Each <field> element represents either a public class variable or the variable's accessor/mutator methods (depending upon the mapping info). Each <field> element is composed of attributes and one <sql> element. The <sql> element represents the field in the relational table to which the <field> element maps.

It is possible to use the mapping file and Castor's default behavior in conjunction. When Castor handles an object but is unable to locate information about it in the mapping file, it will rely upon its default behavior. Castor makes use of the Java programming language Reflection API to introspect the Java objects to determine the methods to use. This is the reason some attributes are not required in the mapping file.

3.6.3. The Mapping File

3.6.3.1. The <mapping> element

  <!ELEMENT mapping ( description?, include*, class*, key-generator* )>

The <mapping> element is the root element of a mapping file. It contains:

  • an optional description

  • zero or more <include> which facilitates reusing mapping files

  • zero or more <class> descriptions: one for each class we intend to give mapping information

  • zero or more <key-generator>: not used for XML mapping

A mapping file look like this:

  <?xml version="1.0"?>
  <!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Object Mapping DTD Version 1.0//EN"
                           "http://castor.org/mapping.dtd">

  <mapping>
     <description>Description of the mapping</description>
     <include href="other_mapping_file.xml"/>

     <class name="A">
         ...
     </class>

     <class name="B">
        ...
     </class>
  </mapping>

3.6.3.2. The <class> element

  <!ELEMENT class ( description?, cache-type?, map-to?, field+ )>
  <!ATTLIST class
            name            ID       #REQUIRED
            extends         IDREF    #IMPLIED
            depends         IDREF    #IMPLIED
            auto-complete   ( true |false ) "false"
            identity        CDATA    #IMPLIED
            access          ( read-only | shared | exclusive | db-locked )  "shared"
            key-generator   IDREF    #IMPLIED >

The <class> element contains all the information used to map a Java object to a relational database. The content of <class> is mainly used to describe the fields that will be mapped.

Description of the attributes:

  • name: The fully qualified package name of the Java object to map to.

  • extends: Should be used only if this Java object extends another Java object for which mapping information is provided. It should not be used if the extended Java object is not referenced in the mapping file.

  • depends: For more information on this field, please see Dependent and related relationships.

  • identity: For more information on this field, please see Design -> Persistence.

  • access: For more information on this field, please see Locking Modes.

  • key-generator: For more information on this field, please see KeyGen.

Description of the elements:

  • <description>: An optional description.

  • <cache-type>: For more information on this field please see Bounded Dirty Checking and Caching.

  • <map-to>: Used to tell Castor the name of the relational table to which to map.

  • <field>: Zero or more <field> elements are used to describe properties of each Java object.

3.6.3.3. The <map-to> element

  <!ELEMENT map-to EMPTY>
  <!ATTLIST map-to
            table      NMTOKEN  #IMPLIED
            xml        NMTOKEN  #IMPLIED
            ns-uri     NMTOKEN  #IMPLIED
            ns-prefix  NMTOKEN  #IMPLIED
            ldap-dn    NMTOKEN  #IMPLIED
            ldap-oc    NMTOKEN  #IMPLIED>

<map-to> is used to specify the name of the item that should be associated with the given Java object. The <map-to> element is only used for the root Java object.

Description of the attributes:

  • table: The name of the relational database table to which the Java object is associated.

3.6.3.4. The <field> element

  <!ELEMENT field ( description?, sql?, xml?, ldap? )>
  <!ATTLIST field
            name           NMTOKEN  #REQUIRED
            type           NMTOKEN  #IMPLIED
            required       ( true | false )  "false"
            direct         ( true | false )  "false"
            lazy           ( true | false )  "false"
            transient      ( true | false )  "false"
            identity       ( true | false )  "false"
            get-method     NMTOKEN  #IMPLIED
            set-method     NMTOKEN  #IMPLIED
            create-method  NMTOKEN  #IMPLIED
            collection     ( array | enumerate | collection | set | 
                             arraylist | vector | map | hashtable | sortedset | iterator )  #IMPLIED
            comparator     NMTOKEN  #IMPLIED>

The <field> element is used to describe a property of a Java object. It provides:

  • the identity ('name') of the property

  • the type of the property (inferred from 'type' and 'collection')

  • the access method of the property (inferred from 'direct', 'get-method', 'set-method')

From this information, Castor is able to access a given property in the Java object.

In order to determine the signature that Castor expects, there are two easy rules to apply.

1. Determine <type>.

  • If there is no 'collection' attribute, the object type is the value of the 'type' attribute. The value of the type attribute can be a fully qualified Java object like 'java.lang.String' or one of the allowed aliases:

    short name Primitive type? Java Class
    big-decimal Njava.math.BigDecimal
    big-integer Yjava.math.BigInteger
    boolean Yjava.lang.Boolean.TYPE
    byte Yjava.lang.Byte.TYPE
    bytes Nbyte[]
    char Yjava.lang.Character.TYPE
    chars Nchar[]
    clob Njava.sql.Clob
    date Njava.util.Date
    double Yjava.lang.Double.TYPE
    float Yjava.lang.Float.TYPE
    int Yjava.lang.Integer.TYPE
    integer Yjava.lang.Integer
    locale Njava.util.Locale
    long Yjava.lang.Long.TYPE
    other Njava.lang.Object
    serializableYjava.io.Serializable
    short Yjava.lang.Short.TYPE
    sqldate Yjava.sql.Date
    sqltime Yjava.sql.Date
    string Njava.lang.String
    strings NString[]
    stream Njava.io.InputStream
    timestamp Njava.sql.Timestamp

    Castor will try to cast the data in the mapping file to the proper Java type.

  • If there is a collection attribute, the items in the following table can be used:

    name type default implementation added in release
    array <type_attribute>[] <type_attribute>[]
    enumerate java.util.Enumeration - 1.0 M3
    collection java.util.Collection java.util.ArrayList
    set java.util.Set java.util.HashSet
    arraylist java.util.ArrayList java.util.ArrayList
    vector java.util.Vector java.util.Vector
    map java.util.Map java.util.HashMap
    hashtable java.util.Hashtable java.util.Hashtable
    sortedset java.util.SortedSet java.util.TreeSet 1.0 M2
    iterator java.util.Iterator n/a 1.0 M3

    The type of the object inside the collection is the 'type' attribute. The 'default implementation' is the type used if the object holding the collection is found to be null and needs to be instantiated.

    For hashtable and map, Castor will add an object using the put(object, object) method - the object is both the key and the value. This will be improved in the future.

It is necessary to use a collection when the content model of the element expects more than one element of the specified type. This is how the 'to-many' portion of a relationship is described.

[Note]Note

It is not possible to use a collection of type 'iterator' or 'enumerate' with lazy loading enabled.

2. Determine the signature of the method

  • If 'direct' is set to true, Castor expects to find a public Java object variable with the given signature:

      public <type> <name>;
  • If 'direct' is set to false or omitted, Castor will access the property though accessor methods. Castor determines the signature of the accessors and mutators as follows: If the 'get-method' or 'set-method' attributes are supplied, it will try to find a function with the following signature:

      public <type> <get-method>();

    or

      public void <set-method>(<type> value);

    If 'get-method' or 'set-method' attributes are not provided, Castor will try to find the following functions:

      public <type> is<capitalized-name>();

    or

      public <type> get<capitalized-name>();

    the former for boolean types, the latter for all other types (or if the 'is<capitalized-name>()' method is not defined for a boolean type), and a standard set method of

      public void set<capitalized-name>(<type> value);

    If there are more than one set<capitalized-name> method it first tries to find the one that exactly matches <type>. If no such method is available and <type> is a java primitive type it tries to find a method with the corresponing java object type.

    <capitalized-name> means that Castor uses the <name> attribute by changing its first letter to uppercase without modifying the other letters.

    The content of the <field> element will contain the information about how to map this field to the relational table.

Description of the attributes:

  • name: If 'direct' access is used, 'name' should be the name of a public variable in the object we are mapping (the field must be public, not static and not transient). If no direct access and no 'get-/set-method' is specified, this name will be used to infer the name of the accessor and mutator methods.

  • type: The Java type of the field. This is used to access the field. Castor will use this information to cast the data type(e.g. string into integer). It is also used to define the signature of the accessor and mutator methods. If a collection is specified, this is used to specify the type of the object inside the collection. See description above for more details.

  • required: If true, the field is not optional.

  • transient: If true, the field will be ignored during persistence (and XML un-/marshalling). If you want this field to be ignored during any persistence-related operations only, please use the 'transient' attribute at the <sql> level.

  • identity: If true, the field is part of the class identity. Please use this as an alternative way of specifying the identity of an object.

  • direct: If true, Castor expects a public variable in the object and will modify it directly.

  • collection: If a parent object expects more than one occurrence of one of its fields, it is necessary to specify which collection type Castor will use to handle them. The type specified is used to define the type of the content inside the collection.

  • comparator: If the collection type equals 'sortedset', it is possible to specify a java.util.Comparator instance that will be used with the java.util.SortedSet (implementation) to specify a custom sort order. Please use this attribute to specify the class name of the Comparator instance to be used. Alternatively, it is possible to not specify a Comparator instance and have the Java objects stored in the SortedSet implement java.util.Comparable to specify the sort order.

  • get-method: An optional name of the accessor method Castor should use. If this attribute is not set, Castor will try to guess the name with the algorithm described above.

  • set-method: An optional name of the mutator method Castor should use. If this attribute is not set, Castor will try to guess the name with the algorithm described above.

  • create-method: Factory method for instantiation of the object.

3.6.3.5. The <sql> element

  <!ELEMENT sql EMPTY>
  <!ATTLIST sql
            name        NMTOKENS  #IMPLIED
            type        NMTOKENS  #IMPLIED
            many-key    NMTOKENS  #IMPLIED
            many-table  NMTOKEN   #IMPLIED
            transient   ( true | false )    "false"
            read-only   ( true | false )    "false"
            dirty       ( check | ignore )  "check">
            cascading	( create | delete | update | all | none ) "none"

The <sql> element is used to denote information about the database column to which a Java object is mapped. It should be declared for all <field> elements. Each <field> element contains one <sql> element. The <sql> element correlates directly to the <map-to> element for the containing <class> element. The <sql> elements contains the following attributes:

  • name: The name of the column in the database table.

  • type: The JDBC type of the column. It is inferred from the object when the type of this field is a persistent Java class that is defined elsewhere in the mapping. The complete list of automatic type conversions, and which values require manual mapping (e.g, java.util.Date) is listed in the SQL Type Conversion section of the Type Support document.

  • read-only: If true, the column in the relational database table will only be read, not updated or deleted.

  • transient (as of 0.9.9): If true, the field will be ignored during persistence only. If you want this field to be ignored during XML un-/marshalling as well, please use the 'transient' attribute at the <field> level.

  • dirty: If the value is 'ignore', the field will not be checked against the database for modification.

  • cascading: If the field is a relation, this attribute specifies which operations to cascade. Possible values are: 'all', none', 'create', 'update' or 'delete'; when not specifying 'none' or 'all', it is possibel to specify more than one value, using whitespace as a delimiter (e.g. 'create update'). For further information see HOW-TO on using cascading operation.

There are two more attributes used only with 'to-many' relations.

  • many-key: Specifies the name of the column that holds the foreign key to this object. That column is in the database table that stores objects of the Java type of this field.

  • many-table: Specifies the name of the bridge table that contains the primary keys of the object on each side of the relationship. This is only used for many-to-many relationships.

3.7. Castor JDO FAQ

3.7.1. Castor's relation to other specifications

3.7.1.1. Does Castor JDO comply with the SUN JSR-000012 specification?

No, Castor JDO doesn't comply with the SUN's JDO specification.

Although Castor JDO carries very similar goals as SUN's JDO, it has been developed independently from the JSR.

Although it is not impossible to shape (perhaps "hammer" is a more descriptive verb) Castor JDO into the SUN's JDO specification, there are several major technical differences which make it unfavorable to do so. Castor is RDBMS centric. Each persistence object loaded by Castor is locked. Locks in Castor are observable, meaning that locks may not be granted because of timeout or deadlock. On the other hand, the SUN's JDO hides details about locks.

Internally, Castor JDO maintains a single copy of lock (and cache) for each active persistence object for all transaction. SUN's JDO specification implicitly requires a copy of cache per object per transaction. SUN's JDO also implicitly requires a bytecode modifier which Castor doesn't require.

Castor also provides other features, such as key generators, long transaction support and OQL query which cannot be found in SUN's JSR.

3.7.1.2. Is Castor JDO better than EJB CMP?

The relation between JDO and EJB Container-Managed Persistence is more complicated than simply saying, "one is better than the other".

An Entity Bean may manage persistence itself - the EJB specification calls this Bean Managed Persistence (BMP). Alternatively, the Entity Bean may rely on an EJB container to manage all peersistence automatically - the EJB specification calls this Container Managed Persistence (CMP). When implementing BMP, an Entity Bean may use Castor JDO as its persistence mechanism, or it may use others methods, such as dealing with JDBC directly. During CMP, an EJB Container vendor may implement their CMP on top of Castor JDO. In such an implementation, Castor JDO will be used to persist the Entity Bean.

If a developer would like to take advantage of an EJB's life-cycle management, security, the "write once deploy anywhere" promise and other distributed business application facilities, then EJB will be the right choice. Otherwise, the fact that Castor is simple, is Open Source (you can always include Castor in your application or product), has much less overhead, provides more design freedom, and is integrated with Castor XML may be enough of a reason to choose Castor JDO.

3.7.2. XML related questions

3.7.2.1. Is it possible to make XML marshalling transactionally using Castor?

No. The decision of putting XML and JDO together is NOT intended to make XML marshalling transactional. Instead, the integration is done to help developers of a typical client-server situation whereby an application server receives incoming XML messages, process the messages and replies to the client.

With Castor, incoming XML messages can be unmarshaled into data objects. Required information can be obtained from a database using JDO in form of data objects. With this approach, all data manipulation can be done in an object-oriented way. Changes to JDO data objects can be committed transactionally, and result data objects can be marshaled into XML and returned to the client.

3.7.2.2. Is it possible to do queries on a XML file using Castor?

No, Castor does not provide an OQL query facility on a XML file. If querying is important for you, you should consider using a DBMS to store your data instead of using XML files, especially if querying performance is a concern.

Another alternative is parse an XML Document directly and use XPath to retrieve Nodes and/or NodeSets from an XML Document. Other open source tools which provide this functionality are:

3.7.3. Technical questions

3.7.3.1. Where can I find some examples to start with?

Download the full SVN snapshot and look into the src/tests/jdo directory.

3.7.3.2. I have encountered problems using Sun JDBC-ODBC bridge with Castor...

It cannot be used with Castor, because it doesn't allow more than one open ResultSet at the same time. Either use JDBC driver of type > 1, or use some other JDBC-ODBC bridge without such a restriction (for example, from Easysoft).

3.7.3.3.  My get-method for the Collection of dependent objects returns null. Why?

You should initialize the Collection yourself:

private Collection _children = new ArrayList();

public Collection getChildren() {
    return _children;
}
            

3.7.3.4. Should my JDO classes implement some special interface?

In general, no. If you need some behavior that is not directly supported by Castor, you can implement interface org.exolab.castor.jdo.Persistent. In order to use dirty checking for long transaction you should implement interface org.exolab.castor.jdo.TimeStampable. If you need an example of use of these interfaces, see Persistent.java and TestPersistent.java among Castor JDO tests.

3.7.3.5. Can Castor automatically create/remove related objects?

First of all, let's agree upon terminology. We distinguish dependent and independent objects:

  • dependent objects are bounded to the parent object's lifecycle

  • independent objects have independent lifecycle

Thus, dependent objects are created/removed automatically, when their parent object is created/removed, while all operations on independent objects should be performed explicitly.

However, with Castor 0.8.x you cannot describe explicitly the kind of object. Instead, the following rule applies: if you have one-to-many relation, and each side of the relation refers to another (Collection attribute on "one" side, simple attribute on "many" side), then "many" side is a dependent object. All other objects are independent. In particular, related objects via one-to-one relation are not created/removed automatically.

With Castor 0.9 dependent objects should be described via "depends" attribute of "class" element in mapping configuration file.

If you wish some independent object was created and/or removed automatically on operations on other independent object, you may use interface Persistent to code the desired behavior.

3.7.3.6.  Is Castor JDO using any connection pooling mechanism to improve the overall performance?

No, Castor JDO doesn't have any built-in JDBC resource pooling. However the framework can transparently use any resource pooling facilities provided through DataSource implementation or -even better- through JDNI. In fact we even recommend people to use some Connection and PreparedStatement pool with Castor as this can increase Castor's performance 3-5 fold.

For example the following set of statements:

db.begin();
db.execute(...)
db.commit()
	            

will be executed in much less time with the resource pooling because it will avoid creating a new physical JDBC connection at every execution.

With Oracle, instead of specifying the usual JDBC driver you can use a DataSource that specifically provides some Connection caching/pooling.

Thus if your jdo config file looks like :

        <database name="..." engine="oracle" >
            <driver class-name="oracle.jdbc.driver.OracleDriver"
                    URL="jdbc:oracle:thin:@localhost:1521:TEST" >
                <param name="user" value="SYSTEM"/>
                <param name="password" value="manager"/>
            </driver>
            ...
        </database>
		        

then it can be changed into (for example):

        <database name="..." engine="oracle" >
            <data-source class-name="oracle.jdbc.pool.OracleConnectionCacheImpl">
                <params URL="jdbc:oracle:thin:@localhost:1521:TEST"
                        user="scott" 
                        password="tiger"
                />
            </data-source>
            ...
        </database>
		        

When Castor is used inside a Container such as an EJB container (within BMP or Session Bean), then the Container usually provides the JDBC resource through the JNDI ENC, which implicitely includes pooling facilities.

3.7.3.7.  I am getting ClassNotFoundException for my JDO class, but it is in the class path. Why?

Probably castor.jar file is in jre/lib/ext directory. In this case you should call:

      jdo.setClassLoader(getClass().getClassLoader());
	            

before jdo.getDatabase().

3.7.3.8.  I am getting exception 'the class ... is not persistence capable...'. Why?

In this case as well as in many others you can get more information with the help of logging. Call:

  jdo.setLogWriter(Logger.getSystemLogger());
	        

and seek in the output for warnings and errors.

3.7.3.9.  I call db.remove() on the dependent object and commit, but this doesn't work...

You should not add/remove dependent objects directly. In order to add/remove the dependent object you should just add/remove it from the collection in the master object and call db.commit()

Dependent objects cannot be created/removed explicitly. It's created automatically when it is added to a master object, and removed automatically when it de-linked/dereferenced from a master object.

Otherwise, we will be encounter into problem where a dependent object created explicitly but removed implicitly (delinked from a master object), or vice versa. It can also lead to other problems that are harder to detect.

3.7.3.10.  How should I represent string/date/boolean literals in OQL query?

It is recommended to replace literals with parameters and to set them via OQLQuery.bind(), for example:

OQLQuery query = db.getOQLQuery(
        "SELECT p FROM Person p "
      + "WHERE name LIKE $1 AND dob>$2 AND married=$3");
query.bind("John %");
query.bind((new SimpleDateFormat("yyyy-MM-dd"))
       .parse("1960-01-01"));
query.bind(false);
         

3.7.3.11.  I get 'java.lang.AbstractMethodError: getBigDecimal' for numeric fields. Why?

Your JDBC driver is not JDBC 2.0 compliant, upgrade it or find another one.

3.7.3.12. Does Castor support both one-way and two-way relationships?

Typcially a relationship between two objects is either one-way (aka uni-directional) or two-way (aka bi-directional). Officially, Castor currently only supports bi-directional relationships. For example, if an Order object contains a reference to a LineItem object, the LineItem object must contain a reference to the Order object. However, this requirement is not enforced in all situations.

This is a very complex problem to solve. So until Castor is expanded the support uni-directional relationships, the best policy is to implement the bi-directionalality for all relationships. This will ensure proper functionality.

3.7.3.13.  I have an object that holds a relation to itself. Does Castor support this?

This is a very common occurrence in an object model and is known as a self-referenential relationship. Unfortunately, Castor does not currently support self-referential relationships. An example of such a relationship occurs when a Folder object contains a reference to another Folder object. Castor does not currently support this. However, there are ways around this limitation.

One way is to manage this type of relationship manually. For example, let's say that a parent object FolderA needs to hold references to child objects FolderB, FolderC and FolderD. The Folder object contains not only a property to hold its own id, but also a property to hold its parent id (we'll call this parentId). The parentId property is used to determine if there is a relationship to another Folder object. If parentId is null, there is no relationship. If parentId is populated, there is a relationship and the object tree can be walked by comparing the object id to the parentId. When the two properties are equal, you're at the top of the tree.

Another say to solve this problem is to make use of an intermediary object. For example, a Folder object contains a Reference object in lieu of the actual Folder object. The Reference object is somewhat of a proxy object whereby it only contains enough information to identify the object to which the Reference object refers. Then the Folder object can be easily instantiated via the information contained in the Reference object.

3.7.3.14.  Why do I get an ObjectModifiedException when trying to commit a transaction?

The dirty checking engine will throw an ObjectModifiedException when the values in the cache and in the database are different. This can happen when someone else changed the database content, but also when type mapping is not reversible.

For example, if a java timestamp (java.util.Date) is stored as a DATE, the time part is lost and the dirty checking will fail. Oracle cannot tell the difference between an empty String and a null value: if an attribute value is an empty String, dirty checking will also fail. Some precision loss sometimes occur with floating point numbers.

To avoid this, always use reversible mapping conversions. If this is not possible, mark the fields with dirty="ignore" in the mapping file.

3.7.3.15. I'm receiving a java.sql.SQLException: ORA-01461

When using Weblogic Portal 4.0 with Oracle I am receiving the following error:

java.sql.SQLException: ORA-01461: can bind a LONG value only for insert into a LONG column
	            

According to Weblogic Release Notes, this error can remedied by setting a Weblogic environment variable.

3.7.4. Castor and performance caches

3.7.4.1.  Sometimes long transaction fails: on update() it is thrown ObjectModifiedException. Why?

Most probably the object that is being updated has more than 100 related objects of one class and the cache size for this class is not enough. You should either increase the size of the cache or change the cache type to time-limited (the default cache type is count-limited, the default size is 100), for example:

<class ...>
  <cache-type type="count-limited" capacity="1000"/>
  ...
</class>
		        

3.7.4.2. Can I use the cache-type='none' with long transactions?

As of release 0.9.5.3, you cannot. When using a cache of type 'none' with your 'Timestampable' objects, a MappingException is thrown when performing long transactions. Currently, Castor requires a (performance) cache of type other than 'none' to be used with classes that implement the TimeStampable interface. In other words, if you want to use long transactions, please make sure that you use one of these cache types: 'unlimited', 'count-limited' or 'time-limited'.

The next entry has some more information about a potential cause of confusion in the context of long transactions and a cache type other than 'unlimited'.

3.7.4.3.  What is causing a PersistenceException with long transactions and how do I fix it?

With long transactions, sometimes update() throws a PersistenceException. As of release 0.9.5.3, Castor requires a (performance) cache (of type other than 'none') to be used with classes that implement the TimeStampable interface.

Please note that if you are using a cache type other than 'unlimited', it is possible that objects expire from the cache. This case will be highlighted to you by a PersistenceException being thrown.

In this cases, please consider switching to cache type 'unlimited' (if possible) or increase the size of the cache according to your needs when using 'count-limited' (which has a default capacity of 100).

3.7.5. OQL

3.7.5.1. Is there any document available for Castor OQL?

Yes. It is available from the Castor website: Advanced JDO —> OQL

3.7.5.2.  The OQL document describes several phases of development. Which is the current phase?

We are currently working on Phase 3.

3.7.5.3. Does Castor OQL support sub-queries?

Not yet

3.7.5.4.  I cannot get Castor OQL to join two objects for me. Is it supported?

Yes or no. Castor OQL supports implicit joins. And, in most case, you simply don't need explicit join.

Consider the following example,

SELECT o FROM Order o, LineItem i WHERE o.id = i.id AND i.price > 100
	            

It is simply equivalent to the following OQL

SELECT o FROM Order o WHERE o.lineItem.price > 100
	            

3.7.5.5. Can I write a pass-thru OQL?

Yes. Just put "CALL SQL" keywords in front of your SQL statement. For example,

OQLQuery oql = castorDb.getOQLQuery(
    "CALL SQL SELECT id, name, date "
   +"FROM user WHERE upper(name) like $1 AS myapp.Product");
	            

But remember that the order of the fields listed must match what is defined in the mapping file.

3.7.5.6. Does Castor OQL support struct?

No, Castor OQL doesn't support struct. For example, the following query CANNOT be done:

select c.name, c.age from Client c
	            

3.7.5.7. How do I structure a query using the 'LIKE' expression?

A query using the 'LIKE' expression includes the use of the SQL wildcard '%'. The wildcard must be included in the bind() statement:

        OQLQuery oql = castorDb.getOQLQuery(
                "SELECT p FROM Product p WHERE p.name LIKE $1" );
        oql.bind( "%widget%" );
	            

3.7.5.8. Does Castor support the SQL 'IN' expression?

Yes. However, the full expression is a bit different using the LIST keyword. The following example provides a demonstration:

SELECT p FROM Product p WHERE p.id IN LIST ( 123, 456, 789 )
	            

If identifiers other than numbers are used, those identifiers must be quoted:

SELECT p FROM Product p WHERE p.name IN LIST ( "abc", "jkl", "xyz" )
	            

To include NULL values in the 'IN' list, use the 'nil' keyword:

SELECT p FROM Product p WHERE p.name IN LIST( "ABC", nil )
	            

It is even possible to include bind values in the 'IN' lists using the following syntax:

SELECT p FROM Product p WHERE p.id IN LIST( $(int)1, $2, $3 )
	            

3.7.6. Features requests

3.7.6.1. Can a foreign key be part of multiple primary keys?

Unfortunately, the answer is no. We're aware that many users need this feature so it is a very high priority in our todo list.

If foreign key is the primary key, as a workaround you may consider using the 'extends' relationship.

3.7.6.2. Is polymorphic collection supported?

Unfortunately, the answer is no.

In version 0.8.11, we tried to enable polymorphic collection by introducing the notation of Object Reloading. Object Reloading delegates the determination of the class to a data object. However, it is proved that reloading can only be done before any instance of the target object is returned to user, and we have no way to determine that. As a result, we removed the support in version 0.9.x.

In the near future, we are going to use a new mechanism to provide extends. The new mechanism loads a table with an SQL statement that outer-joins all of the extending tables with the base. The existence of an extended table row can be used to determine the class of a data object. Notice that all extended table rows of the same entity should always be stored in the same data-store.

In the further future, we also want to let users to define a discriminator column (or determinance field). Basing on the value of discriminator columns in the base table, the bridge layer fetches the additional information and returns the combined entity with the appropriate list of entity classes.

3.7.7. Data model issues

3.7.7.1. Is it possible to map an object to more than one tables?

Yes, if the two tables share the same identity, you can specify one data object to "extends" the other. When the extended data object is loaded, its table (specified in <map-to/> will be joined with all the tables of its super classes'.

Another solution (in my opinion more flexible) is having two set of methods in the main object. One for Castor JDO and another for application.

Consider the following example:

class Employee {
   private int _employeeNumber;
   private Address _address;
   private Collection _workGroup;

   public int getEmployeeNumber() {
        return _employeeNumber;
   }
   public void setEmployeeNumber( int id ) {
        _employeeNumber = id;
   }

   // methods for Castor JDO
   public Address getAddress() {
       return _address;
   }
   public void setAddress( Address address ) {
        _address = address;
   }
   public Collection getWorkGroup() {
        return _workGroup;
   }
   public Collection setWorkGroup( Collection workGroup ) {
        _workGroup = workGroup;
   }

   // methods for application
   public String getAddressCity() {
       return _address.getCity();
   }
   public String getAddressZip() {
       return _address.getZip();
   }
   // ...
}
	            

3.7.7.2.  Can an object with the same identity be re-created after being removed in the same transaction?

Yes, as long as the deleted object is the same instance as the one being recreated.

3.7.7.3. What is a dependent object?

Dependent object is actually a concept from the object-oriented database world. A dependent object's lifetime depends on its master object. So, create/delete/update of the master object will trigger the proper actions, newly linked dependent object will be automatically created and de-referenced dependent object will be removed.

The concept was also used in the earlier CMP 2.0 draft, although it is later removed.

3.7.7.4.  Can a data object involved in many-to-many relationship be dependent?

No

3.7.8. Castor JDO design

3.7.8.1. How does Castor JDO work anyway?

Let's use object loading as an example.

When an application invoke db.load, the underneath TransactionContext is invoked. If the object with the requested identity exists in the TransactionContext, previously loaded object in the TransactionContext is returned. Otherwise, TransactionContext creates a new instance of the interested type and invokes LockEngine to "fill" the object.

LockEngine acquires a lock of the object, and it makes sure ClassMolder has a thread-safe environment when it invokes ClassMolder. In ClassMolder, if the interested set of fields representing the object is not existed in the cache yet, SQLEngine will be invoked and the set of fields from the underneath data store will be returned. ClassMolder binds the loaded or cached fields into the new instance. ClassMolder requests the TransactionContext to load the related and the dependent objects. Eventually, the object is returned after all of the relationships are resolved.

The process of commit has several states. The first state is preStore. In preStore state, objects existing in the TransactionContext are checked for modification one by one, including dependent and related objects. De-referenced dependent objects are marked as delete-able, and reachable dependent objects are added into TransactionContext. An object is marked "dirty" if it is modified. Also, if any modification should cause any related or dependent to be dirty, the related or dependent object is marked as dirty as well.

After the preStore state, all dirty object is properly stored. And, all marked delete object will be removed. Then, the connection is committed. If succeed, all cache with be updated. Finally, all lock is released.

3.7.8.2.  Does Castor support two-phase commits? How is this implemented?

Yes, via javax.transaction.Synchronization interface.

For Castor to work with global transactions, Castor must be configured to use global transaction demarcation in its main configuration file:

<jdo-conf>
   ...
   <transaction-demarcation mode="global" >
      <transaction-manager name="jndi" />
   </transaction-demarcation>
</jdo-conf>
            

When retrieving a Database instance via

...
JDOManager.loadConfiguration("jdo-conf.xml");
JDOManager jdo = JDOManager.createInstance("mydb");
...
Database db = jdo.getDatabase();
            

the Database implementation will authomatically be registered with the transaction manager, as it implements javax.jta.Synchronization interface. Subsequently, the transaction manager communicates with Castor via the beforeCompletion() and afterCompletion() calls.

3.7.8.3. Does Castor support nested transaction?

No

3.7.9. Working with open source databases

3.7.9.1. Does Castor support PosgreSQL?

Yes, starting from PostgreSQL 7.1, where outer joins support has been added.

3.7.9.2. Does Castor support MySQL?

Yes, starting from MySQL version 3.23, where transaction support has been added. Note: if you use Mark Matthews MySQL JDBC driver, then you need version 2.0.3 or higher.

3.7.9.3. Which Open Source database is supported better?

For now only with PostgreSQL 7.1 and SAP DB you get a full set of Castor features. Other Open Source databases don't support select with write lock, so db-locked locking mode doesn't work properly (it works in the same way as exclusive locking mode).

All other Castor features are supported with MySQL, Interbase, InstantDB and Hypersonic SQL.

3.7.10. RDBMS-specific issues

3.7.10.1. MySQL

3.7.10.1.1. Use of DATETIME fields in general

MySQL in it's current releases (4.0.x and 4.1.x) does not store fractions of a second in fields of type DATETIME that are mapped to java.sql.TimeStamp fields. As a result, Castor will throw ObjectModifiedExceptions during commits as Castor internally maintains fractions of a seconds.

Instead, Please use a column type that can be mapped to a long value, as Castor internally handles conversion between java.util.Date and long values with the required precision.

3.7.10.1.2. Use of TIMESTAMP fields & NULLs in long transactions

In MySQL, fields of type 'Timestamp' exhibit special behaviour wih regards to NULLs. When inserting a "NULL" into such a field, it actually inserts the current date and time. This causes problems for Castor's caching mechanism since Castor internally believes the field is still NULL. If you subsequently perform an update on the entry whilst it is still in the cache, an ObjectModifiedException will be thrown, because Castor believes that the database record has changed in the meantime.

The workaround is to use a DATETIME field instead of TIMESTAMP.

3.7.10.1.3. MySQL 4.1.x and upgrade issues

As with many other open source products, MySQL seems to be changing slightly from version to version. There seems to be a problem with concurrency in MySQL 4.1.5 that can be resolved by upgrading to 4.1.7 or higher.

At Castor we frequently use Connector/J 3.0.16, 3.1.13 and nowadays 5.1.6 to execute our test farmework. If you use one of this versions of Connector/J you should be on the safe side. If you are hit by any problems using one of these versions, please let us know.

3.7.10.2. Oracle

3.7.10.2.1. Oracle & (C|B)LOB fields

As of Oracle release 10g, the problem of Castor to handle BLOBs with a size greater than 2kB and CLOBs with a size greater than 4 kB correctly has been resolved. With the 10 release of Oracle's JDBC driver, both driver types (type 2 and type 4) can be used. With earlier releases, only the OCI driver (type 2) seems to work.

The 10g release of the Oracle JDBC Driver can be downloaded here.

3.7.11. Castor & Logging

3.7.11.1.  How can I integrate Castor's logging with a logging infrastructure using Log4J?

Please see this message from the mailing list. It includes an adapter class that will provide this functionality. (Thanks John!)

3.7.11.2.  Can I see what SQL statement Castor issues to the database as a result of an operation?

Yes, you can. By default, Castor uses JDBC proxy classes (wrapping java.sql.Connection and java.sql.PreparedStatement) that capture the core SQL statements as generated by Castor and the user-supplied parameters at execution timeof the various persistence operations, and outputs them to the standard logger used by Castor. By default, these output statements are not visible, as the log level is set to level 'info'. To see these SQL statements, please increase the log level to level 'debug' in log4j.xml.

3.7.11.3. How can I disable the use of JDBC proxy classes?

As of release 0.9.7, a new property

org.exolab.castor.persist.useProxies

has been added to castor.properties to allow configuration of the JDBC proxy classes mentioned above. If enabled, JDBC proxy classes will be used for logging SQL statements. When turned off, no logging statements will be generated at all.

3.7.12. Lazy Loading related questions

3.7.12.1.  How do I configure the JDO mapping to use the lazy loading feature for 1:1 relations?

Let us convert one of the classes from the JDO examples to use lazy-loading.

In the example model, every Product belongs to one ProductGroup. This is reflected in the conventional mapping as below. Here's the mapping for Product:

<!--  Mapping for Product  -->
<class name="myapp.Product"
       identity="id">
   <description>Product definition</description>
   <map-to table="prod" xml="product" />
   <field name="id" type="integer">
       <sql name="id" type="integer" />
       <xml name="id" node="attribute"/>
   </field>
 
   <!-- more fields ... -->

    <!--  Product has reference to ProductGroup,
          many products may reference same group  -->
    <field name="group" type="myapp.ProductGroup">
      <sql name="group_id" />
      <bind-xml name="group" node="element" />
    </field>
    
</class>

Let us now make the relationship between Product and ProductGroup use lazy loading. The relevant field in Product can be re-written like so:

    <field name="group" type="myapp.ProductGroup" lazy="true">
      <sql name="group_id" />
      <bind-xml name="group" node="element" />
    </field>
           

There have been one change only. We have placed the attribute lazy="true" in the field element. Note that no change is required in the ProductDetail mapping.

3.7.12.2. Lazy loading for 1:1 relations and serialization

Please note that Castor does not support full serialization of lazy-loaded objects at this time. Rather than serializing just the information required to re-build the underlying proxy implementation during deserialization, Castor will materialize (read: load from the persistence store) all objects before serialization. As this can lead to a lot of database accesses, please use this feature carefully. A full working solution will be provided with the next release.

3.7.12.3.  How do I configure the JDO mapping to use the lazy loading feature for 1:m and M:N relations?

Let us convert one of the classes from the JDO examples to use lazy-loading.

In the example model, one Product can contain many ProductDetails. This is reflected in the conventional mapping as below. First, the mapping for Product:

<!--  Mapping for Product  -->
<class name="myapp.Product"
       identity="id">
   <description>Product definition</description>
   <map-to table="prod" xml="product" />
   <field name="id" type="integer">
       <sql name="id" type="integer" />
       <xml name="id" node="attribute"/>
   </field>
 
   <!-- more fields ... -->

   <!-- Product has reference to ProductDetail
            many details per product  -->
   <field name="details" type="myapp.ProductDetail" required="true"
           collection="vector">
       <sql many-key="prod_id"/>
       <xml name="detail" node="element" />
   </field>
</class>
	            

Now let us examine ProductDetail. Note, the relationship is mapped bi-directionally as must be all relationships when using Castor JDO.

<!--  Mapping for Product Detail -->
<class name="myapp.ProductDetail" identity="id" depends="myapp.Product" >
   <description>Product detail</description>
    <map-to table="prod_detail" xml="detail" />
   <field name="id" type="integer">
       <sql name="id" type="integer"/>
       <xml node="attribute"/>
    </field>
    <field name="product" type="myapp.Product">
       <sql name="prod_id" />
       <xml name="product" node="element" />
    </field>
   
       <!-- more fields ... -->
   
</class>
	           
	           

Let us now make the relationship between Product and ProductDetail use lazy loading. We need only change the way that the relationship to ProductDetail is specified in the mapping of Product. The relevant field in Product can be re-written like so:

<field name="details" type="myapp.ProductDetail" required="true" lazy="true"
            collection="collection">
   <sql many-key="prod_id"/>
   <xml name="detail" node="element" />
</field>
           
           

There have been two changes.

  • We have placed the attribute lazy="true" in the field element

  • We have changed the type of the underlying collection type to be a java.util.Collection by changing the field element attribute to collection="collection".

Note that no change is required in the ProductDetail mapping.

3.7.12.4.  I have modified my mapping to use lazy loading. Now I get the error 'no method to set value for field: com.xyz.ClassB in class: ClassMolder com.xyz.ClassA' or 'org.exolab.castor.jdo.DataObjectAccessException: no method to set value for field: com.xyz.ClassB in class: ClassMolder com.xyz.ClassA'. What am I doing wrong?

To use lazy loading you must also change the persistent class that will hold the related objects. At the very highest level, you need to provide a set method that accepts a java.util.Collection for the field in question. This is demonstrated by changing the JDO examples below.

In the original Product class we have the following code:

import java.util.Vector;
...
private Vector _details = new Vector();
...
public Vector getDetails()
{
   return _details;
}


public void addDetail( ProductDetail detail )
{
   _details.add( detail );
   detail.setProduct( this );
}
	           

Let us now make the necessary changes to set up lazy loading. As stated above we now require a special set method for the related ProductDetails (stored originally as a java.util.Vector) that accepts a java.util.Collection as an argument. This mandates that we must also use a java.util.Collection to hold our ProductDetails. If this is not added, you will receive the errors above.

import java.util.Collection;
...
private Collection _details;
...
public Collection getDetails()
{
   return _details;
}


public void setDetails( Collection details )
{
   _details = details;
}

	           

3.7.13. Tuning for LOBs

Castor JDO provides a property in castor.properties for adjusting the size of the JDBC driver's buffer for reading LOBs (BLOBs and CLOBs) from the database. The propery is named org.exolab.castor.jdo.lobBufferSize and its default is 5120 bytes (5k). The size of this buffer can be tuned for larger LOBs, but is dependent upon the JDBC driver implementation being used and what it supports.

3.7.14. Database-specific issues

3.7.14.1. HSQL and identity key generators

Due to a product limitation, AUTO_INCREMENT sequences in HSQL begin with 0 rather 1, as is the case with most other RDBMS. As a result of this, long transactions will not work for the object with the identity 0, and a ObjectModifiedException will be thrown.

To avoid this issue, we recommend inserting a temp object into the database in question, and removing thereafter so that no object with identity 0 is stored.

3.7.15. Changing database configurations

Some applications need to change the database connection or switch between different mapping files on the fly. Because Castor caches database configurations per name, you would have to register a new JDO configuration using a distinct name for any of the different configurations.

Instead you can call org.exolab.castor.jdo.engine.DatabaseRegistry.clear() to reset the database registry before registering the new configuration as follows:

// Reset database registry
org.exolab.castor.jdo.engine.DatabaseRegistry.clear();
		

3.8. Castor JDO code samples

Authors

Werner Guttmann ()

3.8.1. Introduction

This document provides object mapping examples and the corresponding Java objects and DDL for the database table.

3.8.1.1. Java class files

The following fragment shows the Java class declaration for the Product class:

package myapp;

public class Product {

    private int       _id;

    private String    _name;

    private float     _price;

    private ProductGroup _group;


    public int getId() { ... }

    public void setId( int anId ) { ... }

    public String getName() { ... }

    public void setName( String aName ) { ... }

    public float getPrice() { ... }

    public void setPrice( float aPrice ) { ... }

    public ProductGroup getProductGroup() { ... }

    public void setProductGroup( ProductGroup aProductGroup ) { ... }
}

The following fragment shows the Java class declaration for the ProductGroup class:

public class ProductGroup {

    private int       _id;

    private String    _name;

    public int getId() { ... }

    public void setId( int id ) { ... }

    public String getName() { ... }

    public void setName( String name ) { ... }

}

3.8.1.2. DDL

The following sections show the DDL for the relational database tables PROD, PROD_GROUP, and PROD_DETAIL:

PROD:

create table prod (
  id        int           not null,
  name      varchar(200)  not null,
  price     numeric(18,2) not null,
  group_id  int           not null
);

PROD_GROUP:

create table prod_group (
  id        int           not null,
  name      varchar(200)  not null
);

PROD_DETAIL:

create table prod_detail (
  id        int           not null,
  prod_id   int           not null,
  name      varchar(200)  not null
);

3.8.1.3. Object Mappings

The following code fragment shows the object mapping for the ProductGroup class:

<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Object Mapping DTD Version 1.0//EN"
                         "http://castor.org/mapping.dtd">
<mapping>

  <class name="myapp.ProductGroup" identity="id">

    <description>Product group</description>

    <map-to table="prod_group" xml="group" />

    <field name="id" type="integer" >
      <sql name="id" type="integer"/>
    </field>

    <field name="name" type="string">
      <sql name="name" type="char" />
    </field>

  </class>

</mapping>

As a result of that declaration, Castor JDO will create the following SQL statements for creating, deleting, loading and updating instances of ProductGroup:

create: INSERT INTO prod_group (id, name) VALUES (?,?)
delete: DELETE FROM prod_group WHERE id=?
load:   SELECT prod_group.id, prod_group.name FROM prod_group WHERE prod_group.id=?;
update: UPDATE prod_group SET name=? WHERE id=?
       
3.8.1.3.1. Read-only fields

To declare the name field read-only, the field definition above for the field name needs to be changed to:

  <class name="myapp.ProductGroup" identity="id">

    ...
    <field name="name" type="string">
      <sql name="name" type="char" read-only="true" />
    </field>

  </class>

As a result of that declaration, Castor JDO creates the following SQL statements for creating, deleting, loading and updating instances of ProductGroup:

create: INSERT INTO prod_group (id) VALUES (?)
delete: DELETE FROM prod_group WHERE id=?
load:   SELECT prod_group.id, prod_group.name FROM prod_group WHERE prod_group.id=?;
update: /* no statement will be generated */
          
3.8.1.3.2. Transient fields

To declare the name field transient with regards to persistence, above field definition for the field name needs to be changed to:

  <class name="myapp.ProductGroup" identity="id">

    ...
    <field name="name" type="string" >
      <sql name="name" type="char" transient="true" />
    </field>

  </class>

3.8.1.4. Relations

3.8.1.4.1. 1:1 relation

The following code fragment shows the mapping file for the Product class. Apart from the simple field declarations, this includes a simple 1:1 relation between Product and ProductGroup, where every product instance is associated with a ProductGroup:

    <class name="myapp.Product" identity="id">

        <map-to table="prod" />

        <field name="id" type="integer">
            <sql name="id" type="integer" />
        </field>

        <field name="name" type="string">
            <sql name="name" type="char" />
        </field>

        <field name="price" type="float">
            <sql name="price" type="numeric" />
        </field>

        <field name="group" type="myapp.ProductGroup" >
            <sql name="group_id" />
        </field>

        <field name="details" type="myapp.ProductDetail"
               collection="vector">
           <sql many-key="prod_id"/>
        </field>

        <field name="categories" type="myapp.Category" collection="vector">
           <sql name="category_id"
                many-table="category_prod" many-key="prod_id" />
        </field>

    </class>
3.8.1.4.2. 1:M relation

The following code fragment shows (again) the mapping file for the Product class. The field definition highlighted shows how to declare a 1:M relation between Product and ProductDetail, where every product instance is made up of many ProductDetails:

    <class name="myapp.Product" identity="id">

        <map-to table="prod" />

        <field name="id" type="integer">
            <sql name="id" type="integer" />
        </field>

        <field name="name" type="string">
            <sql name="name" type="char" />
        </field>

        <field name="price" type="float">
            <sql name="price" type="numeric" />
        </field>

        <field name="group" type="myapp.ProductGroup" >
            <sql name="group_id" />
        </field>

        <field name="details" type="myapp.ProductDetail" collection="vector">
           <sql many-key="prod_id"/>
        </field>

        <field name="categories" type="myapp.Category" collection="vector">
           <sql name="category_id"
                many-table="category_prod" many-key="prod_id" />
        </field>
    </class>

The following code fragment shows the corresponding mapping entry for the ProductDetail class that defines the second leg of the 1:M relation between Product and ProductDetail.

  <class name="myapp.ProductDetail" identity="id" depends="myapp.Product" >

    <description>Product detail</description>

    <map-to table="prod_detail" xml="detail" />

    <field name="id" type="integer">
      <sql name="id" type="integer"/>
    </field>

    <field name="name" type="string">
      <sql name="name" type="char"/>
    </field>

    <field name="product" type="myapp.Product">
      <sql name="prod_id" />
    </field>

  </class>
3.8.1.4.3. M:N relation

The following code fragment shows (again) the mapping file for the Product class. The field definition highlighted shows how to declare a M:N relation between Product and ProductCategory, where many products can be mapped to many product categories:

    <class name="myapp.Product" identity="id">

        <map-to table="prod" />

        <field name="id" type="integer">
            <sql name="id" type="integer" />
        </field>

        <field name="name" type="string">
            <sql name="name" type="char" />
        </field>

        <field name="price" type="float">
            <sql name="price" type="numeric" />
        </field>

        <field name="group" type="myapp.ProductGroup" >
            <sql name="group_id" />
        </field>

        <field name="details" type="myapp.ProductDetail" collection="vector">
           <sql many-key="prod_id">/>
        </field>

        <field name="categories" type="myapp.Category" collection="vector">
           <sql name="category_id"
                many-table="category_prod" many-key="prod_id" />
        </field>

    </class>

The following code fragment shows the corresponding mapping entry for the ProductCategory class that defines the second leg of the M:N relation between Product and Category.

  <class name="myapp.Category" identity="id">

    <description>
        A product category, any number of products can belong to
        the same category, a product can belong to any number of
        categories.
    </description>

    <map-to table="category" xml="category" />

    <field name="id" type="integer">
      <sql name="id" type="integer"/>
    </field>

    <field name="name" type="string">
      <sql name="name" type="char"/>
    </field>

    <field name="products" type="myapp.Product" collection="vector">
      <sql name="prod_id"
           many-table="category_prod" many-key="category_id" />
    </field>

  </class>

3.8.1.5. Extend relation & polymorphism

As of release 0.9.9, Castor supports polymorphic queries on extend hierarchies. (That is, hierarchies where some entities "extend" other entities.) To highlight this new feature, let's add two new classes to what we have currently.

package myapp;

public class Computer extends Product {

    private int       _id;

    private String    _make;

    public int getId() { ... }

    public void setId( int anId ) { ... }

    public String getmake() { ... }

    public void setMake( String aMake ) { ... }

}

public class Car extends Product {

    private int       _id;

    private Date    _registeredIn;

    public int getId() { ... }

    public void setId( int anId ) { ... }

    public Date getRegisteredIn() { ... }

    public void setRegisteredIn( Date aRegisteredIn ) { ... }
}

The corresponding DDL statements for the relational database tables COMP and CAR would look as follows:

COMP:

create table comp (
  id        int           not null,
  make      varchar(200)  not null
);

CAR:

create table car (
  id        int           not null,
  regIn     int           not null
);

Based upon the mapping defined for the Product class as shown above, the following code fragment shows the mapping for the Computer and Car classes.

    <class name="myapp.Computer" extends="myapp.Product" identity="id">

        <map-to table="COMP" />

        <field name="id" type="integer">
            <sql name="id" type="integer" />
        </field>

        <field name="make" type="string">
            <sql name="make" type="char" />
        </field>

    </class>

    <class name="myapp.Car" extends="myapp.Product" identity="id">

        <map-to table="CAR" />

        <field name="id" type="integer">
            <sql name="id" type="integer" />
        </field>

        <field name="registeredIn" type="date">
            <sql name="regIn" type="long" />
        </field>

    </class>

Based upon this mapping, it is possible to execute the following OQL queries against this class model:

OQLQuery query = d.getOQLQuery("SELECT c FROM myapp.Computer c");
        

To return all computers:

OQLQuery query = d.getOQLQuery("SELECT c FROM myapp.Computer c WHERE c.make = $");
        

To return all computers of a particular make:

OQLQuery query = d.getOQLQuery("SELECT p FROM myapp.Product p");
        

To return all products (where Castor will return the actual object instances, i.e. a Computer instance if the object returned by the query is of type Computer or a Car instance if the object returned by the query is of type Car):

OQLQuery query = d.getOQLQuery("SELECT p FROM myapp.Product p WHERE p.group.name = $");
        

To return all products that belong to the specified product group (where Castor will return the actual object instances, i.e. a Computer instance if the object returned by the query is of type Computer or a Car instance if the object returned by the query is of type Car):

3.8.1.6. OQL samples

Based upon above definitions, here are a few OQL sample queries that highlight various artifacts of the OQL support of Castor JDO.

To Be Written

3.9. Castor JDO - How To's

3.9.1. Introduction

This is a collection of HOW-TOs. The Castor project is actively seeking additional HOW-TO contributors to expand this collection. For information on how to do that, please see 'How to author a HOW-TO'.

3.9.2. Documentation

3.9.2.1. How to author a HOW-TO document

3.9.2.2. How to author an FAQ entry

3.9.2.3. How to author a code snippet

3.9.2.4. How to author core documentation

3.9.3. Contribution

3.9.3.1. How to setup Castor project in eclipse

3.9.3.1.1. Introduction

Are you just interested in how Castor source looks like, want to report a bug or enhancement request or like to contribute to the project? The first step we suggest you to do is to setup a Castor project with eclipse IDE. As we use eclipse to work at Castor, there is everything in place to work with eclipse. While you are free to use other IDE's, you will need to configure things yourself with them.

3.9.3.1.2. Prerequisites
3.9.3.1.3. Create Project
  • Create a New Project in eclipse from File -> New -> Projects

  • Select "Checkout Projects from SVN" in "SVN" from "Select a wizard" window and click Next (this option will only come if you have installed the subclipse plugin)

  • Select "Create a new repository location" and click Next

  • Enter the URL "https://svn.codehaus.org/castor/castor" and click Next

  • Select the folder "trunk" from the list and click Next

  • In "Check Out As" window the name of the project will be "castor" then click Next

  • At last, you can choose the workspace and click Finish

  • You can see castor project in your "Project Navigator" of eclipse

3.9.3.1.4. Troubleshooting

If you have trouble with Subclipse behind a proxy server: In Windows development environment, open the file: C:\Documents and Settings\MyUserId\Application Data\Subversion\servers in text editor. Near the bottom of that file is a [global] section with http-proxy-host and http-proxy-port (user and password also) settings. Uncommented those lines, modified them for your proxy server and go back to the SVN Repository view in Eclipse. This should solve the problem.

3.9.3.2. How to run Castor JDO's database independend unit tests

3.9.3.2.1. Overview

At the time of this writing Castor JDO has 3 kinds of test suites:

  • Database independend plan unit tests.

  • A JUnit based test suite that is used to test various functional areas against different database engines to give developers/committers some reassurance when changing the codebase.

  • A Junit based test suite to evaluate impact of changes on performance.

This document provides general information about running Castor JDO's database independend unit tests.

3.9.3.2.2. Prerequisites

See: How to setup Castor project in eclipse

3.9.3.2.3. Execute tests in eclipse

To execute tests in eclipse, go and right click on cpa/src/test/java source folder and select "Run As -> JUnit Test".

3.9.3.3. How to run Castor JDO's test suite

3.9.3.3.1. Overview

At the time of this writing Castor JDO has 3 kinds of test suites:

  • Database independend plan unit tests.

  • A JUnit based test suite (CTF) that is used to test various functional areas against different database engines to give developers/committers some reassurance when changing the codebase.

  • A Junit based test suite (PTF) to evaluate impact of changes on performance.

This document provides general information about running Castor JDO's test suite (CTF).

3.9.3.3.2. Intended Audience

Anyone who wants to run the CTF test suite. This document outlines the basic steps to get people unfamiliar with this area started.

3.9.3.3.3. Prerequisites

Anybody wishing to run the CTF test suite should have access to the source code of Castor. This can be obtained in one of the following ways:

  • Download the sources distribution from the download page

  • Download the latest snapshot from SVN from Fisheye (see links on the bottom left corner)

  • Check out the latest code from SVN into your prefered developement environment. For instructions on this task, take a look at Subversion access. For eclipse How to setup Castor project in eclipse provides a detailed description of this task.

3.9.3.3.4. 2 versions of CTF

At the moment we are in the middle of replacing the old CTF with a new one. While the old CTF still is our reference for refactorings of Castor does the new CTF contain some tests which could not be added to the old one due to its limitations. On the other hand are not all tests ported to the new CTF yet.

In the next sections we describe how to setup the environment to execute both CTF versions. While both versions of CTF are designed to be executed against every supported database engine, we will describe things with regard to mysql. Having said that there are only scripts for mysql at the new CTF at the moment. At a later step of the CTF refactoring we will add scripts for other databases as well. In addition we intend to allow its execution with an embedded derby database out of the box, but this have not been implemented yet.

For those who might be wondering about the numbering of tests, the numbers of the old tests are just random. The numbers of the new tests are the jira issue numbers.

3.9.3.3.5. Steps to setup environment for old CTF

To execute tests against mysql database you probably need access to a mysql server. To create a database for CTF, you have to execute the following commands on mysql consol.

# create database test;
# grant all on test.* at "localhost" to "test" identified by "test";
# use test;
# source [path-to-script];

If the server is not installed on your local machine (the one you execute the tests on) you have to replace "localhost" with the IP of the machine the tests get executed on. The script to execute can be found in "cpactf/src/old/ddl/" directory. For mysql it's "mysql.sql". If you like to use a different name for the database or use other user credential you can adjust them at "cpactf/src/old/resources/jdo/mysql.xml".

As we do not include JDBC drivers for every database with Castor you also have to add the driver you like to use to your classpath to execute the tests. The easiest way is to copy the driver to "lib/" directory as all jar's contained therein are added automatically. Another option is to modify "bin/test.sh" or "bin/test.bat" script depended on your operating system.

For mysql™ we still use "mysql-connector-java-3.1.13-bin", also for mysql server™ of version 5. This version has proven to be stable. While other versions of mysql connector™ may also work, some of them have bugs from our experience.

As already explained you will find JDO configurations for every supported database in "cpactf/src/old/resources/jdo". The JDO configurations are named mysql.xml, oracle.xml etc. In the same directory you will also find main mapping file "mapping.xml" that includes all other mappings which are located in the "cpactf/src/old/resources/ctf/jdo/..." directories. There is one more important file for the old tests "cpactf/src/old/resources/tests.xml", it is the main config file which defines which test should be executed against which database engine. As mentioned previously not every test works with every database engine as some missing some features or castor does not support everything of every engine.

3.9.3.3.6. Steps to run old CTF from commandline

From a command line (e.g a shell), please execute the following commands to run the whole test suite against mysql (where <castor-root> points to the directory where you installed the Castor sources:

cd <castor-root>/bin
build clean
build tests
test castor.mysql

To execute just one of the many tests of the complete test suite, please change this to:

cd <castor-root>/bin
build clean
build tests
test castor.mysql.TC30
[Note]Note

You have to execute "build clean" and "build tests" again if you have changed anything within eclipse (e.g. a configuration file or a class).

3.9.3.3.7. Steps to run old CTF out of eclipse

Now, let's see how we can run these old CPACTF tests through eclipse.

  • Go to "/cpacft/src/old/java" and right click

  • Select Run As -> Run...

  • Select "Java Application" from the left side menu and double click on it to create "New_configuration".

  • Select Project -> castor

  • Enter Main class -> MainApp

  • Select Arguments Tab

  • Enter Program Arguments for example: "castor.mysql.TC31" or "castor.mysql"

  • Now "Run"

3.9.3.3.8. Short description of the old CTF tests

As some features are not supported by all database engines (e.g. sequence keygenerator) or a test have not been verified against a database, only a subset of the following tests will be executed if you run CTF.

  • TC01 Duplicate key detection tests.

  • TC02 Concurrent access tests.

  • TC03 Read only tests.

  • TC04 Deadlock detection tests.

  • TC05 Update rollback tests.

  • TC06 Race tests.

  • TC07 Cache leakage tests.

  • TC08 Cache expiry measure.

  • TC09 TxSynchronizable interceptor tests.

  • TC10 Type handling tests.

  • TC11 Type handling of LOB tests.

  • TC12 Type Conversion tests.

  • TC13 Serializable object tests.

  • TC14 Rollback primitive tests.

  • TC15 Multiple columns primary keys tests.

  • TC15a Multiple columns primary keys only tests.

  • TC16 Nested fields tests.

  • TC17 Timestamp tests.

  • TC18 Persistence interface tests.

  • TC19 InstanceFactory interface tests.

  • TC20 Key generators: MAX, HIGH-LOW.

  • TC23 Key generator: IDENTITY.

  • TC24 Key generator: UUID.

  • TC25 Dependent objects tests.

  • TC26 Dependent objects tests.

  • TC27 Dependent update objects tests.

  • TC28 Dependent update objects tests.

  • TC30 OQL-supported syntax.

  • TC31 OQL queries for extends.

  • TC32 Test limit clause.

  • TC33 Test limit clause with offset.

  • TC34 Test limit clause with offset at extended object.

  • TC36 SizeOracle.

  • TC37 Absolute.

  • TC38 CALL SQL with parameters.

  • TC38a Named query support.

  • TC70 Collections.

  • TC71 Test special collections.

  • TC72 Test sorted collections.

  • TC73 ManyToMany.

  • TC74 ManyToManyKeyGen.

  • TC75 Expire Many-To-Many.

  • TC76 Cached OID with db-locked.

  • TC77 Query garbage collected.

  • TC78 JDBC connection.

  • TC79 Test the use of Database.isLocked().

  • TC79a Test auto-store attribute.

  • TC79aa Test auto-store attribute for 1:M relations.

  • TC79b Test the use of Database.isPersistent().

  • TC80 self-referential relation test with extend hierarchies.

  • TC81 Dependent relation test.

  • TC82 Dependent relation test (using no key generators).

  • TC83 Identity definition through identity attribute in field mapping.

  • TC84 Transient attribute.

  • TC85 TestEnum.

  • TC87 TestLazy1to1.

  • TC88 Lazy Loading.

  • TC89 Expire Lazy Employee.

  • TC93 Polymorphism Degenerated tests.

  • TC94 Polymorphism tests.

  • TC95 Polymorphism tests with key generator.

  • TC96 Polymorphism tests for depend relations.

  • TC97 Polymorphism tests.

  • TC98 Polymorphism tests in a threaded environment.

  • TC99 Polymorphism tests (many 2 many).

  • TC200 Self-referential relation tests.

  • TC201 Self-referential relation tests with extend hierarchy.

  • TC202 ForeignKeyFirst tests.

  • TC203 Timezone tests.

3.9.3.3.9. Steps to setup environment for new CTF

To execute tests against mysql database you probably need access to a mysql server. To create a database for CTF, you have to execute the following commands on mysql console.

# create database cpactf;
# grant all on cpactf.* at "localhost" to "test" identified by "test";
# use cpactf;
# source <path-to-script>;

If the server is not installed on your local maschine (the one you execute the tests on) you have to replace "localhost" with the IP of the maschine the tests get executed on. For mysql execute every "mysql.sql" script found in subdirectories of "cpactf/src/test/ddl/" directory. If you like to use a different name for the database or use other user credential you can adjust them at "cpactf/src/test/resources/cpactf-conf.xml".

As we do not include JDBC drivers for every database with Castor you also have to add the driver you like to use to your classpath to execute the tests. The easiest way is to copy the driver to "lib/" directory as all jar's contained therein are added automatically. Another option is to modify "bin/test.sh" or "bin/test.bat" script depended on your operating system.

For mysql we still use "mysql-connector-java-3.1.13-bin", also for mysql server™ of version 5. This version has proven to be stable. While other versions of mysql connector™ may also work, some of them have bugs from our experience.

3.9.3.3.10. Steps to run new CTF out of eclipse

Execution of the new test suite from within eclipse against mysql™ is very simple.

  • Select "cpactf/src/test/java" and right click

  • Select "Run as" -> "JUnit tests"

In in the configuration file "cpactf-conf.xml" mysql is configuerd as default database. To execute tests against another database engine or to force execution of tests that have been excluded you can pass VM parameter to the test framework. VM Arguments can also be specified in eclipse.

  • Select "Run as" -> "Run.." from main menu

  • Select Arguments Tab

  • Enter VM Arguments for example: "-Dname=value"

  • Now "Run"

The following VM parameters are supported by CTF.

config

Path to an alternate configuration file.

database

Name of the database configuration.

transaction

Name of the transaction manager configuration.

force

Force execution of excluded tests (true/false).

3.9.3.3.11. Troubleshooting

For those who face the following problem in eclipse while executing the tests

#An unexpected error has been detected by HotSpot Virtual Machine:
#
#EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x7c918fea, pid=2460, tid=3712
#
#Java VM: Java HotSpot(TM) Client VM (1.5.0-b64 mixed mode)
#Problematic frame:
#C [ntdll.dll+0x18fea]
#
#An error report file with more information is saved as hs_err_pid2460.log
#
#            If you would like to submit a bug report, please visit:
#              http://java.sun.com/webapps/bugreport/crash.jsp
#

It is a problem with memory configured for eclipse. It can be changed in eclipse.ini file which can be found in installation directory of Eclipse. By default it is -Xmx256m, just increase it and problem go away.

3.9.3.4. How to run Castor JDO's performance tests

3.9.3.4.1. Overview

At the time of this writing Castor JDO has 3 kinds of test suites:

  • Database independend plan unit tests.

  • A JUnit based test suite that is used to test various functional areas against different database engines to give developers/committers some reassurance when changing the codebase.

  • A Junit based test suite to evaluate impact of changes on performance.

This document provides general information about running Castor JDO's performance tests.

3.9.3.4.2. Prerequisites

See: How to setup Castor project in eclipse

3.9.3.4.3. Steps to setup environment for old CTF

To execute performance tests against mysql database you probably need access to a mysql server. To create a database for PTF, you have to execute the following commands on mysql console.

# create database cpaptf;
# grant all on cpaptf.* at "localhost" to "test" identified by "test";
# use cpaptf;
# source [path-to-script];

If the server is not installed on your local machine (the one you execute the tests on) you have to replace "localhost" with the IP of the machine the tests get executed on. The script to execute is "cpaptf/src/test/ddl/mysql.sql".

As we do not include JDBC drivers for every database with Castor you also have to add the driver you like to use to your classpath to execute the tests. The easiest way is to copy the driver to "lib/" directory as all jar's contained therein are added automatically.

For mysql we still use "mysql-connector-java-3.1.13-bin", also for mysql server™ of version 5. This version has proven to be stable. While other versions of mysql connector™ may also work, some of them have bugs from our experience.

3.9.3.4.4. Steps to execute performance tests in eclipse

Execution of the performance test suite from within eclipse is very simple.

  • Select "cpaptf/src/test/java" -> "org.castor.cpaptf" -> "TestAll.java" and right click

  • Select "Run As" -> "JUnit Test"

By default the test uses 2000 service objects that get created, loaded with various configurations and deleted afterwards. Obviously this will take quite some time. Please be patient for normal termination of the tests as you will need to clean test tables by hand otherwise. To execute tests with a different number of objects you can adjust "FACTOR" constant in "TestCreate.java". For example, if you set "Factor" to 1.0 the tests will be executed with 10000 service objects. When using more test objects be aware that you may need to increase heap size of the virtual machine for the test to finish.

By default you won't see any output of results on the console as logging level of log4j is set to "warn" by default. But if you change log level of "org.castor.cpaptf" package to "info" you will see detailed execution times for every test on the console. Be aware that there are quite some log4j.xml configurations in the whole Castor project at the moment of which the one first one on classpath will be used.

If you like to review some older test results you will find them under "cpaptf/src/site/resources/results/" but they depend heavy on the machine you are running the tests on.

3.9.3.5. Submitting a bug report

3.9.3.5.1. Submitting a problem report
3.9.3.5.1.1. Driver

Most of the traffic on the Castor Users mailing list is about people asking for help on various features of Castor (whether JDO or XML). Whilst there is nothing wrong about asking questions and looking for help if you are stuck, it seems that there is room to improve the structure of this 'conversation'.

If you have a look at some of these threads (e.g. at the searchable mailing list archive), it's quite easy to see that most of the time ...

  1. An initial question is posted.

  2. An initial reply is posted with some follow-up questions, e.g. request for code fragments, mapping files, etc.

  3. One or more code fragments, mapping files, etc. are being posted.

  4. etc.

This document will establish guidelines with regards to step 3) above.

3.9.3.5.1.2. Introduction

This document provides step-by-step instructions on how to submit a problem report (when being asked to do so). It does so by walking you through a fictive problem and its resulting bug report, and providing instructions based upon this scenario.

Per definition, any problem report submitted (in other words, most likely attached to a bug report at http://jira.codehaus.org/browse/CASTOR) by the means of a patch has to include the following artefacts:

  • A JUnit test case that 'showcases' your problem.

  • Castor JDO configuration file.

  • Castor JDO mapping file.

  • One or more 'entity' classes required to run your test case.

  • A SQL schema file (to help in the creation of required tables).

  • A README file (with any additional information/instructions required to understand /run your test case.

[Note]Note

Whilst we can and will not impose these guidelines in their strictest sense, I think that the use of technologies such as JUnit will simplify problem resolution and as a result lead to shorter turn-around times. Which is ultimately where you, the user, gains..

3.9.3.5.1.3. Templates

To facilitate creation of a fully featured patch as discussed above, we have provided you with an already existing bug template at 'src/bugs/jdo/template'.

[Note]Note

Please note that this directory is distributed only as part of the source archive(s).

This directory holds all artefacts mentioned above, as is structured as follows:

Table 3.7. bug template artefacts

srcSource code
src/bugsCommon root for bug reports
src/bugs/jdo Common root for JDO bug reports
src/bugs/jdo/template Patch template
src/bugs/jdo/template/TestTemplate.java JUnit test case.
src/bugs/jdo/template/EntityOne.java Entity required by the test case
src/bugs/jdo/template/jdo-conf.xml Castor JDO configuration
src/bugs/jdo/template/mapping.xml Castor mapping file
src/bugs/jdo/template/create.sql SQL schema to create database table(s)
src/bugs/jdo/template/READMETest instructions

To create you own bug report, please copy 'template' to e.g. bug1820, assuming that 1820 is the number assigned to your BugZilla problem report). Subsequently, please amend the files provided to match your own requirements. After you have consolidated your changes, the original directory structure in src/bugs could look as follows:

Table 3.8.  directory structure of src/bugs

src Source code
src/bugs Common root for bug reports
src/bugs/jdo Common root for JDO bug reports
src/bugs/jdo/template Patch template
src/bugs/jdo/template/TestTemplate.java JUnit test case.
src/bugs/jdo/template/EntityOne.java Entity required by the test case
src/bugs/jdo/template/jdo-conf.xml Castor JDO configuration
src/bugs/jdo/template/mapping.xml Castor mapping file
src/bugs/jdo/template/create.sql SQL schema to create database table(s)
src/bugs/jdo/template/README Test instructions
src/bugs/jdo/bug1820 Your specific bug (as per BugZilla bug number)
src/bugs/jdo/bug1820/TestCase.java JUnit test case.
src/bugs/jdo/bug1820/Entity1.java Entity required
src/bugs/jdo/bug1820/Entity2.java Entity required
src/bugs/jdo/bug1820/jdo-conf.xml Castor JDO configuration
src/bugs/jdo/bug1820/mapping.xml Castor mapping file
src/bugs/jdo/bug1820/schema.sql SQL schema to create database table(s)
src/bugs/jdo/bug1820/README Test instructions

3.9.3.5.1.4. Add your test case to the master test suite

Once you have successfully executed your JUnit test case, please add this test to the master test suite available in src/bugs/AllTests.java as follows. To add a test to this suite, please duplicate the line

			
    suite.addTestSuite(template.TestTemplate.class);
			

and replace the term 'jdo.template.TestTemplate.class' with 'jdo.bug1820.TestCase'.

This will allow us to run all tests related to all open bugs in one go by executing just this test suite.

3.9.3.5.1.5. Creating the patch

As explained in Guidelines For Code Contribution, we ask you to submit your code changes in the form of a unified patch by attaching it to the relevant bug report.

To create a unified patch for submission, you can either use the command line SVN client (which you find instructions to download for at http://subversion.tigris.org/ or any IDE that offers support or SVN (such as Eclipse with Subclipse plugin)

In any case, please note that we are trying to standardize on the use of unified patches only, and that you should always update your code (against the SVN repository) before creating the patch. If you have never used SVN before, there will be ways to convince us to accept e.g. a Zip file includig your changes.

3.9.3.5.1.6. References

Whilst we cannot assume that every Castor (JDO) user is an expert in the use of JUnit, we do believe that they are quite easy to get acquainted with. As a starting point, please find below some references:

  • JUnit - A well-known framework for writing integration-level and functional tests.

  • SVN Home - many usefulSVN related items

3.9.3.6. How to prepare a patch

3.9.3.6.1. Overview

A "patch" is the set of differences between two versions of the same file. Patches are used to send someone the exact changes that you have made to your version of a program or a document. They can then apply that patch to their version to merge the changes and bring their version up-to-date with your version.

As our example we use the contribution of a simple documentation patch for the Castor project. The principles apply to any project and to any type of file, e.g. *.xml, *.java, *.xsd, etc.

3.9.3.6.2. Intended Audience

Anyone who wants to contribute to a project. This document addresses the basics, so as to get new people started.

Our example describes the use of command-line tools for a UNIX system. Other tools can be used, as long as they produce a "unified diff".

3.9.3.6.3. Prerequisites

Contributers should have:

  • The source code of the documents as a local working copy of the SVN repository. If you are working with the current SVN HEAD then you will have already done a 'svn checkout castor'. However, see below for other ways of obtaining source for diff comparison.

  • The tools with which to prepare a patch. On UNIX the "svn" program has the svn diff command.

3.9.3.6.4. Steps

Here is how to proceed.

3.9.3.6.4.1. Understand what a patch is

A "Patch" is the set of differences between two versions of the same file. A patch comprises one or more "diff" files. These diffs are produced by the program of the same name: diff.

Here is an example of a single diff for one of the Castor How-to pages, where we are suggesting a minor text change. Do not get frightened. These are just human-readable instructions to the "patch" program.

            
Index: contrib.xml
===================================================================
RCS file: /home/projects/castor/src/doc/jdo-howto.xml,v
retrieving revision 1.7
diff -u -r1.7 contrib.xml
--- jdo-howto.xml 30 Apr 2002 07:44:52 -0000      1.7
+++ jdo-howto.xml 26 May 2002 04:08:23 -0000
@@ -208,7 +208,7 @@
    to create a patch. (The commands are for Linux.)
   </p>

- <s2 title="How to Establish your Local Repository">
+ <s2 title="How to Establish your Local Working Copy">

   <p>
    This will checkout the current copy of the master cvs repository and
            

That is a "unified diff" ... there a some lines of context on each side of the changes. This patch is basically saying "Change the text on line 208".

  • lines to be deleted are preceded with -

  • lines to be added are preceded with +

  • contextual lines with no leader remain the same

3.9.3.6.4.2. Modify your document and ensure consistency

Let us now go though the process of preparing that patch. Go ahead and edit your local copy of the document at $CASTOR_HOME/src/doc/jdo-howto.xml.

Ensure that it is valid XML using your favourite XML editor or an external validating parser. Please do not leave it up to the poor committer to fix broken XML.

Run the 'build doc' target to be sure that links are not broken and that the new document is presented as you intend it to be.

3.9.3.6.4.3. Get ready

If you are using the HEAD of SVN then ensure that your working copy is up-to-date. Of course, if you are using a previous public release version of Castor, then it is already up-to-date.

3.9.3.6.4.4. Generate the differences

Prepare the diff file. SVN will contact the remote repository, ensure that your working copy is up-to-date, then compare your local copy with the master repository.

            
    cd src/doc
    svn diff jdo-howto.xml > $TEMP/castor/patch/jdo-howto.xml.diff
            
3.9.3.6.4.5. Describe the patch

Prepare a brief explanation of what your patch does. Get this ready in a text file before you go to Jira. See further hints about this in the "Description" section of the How-to Jira.

What revision of SVN did you patch against? Was it HEAD branch? Was it a nightly build? Was it a public release?

3.9.3.6.4.6. Submit via Jira

To contribute your patch to a specific project, use Jira - The Codehaus Issue Database. The procedure is explained in How to Contribute a Patch via Jira.

3.9.3.6.5. Real World Extension
3.9.3.6.5.1. Multiple diffs in a single patch

A patchfile can contain the differences to various individual documents. For example, the following command does that ...

            
    cd src
    svn diff > $WORK/castor/patch/src.dir.diff
            

However, be careful not to go overboard with this technique. When producing multiple diffs in one patchfile, try to limit it to one particular topic, i.e when fixing the same broken external link in various pages, then it would be fine to produce a single diff. Consider the committer - they will find it hard to apply your patch if it also attempts to fix other things.

3.9.3.6.5.2. Other ways of obtaining source for diff comparison

Ideally you will prepare your patches against a SVN repository. There are other ways to do this. They do create more work for the committers, however it may be the only way that you can do it. We would certainly rather receive your patch however it comes. As a matter of fact, we would politely ask you first to send us a unified patch.

You could get the source document via the web interface to SVN. Here are the steps ...

  • get the relevant XML file via FishEye

  • save the file to your local disk: ./jdo-howto.xml.orig

  • create a copy of the file: ./jdo-howto.xml

  • make your modifications and validate the XML

  • use the "diff" command (i.e. not 'cvs diff') as follows

  • diff -u jdo-howto.xml.orig jdo-howto.xml > $WORK/castor/patch/jdo-howto.xml.diff

  • proceed as for Step 5.

3.9.3.6.6. Tips
  • Please review your diffs before you submit your patch to JIRA

3.9.3.6.7. References
  • The UNIX manual pages 'man diff' and 'man patch'.

  • SVN Home - many useful SVN related items

3.9.3.7. How to Contribute a Patch via Jira

3.9.4. OQL

3.9.4.1. How to use a LIMIT clause with OQL

3.9.4.1.1. Overview

3.9.4.1.2. Intended Audience

Anyone who wants to execute an OQL statement and limit the result size.

The example given describes the addition of LIMIT/OFFEST clauses to an existing OQL statement.

3.9.4.1.3. Prerequisites

You should have a valid class mapping for two Java classes Product and ProductGroup, similar to the following one:

        
package myapp;

public class Product 
{
    private int       _id;

    private String    _name; 

    private float     _price; 

    private ProductGroup _group;


    public int getId() { ... }
    public void setId( int anId ) { ... }

    public String getName() { ... }
    public void setName( String aName ) { ... }

    public float getPrice() { ... }
    public void setPrice( float aPrice ) { ... }

    public ProductGroup getProductGroup() { ... }
    public void setProductGroup( ProductGroup aProductGroup ) { ... }
}
        

The following fragment shows the Java class declaration for the ProductGroup class:


public class ProductGroup
{

    private int       _id;

    private String    _name;

    public int getId() { ... }
    public void setId( int id ) { ... }

    public String getName() { ... }
    public void setName( String name ) { ... }

}
        
3.9.4.1.4. Steps

Here is how to proceed.

3.9.4.1.4.1. Compose an OQL statement to obtain all ProductGroup instances

The following code fragment shows an OQL query to select the all ProductGroup instances.

            
OQLQuery query = db.getOQLQuery("select product from ProductGroup product");
query.bind(10);
OQLResults results = query.execute();
            
3.9.4.1.4.2. Add LIMIT clause to OQL statement

The following code fragment shows the same OQL query as above, to this time the LIMIT keyword is added to select the first 10 instances only.

            
    OQLQuery query = db.getOQLQuery(
            "select product from ProductGroup product LIMIT $1");
    query.bind(10);
    OQLResults results = query.execute();
            
3.9.4.1.4.3. Add OFFSET clause to OQL statement

Below is the same OQL query again, restricting the number of ProductGroup instances returned to 10, though this time it is specified that the ProductGroup instances 101 to 110 should be returned.

            
OQLQuery query = db.getOQLQuery(
        "select product from ProductGroup as product LIMIT $1 OFFSET $2");
query.bind(10);
query.bind(100);
OQLResults results = query.execute();
            
3.9.4.1.5. Limitations

The following RDBMS fully/partially support LIMIT/OFFSET clauses.

Table 3.9. Support for LIMIT in RDBMS

RDBMSLIMITOFFSET
postgreSQLYesYes
mySQLYesYes
Oracle 1) 2)YesYes
HSQLYesYes
MS SQLYes-
DB2Yes-

1) Caster has full support for LIMIT/OFFSET clauses for Oracle Releases 8.1.6 and later.

2) For the LIMIT/OFFSET clauses to work properly the OQL query is required to include an ORDER BY clause.

3.9.4.1.6. Tips
  • In the case a RDBMS does not support LIMIT/OFFSET clauses, a SyntaxNotSupportedException will be thrown.

3.9.4.1.7. References

3.9.5. Core features

3.9.5.1. How to use a (performance) cache with Castor

3.9.5.1.1. Intended Audience

Anyone who wants to enable caching for classes already mapped with Castor JDO.

This document addresses the basics to get people familiar with the basic concepts and discusses some implementation details.

The example given describes the addition of a <cache-type> element to an existing class mapping.

3.9.5.1.2. Prerequisites

You should have a valid class mapping for a Java class, similar to the following one:

        
<mapping>
    <class name="com.xyz.MyOtherObject" identity="id">
        <field name="id" type="integer">
               ...
        </field>
        <field name="description" type="string">
               ...
        </field>
    </class>
</mapping>
        
3.9.5.1.3. Steps

Here is how to proceed.

3.9.5.1.3.1. Add <cache-type> element to class mapping

Add a <cache-type> element as shown below, specifying the cache provider to use in the 'type' attribute.

            
<mapping>
    <class name="com.xyz.MyOtherObject" identity="id">
        <cache-type type="time-limited"/>
        <field name="id" type="integer">
               ...
        </field>
        <field name="description" type="string">
               ...
        </field>
    </class>
</mapping>
            

This, for example, defines the 'time-limited' cache provider to be used for the com.xyz.MyOtherObject. This cache provider applies internally a time-limited least-recently-used algorithm for com.xyz.MyObject instances.

3.9.5.1.4. Tips
  • With the current release, performance caches also serve a dual purpose as dirty checking caches for long-transactions. This limitation implies that the object's duration in the performance cache determines the allowed time span of a long transaction. This might become an issue when performance caches of type 'count-limited' or 'time-limited' are being used, as objects will eventually be disposed. If an application tries to update an object that has been disposed from the dirty checking cache, an ObjectModifedException will be thrown.

3.9.5.2. How to map typesafe enumerations with Castor

3.9.5.2.1. Intended Audience

Anyone who wants to persist object that refer to a typesafe enumeration.

This document addresses the basics and shows an example how to map an object that has a typesafe enumeration property.

3.9.5.2.2. Prerequisites

Enumerations are a common method for ensuring data integrity, both in software and in relational databases. As a platform for linking the two, we added support for persisting class fields whose type is a Java typesafe enumeration to Castor JDO.

To use this new feature your typesafe enumeration should follow the enum pattern commonly used and provide a static valueOf(String) method. An enum of different kinds of computer equipment may look like:

        
public class KindEnum {
    private static final Map KINDS = new HashMap();

    public static final KindEnum MOUSE = new KindEnum("Mouse");
    public static final KindEnum KEYBOARD = new KindEnum("Keyboard");
    public static final KindEnum COMPUTER = new KindEnum("Computer");
    public static final KindEnum PRINTER = new KindEnum("Printer");
    public static final KindEnum MONITOR = new KindEnum("Monitor");

    private final String _kind;

    private KindEnum(final String kind) {
        _kind = kind;
        KINDS.put(kind, this);
    }

    public static KindEnum valueOf(final String kind) {
	    return (KindEnum) KINDS.get(kind);
    }

    public String toString() { return _kind; }
}
        

At your Product class you may want to have a property that tells you what kind of computer equipment a product is of.

        
public class Product {
    private int         _id;
    private String      _name;
    private KindEnum    _kind;
    
    public Product() { }
    
    public int getId() { return _id; }
    public void setId(int id) { _id = id; }

    public String getName() { return _name; }
    public void setName(String name) { _name = name; }

    public KindEnum getKind() { return _kind; }
    public void setKind(KindEnum kind) { _kind = kind; }
}
        
3.9.5.2.3. Steps

Your mapping for the Product class should be:

        
<class name="Product" identity="id">
  <description>Product with kind enum</description>
  <map-to table="enum_prod"/>
  <field name="id" type="integer">
    <sql name="id" type="integer"/>
  </field>
  <field name="name" type="string">
    <sql name="name" type="char"/>
  </field>
  <field name="kind" type="KindEnum">
    <sql name="kind" type="char"/>
  </field>
</class>
        
3.9.5.2.4. Tips
  • To add this new feature we added an additional check when searching for field types. Like before Castor first searches for know types and thereafter for a mapping for the class you specified as type. If both of them do not match it now checks if the class specified as type is available at classpath and has a static valueOf(String) method. Only if all of this conditions are met it will be viewed as a valid mapping.

3.9.5.3. How to use Castor JDO's connection proxies

3.9.5.3.1. Introduction

Castor JDO uses the Jakarta Common's Logging package for output information relevant to the execution of a specific JDO operations to a log file. The information output historically included the SQL statements used by Castor to execute the various persistence operations such as loading or updating domain entities. Unfortunately, the SQL statements logged did not include any information about the parameters being bound to the prepared statements immediately before execution, and hence made it very hard for users of Castor JDO to analyze these in the case of an issue/problem.

To improve this situation, proxy classes for the java.sql.Connection and java.sql.PreparedStatement interfaces have been added, to allow for complete and better JDBC statements to be output to the log files. As this might impose a performance penalty at run-time, we have allowed for this to be turned off completely through the standard Castor property file.

A new property has been added to the Castor property file (castor.properties) to allow configuration of this feature.

3.9.5.3.2. Intended Audience

Anyone who wants to use the new JDBC proxy classes with Castor JDO selectively, i.e. enabling and disabling their use.

The example given describes how to turn the use of the proxy classes on/off.

3.9.5.3.3. Prerequisites

You should have a valid castor.properties file as part of your application.

3.9.5.3.4. Steps

Here is how to proceed.

3.9.5.3.4.1. Enable the use of the JDBC proxy classes

To enable the use of the JDBC proxy classes described above, please add the following section to your castor.properties file.

            
  # True if JDBC proxy classes should be used to enable more detailed logging output of SQL
  # statements; false otherwise (logging of SQL statements will be turned off completely).
  #
  org.exolab.castor.persist.useProxy=true
            

This instructs Castor JDO to use the JDBC proxy classes and to output full information about the SQL statements used at run-time. When disabled, no logging of SQL statements will occur at all.

3.9.6. Cascading

3.9.6.1. How to use cascading operations

3.9.6.1.1. Overview

Up to Castor 1.3.1, users of Castor JDO have been able to automatically store/update or delete objects across relations by issuing ...

        
  Database.setAutostore(true)
        

before going starting a transaction. This feature was useful, indeed, but on a second look its limitation (global definition across all entities) became obvious, especially on big projects. You might want to have cascading operations activated selectively (activated for one object, but not for another). Or even more tricky, you might like to automatically track changes across one relation from a starting object, but but not across another relation from the very same object.

As of Castor 1.3.2, a new cascading attribute has been introduced to the <sql> tag of the JDO mapping file.

3.9.6.1.2. Intended Audience

This and all other cascading documents address people familiar with the basic concepts of mapping domain entities to database tables and defining relations between objects (on database level as well as on object level). But in particular, this document applies to the following user groups:

  1. Everyone who wants to cascade operations across (any type of) object relation(s).

  2. Everyone who now uses Database.setAutoStore(boolean) to have persistence operations cascaded across relations.

[Note]Note

Especially the second user group should change their approach towards using cascading operations, and switch to using the new cascading attribute. As of Castor 1.3.2, the current Database.setAutoStore(boolean) methods will be deprecated, and in the long run, this operations will be removed from the JDO interfaces.

3.9.6.1.3. Prerequisites

You should have a valid mapping file, containing at least two objects, being in relation with each other. For the remainder of this document, we'll be using the following example mapping file as a starting point.

        
<mapping>
  <class name="org.castor.cascading.Author" identity="id">
    <cache-type type="none" />
    <map-to table="OneToOne_Author" />
    <field name="id" type="integer">
      <sql name="id" type="integer" />
    </field>
    <field name="timestamp" type="long">
      <sql name="time_stamp" type="numeric" />
    </field>
    <field name="name" type="string">
      <sql name="name" type="char" />
    </field>
  </class>
  <class name="org.castor.cascading.Book" identity="id">
    <cache-type type="none" />
    <map-to table="OneToOne_Book" />
    <field name="id" type="integer">
      <sql name="id" type="integer" />
    </field>
    <field name="timestamp" type="long">
      <sql name="time_stamp" type="numeric" />
    </field>
    <field name="name" type="string">
      <sql name="name" type="char" />
    </field>
    <field name="author" type="org.castor.cascading.Author">
      <sql name="author_id"/>
    </field>
  </class>
</mapping>
        
3.9.6.1.4. Use of the cascading attribute

In order to activate cascading for create operations for the author relation defined in the mapping file above, you have to add the following attribute to the field mapping of the author property:

	  
<class name="org.castor.cascading.one_to_one.Book" identity="id">
   <cache-type type="none" />
   ...
	
   <field name="author" type="org.castor.cascading.one_to_one.Author">
      <sql name="author_id" cascading="create"/>
   </field>
</class>
        

Remember that the code above adding a cascading attribute with a value of create is only an example. You can define any combination of cascading attributes, delimiting those values by spaces, as shown in the following example:

        
<field name="author" type="org.castor.cascading.one_to_one.Author">
   <sql name="author_id" cascading="create update"/>
</field>
        
3.9.6.1.5. Values for the cascading attribute

In order to achieve an optimal granulation of activating and de-activating functionality, there are 5 possible values, out of which 3 can be activated separately or in any combination.

In general, what you have to keep in mind is that some cascading types do not only affect the the (coincidentally) identically named database operation, but also other persistence operations. For more details please read the following references carefully.

If no cascading attribute is defined, its default value will be none.

3.9.6.1.6. References

3.9.6.2. How to cascade creation

3.9.6.2.1. Overview

Cascading creation allows you to transfer some of the responsibilities of creating objects to Castor JDO. To be more precise: if you enable cascading creation on a relation between two classes, all objects on one end of that relation that have not yet been created will be created when the other end gets persisted. This saves you from manually creating every single object, which is especially useful when dealing with large object graphs that have 1:M (one to many) relations or many objects in a single relationship.

3.9.6.2.2. Enabling cascading creation

To enable cascading creation on a relation, you simply set the cascading attribute of the <sql> field describing the relation to "create" (or "all"):

In other words, the field mapping for the Java property book ...

		
<field name="book" type="myapp.Book" >
  <sql name="book_id" />
</field>
     

becomes

        
<field name="book" type="myapp.Book" >
  <sql name="book_id" cascading="create" />
</field>
     

In case of bidirectional relations, it does matters on which end you enable cascading creation. It is also possible to enable it on both ends.

3.9.6.2.3. Scenarios
3.9.6.2.3.1. db.create()

The most intuitive case is when you explicitly call db.create() on an object that has cascading creation enabled on one or more of his relations. If the objects in those relationships have not yet been created, they will be as part of the create() execution.

Here is a simple example, where the objects Author and Book are in a one-to-one relation (i.e. every Book has exactly one Author):

			
db.begin();

Author author = new Author();
author.setId(1);
author.setName("John Jackson");

Book book = new Book();
book.setId(1);
book.setTitle("My Life");
book.setAuthor(author);

db.create(book);

db.commit();
            

Once the commit operation has successfully completed, both the Author and the Book instance will have been persisted to your data store. To highlight this, lets's have a look at the corresponding database tables before and after the execution of above code fragment.

Before

Table 3.10. Author

idname
(empty table)


Table 3.11. Book

idtitleauthor_id
(empty table)


After

Table 3.12. Author

idname
1"John Jackson"


Table 3.13. Book

idtitleauthor_id
1"My Life"1


3.9.6.2.3.2. db.commit()

Cascading creation also works implicitly: any objects that are on the receiving end of a cascaded relation will be created upon transaction commit, provided they do not exist yet and that the object on the primary end of that relation does. In other words: if you modify a relation property of a loaded object, any new objects that now need to be created will be created.

To demonstrate, let's continue the example from the previous section. We, again, have a Book and an Author, in a one-to-one relation, both already persisted. If we now change the book's author to someone new, any object that is not yet in the database will be persisted automatically. Just call db.commit() after setting the new author, and the new author will be persisted as well.

			
db.begin();

Author author = new Author();
author.setId(2);
author.setName("Bruce Willis");

Book book = db.load(Book.class, 1);
book.setAuthor(author);

db.commit();
        

In terms of unit test assertions, the current state of the author and book instances can be expressed as follows:

            
db.begin();

Book book = db.load(Book.class, 1);
assertNotNull(book);
assertEquals(1, book.getId());

Author author = book.getAuthor();
assertNotNull(author);
assertEquals(2, book.getId());

db.commit();

As above, let's have a look at the corresponding database tables for the entities Author and Book:

Before

Table 3.14. Author

idname
1"John Jackson"


Table 3.15. Book

idtitleauthor_id
1"My Life"1


After

Table 3.16. Author

idname
1"John Jackson"
2"Bruce Willis"


Table 3.17. Book

idtitleauthor_id
1"My Life"2


Please note that we now have two authors stored, and that the book with an id value of '1' now has a foreign key relationship to the author with the id value '2'.

3.9.6.2.3.3. Cascading create and collections

The real benefit of using cascading for object creation shows when dealing with 1:M relations, usually expressed through Java collections in your entity classes.

For the remainder of this secction, we will use the Java classes Department and Employee, which have a 1:M relationship (in other words, every department has one or more employees). On the Java side, this is expressed as the Department having a collection of Employee objects in form of a Java collection. In the database, this will obviously be the other way around, with the emp table referencing the dept table. Every example in this section will use the same database state as a starting point, as shown here:

Table 3.18. dept

idname
23"Accounting"

Table 3.19. emp

idnamedept_id
1"John"23
2"Paul"23
3"Ringo"23

Example 1: Adding objects

			
db.begin();

Employee employee = new Employee();
employee.setId(4);
employee.setName("George");

Department department = db.load(Department.class, 23);
department.getEmployees().add(employee);

db.commit();
        

After

Table 3.20. dept

idname
23"Accounting"

Table 3.21. emp

idnamedept_id
1"John"23
2"Paul"23
3"Ringo"23
4"George"23

Example 2: Removing objects

			
db.begin();

Department department = db.load(Department.class, 23);
department.getEmployees().remove(2);

db.commit();
            

After

Table 3.22. dept

idname
23"Accounting"

Table 3.23. emp

idnamedept_id
1"John"23
2"Paul"23
3"Ringo"NULL

[Note]Note

this of course only works if you allow the employee's foreign key dept_id to be NULL or, alternatively, also delete the Employee when you remove the relationship (either by manually calling db.remove() or TODO)

Example 3: Adding & removing objects

			
db.begin();

Employee e4 = new Employee();
e4.setId(4);
e4.setName("George");

Employee e5 = new Employee();
e5.setId(5);
e5.setName("Joe");

Employee e6 = new Employee();
e6.setId(6);
e6.setName("Jack");

Department dep = db.load(Department.class, 23);
dep.setEmployees(Arrays.asList(e4, e5, e6));

db.commit();
            

Database after:

Table 3.24. dept

idname
23"Accounting"

Table 3.25. emp

idnamedept_id
1"John"NULL
2"Paul"NULL
3"Ringo"NULL
4"George"23
5"Joe"23
6"Jack"23

The note to example 2 also applies here.

3.9.6.3. How to cascade deletion

3.9.6.3.1. Overview

If you enable cascading deletion on a relationship, deleting the object on one end of the relationship (i.e. calling db.remove() on the object) will also delete the object on the other end.

3.9.6.3.2. Enabling cascading deletion

To enable cascading deletion on a relationship you simply set the cascading attribute of the <sql> field describing the relation to "delete" (or "all"):

		
<field name="book" type="myapp.Book" >
  <sql name="book_id" cascading="delete" />
</field>
     

In case of bidirectional relationships, be aware that it matters on which end you enable cascading deletion. It is also possible to enable it on both ends.

3.9.6.3.3. Scenarios

3.9.6.3.3.1. db.remove()

Let's say we have the objects Author and Book and they are in a one-to-one relationship, with every Book having exactly one Author. The database looks like this:

Table 3.26. Author

idname
1"John Jackson"

Table 3.27. Book

idtitleauthor_id
1"My Life"1

Now, since we specified the relationship to cascade deletion, if we remove the book, the author gets removed too (after all, an author without a book isn't really an author).

			
db.begin();

Book b1 = db.load(Book.class, 1);
db.remove(db1);

db.commit();
            

Afterwards, the database predictably looks like this:

Table 3.28. Author

idname
(empty table)

Table 3.29. Book

idtitleauthor_id
(empty table)

Cascading the deletion of objects in to-many relationships works in exactly the same way.

Note: You need to explicitly invoke db.remove() to delete an object. Simply setting a relational property to NULL or removing an item from a collection will not remove the corresponding entity from the database, even with cascading deletion enabled.

3.9.6.4. How to cascade update

3.9.6.4.1. Overview

When working with long transactions, you can cascade the db.update() operation, so that, for example, updating the root of a large object graph causes all connected entities to update as well (provided cascading update is enabled on the particular relationships, of course).

3.9.6.4.2. Enabling cascading update

To enable cascading update on a relationship you simply set the cascading attribute of the <sql> field describing the relation to "update" (or "all"):

		
<field name="book" type="myapp.Book" >
  <sql name="book_id" cascading="update" />
</field>
     

In case of bidirectional relationships, be aware that it matters on which end you enable cascading update. It is also possible to enable it on both ends.

3.9.6.4.3. Scenarios

3.9.6.4.3.1. db.update()

Let's say we have the objects Author and Book and they are in a one-to-one relationship, with every Book having exactly one Author. The database looks like this:

Table 3.30. Author

idname
1"John Jackson"

Table 3.31. Book

idtitleauthor_id
1"My Life"1

Now let's change the book's title. Note that we never directly load the book and that the change happens outside of any transaction:

			
db.begin();
Author a1 = db.load(Author.class, 1);
db.commit();

a1.getBook().setName("My Fantastic Life");

db.begin();
db.update(a1);
db.commit();
            

Afterwards, the database looks like this:

Table 3.32. Author

idname
1"John Jackson"

Table 3.33. Book

idtitleauthor_id
1"My Fantastic Life"1

3.9.6.4.4. Limitations
  • To-many relationships are currently not supported (except many-to-one).

  • As it is now, enabling cascading update will cause db.update() to also create any entities that have not yet been persisted. (In other words: setting cascading to "update" has the same effect as setting it to "update create", but only when invoking db.update().)

3.9.7. Caches

3.9.7.1. How to use Castor in a J2EE cluster

3.9.7.1.1. Introduction

With release 0.9.9, several cache providers have been added that are distributed caches per se or can be configured to operate in such a mode. This effectively allows Castor JDO to be used in a clustered J2EE (multi-JVM) environment, where Castor JDO runs on each of the cluster instances, and where cache state is automatically synchronized between these instances.

In such an environment, Castor JDO will make use of the underlying cache provider to replicate/distribute the content of a specific cache between the various JDOManager instances. Through the distribution mechanism of the cache provider, a client of a Castor JDO instance on one JVM will see any updates made to domain objects performed against any other JVM/JDO instance.

3.9.7.1.2. Intended Audience

Anyone who wants to use Castor JDO in a J2EE cluster.

The example given describes the use of the Coherence cache provider to enable distributed caching.

3.9.7.1.3. Prerequisites

You should have a valid class mapping for a Java class, similar to the following one:

        
<mapping>
    <class name="com.xyz.MyOtherObject" identity="id">
        <field name="id" type="integer">
               ...
        </field>
        <field name="description" type="string">
               ...
        </field>
    </class>
</mapping>
        
3.9.7.1.4. Steps

Here is how to proceed.

3.9.7.1.4.1. Add <cache-type> element to class mapping

Add a <cache-type> element as shown below, specifying the cache provider to use in the 'type' attribute.

            
<mapping>
    <class name="com.xyz.MyOtherObject" identity="id">
        <cache-type type="coherence"/>
        <field name="id" type="integer">
               ...
        </field>
        <field name="description" type="string">
               ...
        </field>
    </class>
</mapping>
            

This instructs Castor JDO to use the 'coherence' cache provider for objects of type com.xyz.MyOtherObject. It is the cache provider that is responsible to distribute any changes to the cache state to all other Castor JDO instances within the same cluster.

3.9.7.1.4.2. Add Coherence JARs to CLASSPATH

Add the Coherence JARs to the class path of your e.g. web application by putting the JARs into the WEB-INF/lib folder of your web application.

3.9.8. Connection pooling

3.9.8.1. How to use Jakarta's DBCP for connection pooling

3.9.8.1.1. Introduction

This HOW-TO provide users with instructions on hot to configure Castor JDO so that Apache Jakarta's DBCP package is used as a connection pool.

3.9.8.1.2. Intended audience

Anyone who wants to use DBCP as connection pool mechanism with Castor JDO.

3.9.8.1.3. Steps

Below are defined the steps to configure Castor JDO to use DBCP's BasicDataSource for connection pooling.

3.9.8.1.3.1. Configuration

To use a DBCP BasicDataSource with Castor JDO, please provide the following <data-source> entry in the jdo-conf.xml file.

            
  <data-source class-name="org.apache.commons.dbcp.BasicDataSource">
    <param name="driver-class-name" value="com.mysql.jdbc.Driver" />
    <param name="username" value="test" />
    <param name="password" value="test" />
    <param name="url" value="jdbc:mysql://localhost/test" />
    <param name="min-active" value="10" />
    <param name="max-active" value="40" />
  </data-source>
            

Above example makes use of the mySQL JDBC driver to establish a connection pool to a mySQL instance named 'test' running on the same machine as Castor itself. The pool initially holds 10 connections, but is configured to allow a maximum of 40 active connections at the same time.

3.9.8.1.4. References

3.9.9. Use of Castor in J2EE applications

3.9.9.1. How to use Castor with(in) distributed J2EE transactions

3.9.9.1.1. Overview

J2EE applications depend on the J2EE container (hosting Servlet, EJB, etc) to configure a database connection (as well as other resource managers) and use JNDI to look it up. This model allows the application deployer to configure the database properties from a central place, and gives the J2EE container the ability to manage distributed transactions across multiple data sources.

This HOW-TO shows how to seamlessly use Castor JDO in such a managed environment, and how to make Castor participate in a distributed transaction.

3.9.9.1.2. Intended audience

Anyone who wants to use Castor JDO with(in) distributed J2EE transactions.

3.9.9.1.3. Steps

The following sections highlight the steps necessary to use Castor JDO seamlessly in such a (managed) environment, and how to make Castor participate in a distributed transaction.

3.9.9.1.3.1. Make Castor participate in a J2EE transaction

The following code fragment shows how to use JNDI to lookup a database and how to use a JTA UserTransaction instance to manage the J2EE (aka distributed) transaction:

// Lookup databse in JNDI
Context ctx = new InitialContext();
Database db = (Database) ctx.lookup( "java:comp/env/jdo/mydb" );

// Begin a transaction
UserTransaction ut = (UserTransaction) ctx.lookup( "java:comp/UserTransaction" );
ut.begin();
// Do something
. . .
// Commit the transaction, close database
ut.commit();
db.close();
            
3.9.9.1.3.2. Make Castor participate in container-managed J2EE transaction

If the transaction is managed by the container, a common case with EJB beans and in particular entity beans, there is no need to begin/commit the transaction explicitly. Instead the application server takes care of enlisting the database used by Castor JDO to insert domain entities into a database in the ongoing transaction and commiting/rolling back at the relevant time.

The following code snippet relies on the container to manage the transaction.

InitialContext  ctx;
UserTransaction ut;
Database        db;

// Lookup databse in JNDI
ctx = new InitialContext();
db = (Database) ctx.lookup( "java:comp/env/jdo/mydb" );

// Do something
. . .
// Close the database
db.close();
            

As transaction enregistration is dealt with at the J2EE container, it is not necessary anymore to obtain a UserTransaction and start/commit the transaction manually.

3.9.9.1.3.3. Resource enlisting

Instead of constructing required resources directly, a typical J2EE application uses the JNDI API to look up resources from centrally managed place such as a naming and directory service. In such an environment, Castor JDO takes on the role of a managed resource as well. It follows that, instead of constructing a org.exolab.castor.jdo.JDOManager directly, a typical J2EE application should use JNDI to look it up.

We thus recommend enlisting the JDOManager object under the java:comp/env/jdo namespace, compatible with the convention for listing JDBC resources.

3.9.9.1.4. Tips
  • When using Castor JDO in a J2EE environment, Castor allows you to enable a special Database instance pooling support. This option is configured via the org.exolab.castor.jdo.JDOManager.setDatabasePooling(boolean) method; by default, it is turned off. This option only affects JDOManager if J2EE transactions are used and if a transaction is associated with the thread that calls {@link #getDatabase}.

    If database pooling is enabled, JDOManager will first search in this special pool to see if there is already a org.exolab.castor.jdo.Database instance for the current transaction. If found, it returns this org.exolab.castor.jdo.Database instance; if not, it creates a new one, associates it will the transaction and returns the newly created org.exolab.castor.jdo.Database instance.

    Please make sure that you call this method before calling {@link #getDatabase}.

3.9.9.1.5. References

3.9.10. Database specifica

3.9.10.1.  How to connect to a Apache Derby instance in network server mode

3.10. Castor JDO - Tips & Tricks

3.10.1. Logging and Tracing

When developing using Castor, we recommend that you use the various setLogWriter methods to get detailed information and error messages.

Using a logger with org.exolab.castor.mapping.Mapping will provide detailed information about mapping decisions made by Castor and will show the SQL statements being used.

Using a logger with org.exolab.castor.jdo.JDO will provide trace messages that show when Castor is loading, storing, creating and deleting objects. All database operations will appear in the log; if an object is retrieved from the cache or is not modified, there will be no trace of load/store operations.

Using a logger with org.exolab.castor.xml.Unmarshaller will provide trace messages that show conflicts between the XML document and loaded objects.

A simple trace logger can be obtained from org.exolab.castor.util.Logger. This logger uses the standard output stream, but prefixes each line with a short message that indicates who generated it. It can also print the time and date of each message. Since logging is used for warning messages and simple tracing, Castor does not require a sophisticated logging mechanism.

Interested in integratating Castor's logging with Log4J? Then see this question in the JDO FAQ.

3.10.2. Access Mode

If you are using JDO objects with the default access mode ('shared') and too many transactions abort when attempting to commit due to locks, you should consider upgrading to an 'exclusive' mode. When two transactions attempt to modify and store the same object at the same time, lock issues arise. Upgrading to an 'exclusive' mode will prevent concurrent transactions from modifying the same object at once.

If too many transactions abort when attempting to commit due to dirty checking, you should consider upgrading to a 'locked' mode. When external database access modifies the same objects being managed by Castor, Castor will complain that objects are dirty. Upgrading to a 'locked' mode will prevent concurrent update.

Be advised that 'exclusive' mode introduces lock contention in the Castor persistence engine, and 'locked' mode adds lock contention in the database. Lock contention has the effect of slowing down the application and consuming more CPU.

If too many transaction abort due to deadlock detection, consider modifying the application logic. Deadlock occurs when two transactions attempt to access the same objects but not in the same order.

3.10.3. Inheritence

There are two types of inheritence: Java inheritence and relational inheritence.

With Java inheritence, two objects extend the same base class and map to two different tables. The mapping file requires two different mappings for each of the objects. For example, if Employee and Customer both extend Person, but Employee maps to the table emp and Person to the table person, the mapping file should map both of these objects separately.

With relation inheritence, one table provides the base information and another table provides additional information using the same primary keys in both. Use the extends attribute to specify such inheritence in the mapping file. For example, if Computer extends Product and the table comp provides computer-specific columns in addition to product columns in prod, the mapping for Computer will specify Product as the extended class.

When a class just extends a generic base class or implements an interface, this form of inheritence is not reflected in the mapping file.

3.10.4. Views of Same Object

It is possible to use different objects and mappings to the same tables. For example, it is possible to define a subset of a table and load only several of the columns, or load an object without its relations.

To determine the first and last names and e-mail address of an object without loading the entire person object, create a subset class and map that class to a portion of the table. Such a class cannot be used to create a new person, but can be used to delete or modify the person's details.

Use partial views with care. If you attempt to load the same record using a full object and a subset object, changes to one of these objects are allowed, but changes to both will result in a conflict and roll back the transaction. Locking will not work properly between full and subset objects. Also note, that each of the two objects will have its own cache, so if you update the first object and load the second, you may obtain old values. To avoid this situation you may turn off the cache for both objects:

             <class ... >
                <cache-type type="none">
                ...
             </class>
         

3.10.5. Upgrading Locks

When an object is loaded into memory in the default access mode ('shared'), a read lock is acquired on that object. When the transaction commits, if there are changes to the object a write lock will be required. There is no guarantee that a write lock can be acquired, e.g. if another transaction attempts to change the same object at the same time.

To assure concurrent access, you may upgrade the object's lock by calling the org.exolab.castor.jdo.Database.lock(java.lang.Object) method. This method will either acquire a write lock or return if a timeout elapses and the lock could not be acquired. Once a lock has been acquired, no other transaction can attempt to read the object until the current transaction completes.

Object locking is recommended only if concurrent access results in conflicts and aborted transactions. Generally locks results in lock contention which has an effect on performance.

3.10.6. NoClassDefFoundError

Check your CLASSPATH, check it often, there is no reason not to!

3.10.7. Create method

Castor requires that classes have a public, no-argument constructor in order to provide the ability to marshal & unmarshal objects of that type.

create-method is an optional attribute to the <field> mapping element that can be used to overcome this restriction in cases where you have an existing object model that consists of, say, singleton classes where public, no-argument constructors must not be present by definition.

Assume for example that a class "A" that you want to be able to unmarshal uses a singleton class as one of its properties. When attempting to unmarshal class "A", you should get an exception because the singleton property has no public no-arg constructor. Assuming that a reference to the singleton can be obtained via a static getInstance() method, you can add a "create method" to class A like this:

         
            public MySingleton getSingletonProperty()
            {
               return MySingleton.getInstance();
            }
      

and in the mapping file for class A, you can define the singleton property like this:

         
            <field name="mySingletonProperty"
                  type="com.u2d.MySingleton"
                  create-method="getSingletonProperty">
               <bind-xml name="my-singleton-property" node="element" />
            </field>
      

This illustrates how the create-method attribute is quite a useful mechanism for dealing with exceptional situations where you might want to take advantage of marshaling even when some classes do not have no-argument public constructors.

[Note]Note

As of this writing, the specified create-method must exist as a method in the current class (i.e. the class being described by the current <class> element). In the future it may be possible to use external static factory methods.

3.11. Castor JDO - Advanced features

Authors

Werner Guttmann ()

3.11.1. Introduction

As explained at the introduction to Castor JDO, Castor has support for many advanced features such as caching, depend relations, inheritance, polymorphism, etc. The below sections detail these features, as their understanding is required to use Castor JDO in a performant and secure way.

3.11.2. Caching

All information related to caching and related concepts supported by Castor has been consolidated into one place, and is available here.

3.11.3. Dependent and related relationships

Castor distinguishes the relationship of two objects as dependent or related, and maintains the life cycle independently for the two types of relationships. Starting from Castor 0.9, the developer can explicitly define a dependent relationship in the mapping file.

When using independent relations, related objects' life cycle is independent of each other, meaning that they have to be created, removed and updated (for long transaction) independently.

When using dependent relations, one data object class must be declared as depends on one other data object class in the mapping file, and such an object is called a dependent data object class. A data object class without depends declared in the mapping is called a master object. A master object can be depended upon by zero or more dependent data object class.

As of Castor 0.9, a dependent object class can be related to other master data object classes including extended classes, but cannot depend on more than one master class.

If an object class declared as depends on another class, it may not be created, removed or updated separately. Attempting to create, remove or update a dependent object will result in ObjectNotPersistcapableException. Note that Castor doesn't allow a dependent object instance to change its master object instance during a transaction. Each dependent object can have only one master object. Both dependent and master objects must have identities, and may or may not make use of key-generators.

Here is the DTD for declaring dependent object:

<!ATTLIST class     name ID  #REQUIRED
          extends   IDREF    #IMPLIED
          depends   IDREF    #IMPLIED
          identity  CDATA   #IMPLIED
          access    ( read-only | shared | exclusive | db-locked )  "shared"
          key-generator   IDREF #IMPLIED
            

For example,

<mapping>
    <class name="com.xyz.MyDependentObject"
        depends="com.xyz.MyObject">
        ...
    </class>
</mapping>
            

declares the data object class com.xyz.MyDependentObject as a dependent upon class com.xyz.MyObject.

3.11.4. Different cardinalities of relationship

Castor supports different cardinalities of relationship, namely one-to-one, one-to-many, and many-to-many. Many-to-many relationship must be related rather than dependent, because each dependent object can have only one master object.

Many-to-many requires a separate table for storing the relations between two types of objects. Many-to-many introduces two attributes, namely many-key and many-table that reside in the <sql> element which is a sub-element of the <field> element. For all many-to-many relations, a many-table must be specified. If the column name of the primary key of the class is different from the foreign keys columns of the class in the relation tables, then the relation table columns can be specified using the many-key attributes. Similarly, if the column name of the primary key of the related class is different from the foreign key columns of the related class, then the relation table columns can be specified using the name attribute.

The many-table is used to store relations in a separate table

<mapping>
    <class>
        <field>
            <sql many-key="#OPTIONAL" name="#OPTIONAL"
                 many-table="#REQURIED">
        </field>
    </class>
</mapping>

So, for example, if the SQL table is the following,

Table 3.34. employee_table

idnamesalary
1482Smith, Bob$123,456
628Lee, John$43,210
1926Arnold, Pascal$24,680


Table 3.35. department_table

idnamecomment
3Accounting 
7EngineeringThe very important department. :-)


Table 3.36. employee_department

e_idd_id
........


Then, the mapping for employee data object would look like this

<mapping>
    <class name="com.xyz.Employee" identity="id">
        <map-to table="employee_table"/>
            <field name="id" type="integer">
                <sql name="id"/>
            </field>
            <field>
                <sql many-table="employee_department"
                     many-key="e_id" name="d_id"/>
            </field>
            <field name="salary">
                <sql name="salary" type="integer">
            </field>
    </class>
</mapping>

3.11.5. Lazy Loading

As of release 0.9.6, Castor has full support for lazy loading object instances referenced as part of all relation types currently supported:

  • 1:1 relations

  • 1:m relations

  • M:N relations.

3.11.5.1. 1:1 relations

As of release 0.9.6, Castor supports lazy-loading of 1:1 relations. Imagine the following class mapping:

<mapping>
    <class name="com.xzy.Department">
       ...
       <field "employee" type="com.xyz.Employee" lazy="true" />
       ...
    </class>
</mapping>
            

Per definition, when an instance of Department is loaded through e.g. Database.load(), Castor will not (pre-)load the Employee instance referenced (as such reducing the size of the initial query as well as the size of the result set returned). Only when the Emplyoee instance is accessed through Department.getEmployee(), Castor will load the actual object into memory from the persistence store.

This means that if the Employee instance is not accessed at all, not only will the initial query to load the Department object have had its complexity reduced, but no performance penalty will be incurred for the additional access to the persistence store either.

3.11.5.2. 1:M and M:N relations

The elements in the collection are only loaded when the application asks for the object from the collection, using, for example, iterator.next(). The iterator in Castor's lazy collection is optimized to return a loaded object first.

In the mapping file, lazy loading is specified in the element of the collection's <field>, for example,

<mapping>
    <class name="com.xzy.Department">
       ...
        <field name="employee" type="com.xyz.Employee" lazy="true"
               collection="collection"/>
    </class>
</mapping>

declares that the collection of type Employee in a Department is lazy loaded.

If lazy loading is specified for a field of a class, Castor will set the field with a special collection which contains only the identities of the objects. Because of that, it requires the data object to have the method setDepartment( Collection department) in the data object class which was not required in previous versions.

[Note]Note

Please note that currently only the java.util.Collection type is supported.

3.11.6. Multiple columns primary keys

The support of multiple column primary keys (also called compound primary keys) was another major enhancement added into Castor 0.9. Specifying multiple column primary keys is simple and straightforward, in the mapping file,

<mapping>
    <class name="com.xyz.MyObject" identity="firstName lastName">
        <field name="firstName" type="string">
           <sql name="fname"/>
        </field>
        <field name="lastName" type="string">
           <sql name="lname"/>
        </field>
           ...
    </class>
</mapping>

Multiple column primary keys work with both master and dependent objects, all cardinalities of relationship, including one-to-one, one-to-many and many-to-many, as well as lazy loading.

However, multiple column primary keys should only be used to adhere to an existing database design, not when designing a new database. In general, it is not a good idea to use an identity or identities which can be modified by the user, or which contain application-visible data. For example, if the system allows the user name to be changed, using user name as identity is highly discouraged, as this practice can require a major data migration to a new schema to update all foreign keys to adhere to a new primary key structure, should the user name no longer be adequate as a primary key. It should be noted that Castor doesn't support identity change, as specified in the ODMG 3.0 specification. So, primary keys changes are almost certainly a large trade off between data integrity and performance. Well chosen primary keys are usually single (not multiple) column numeric or character fields for the reasons outlined above, as well as performance, as joining operations are faster for single column primary keys.

3.11.7. Callback interface for persistent operations

For the various persistence operations as available through the org.exolab.castor.jdo.Database interface, Castor JDO provides a callback interface that informs the implementing class on events taking place related to selected persistence operations.

Once your entity class implements the org.exolab.castor.jdo.Persistence interface, you'll have to provide implementations for the following methods (with their respective semantics described next to them):

Table 3.37. Interface methods

MethodDescription
jdoAfterCreate() Indicates that an object has been created in persistent storage.
jdoAfterRemove() Indicates that an object has been removed from persistent storage.
jdoBeforeCreate() Indicates that an object is to be created in persistent storage.
jdoBeforeRemove() Indicates that an object is to be removed from persistent storage.
jdoLoad() Indicates that the object has been loaded from persistent storage.
jdoPersistent(Database) Sets the database to which this object belongs when this object becomes persistent.
jdoStore()Indicates that an object is to be stored in persistent storage.
jdoTransient()Indicates the object is now transient.
jdoUpdate() Indicates that an object has been included to the current transaction by means of db.update() method (in other words, at the end of a "long" transaction).

3.12. Running the self-executable Castor JDO examples

Authors

Werner Guttmann ()

As of release 1.0M3, the Castor JDO examples have been packaged in a new way and are available for download at the download page. In the following sections, we explain the steps required to unpack this new archive, and how to execute the tests.

3.12.1. Download the castor-$RELEASE-examples.zip archive

In order to be able to run the new Castor JDO examples, please download the new castor-${RELEASE}-examples.zip from the download page and put it into some location on your computer.

3.12.2. Unpack the ZIP file

To unpack the ZIP file downloaded, issue one of the following commands:

unzip castor-1.1M2-examples.zip

or

jar xvf castor-1.1M2-examples.zip

You can now run the examples using the directions provided in the next section.

3.12.3. Running the Castor JDO samples

In the directory where you have unpacked the ZIP file you'll find a castor-1.1M2-examples.jar. In addition, you'll find a directory named lib where you'll find several JAR files required to run the samples.

To execute the samples, issue the following command:

java -jar castor-1.1M2-examples.jar

... et voila ! You'll see various lines of logging output flashing by whilst the Castor JDO samples are executing against a database.

3.12.4. What happens

The Castor JDO sample will test persistence between a set of Java classes (Product, ProductGroup, ProductDetail, etc.) and perform this code against an Apache Derby instance as RDBMS. For this purpose, the sample code will start an embedded Derby instance on the fly, create the required tables, and then continue to execute several code fragments using various Castor JDO artifacts (JDOManager, Database, etc.) against this embedded database.

In other words, everything is self-contained and there is no need to install a database, create a database schema, or create database tables. Everything required for the tests is initiated and set up from within the sample code.

3.12.5. Hints

  • If it is not set yet, you might have to set the JAVA_HOME environment variable, and add the $JAVA_HOME/bin directory to your path.