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.
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.
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.
The sample domain objects used in here basically define a
Catalogue
,
which is a collection of Product
s.
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>
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.
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>
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.
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:
Statically load the JDO configuration file through one of the
loadConfiguration() methods, e.g.
org.exolab.castor.jdo.JDOManager.loadConfiguration(java.lang.String)
.
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.
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();
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.
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).
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();
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();
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();
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>
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.
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 name | RDBMS |
---|---|
db2 | DB/2 |
derby | Derby |
generic | Generic JDBC support |
hsql | Hypersonic SQL |
informix | Informix |
instantdb | InstantDB |
interbase | Interbase |
mysql | MySQL |
oracle | Oracle 7 - Oracle 9i |
postgresql | PostgreSQL 7.1 |
sapdb | SAP DB / MaxDB |
sql-server | Microsoft SQL Server |
sybase | Sybase 11 |
pointbase | Borland Pointbase |
progress | Progress RDBMS |
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>
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.
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" />
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
Name | Description |
---|---|
jndi | TM looked up in the JNDI ENC |
websphere | IBM WebSphere 4 and previous releases |
websphere5 | IBM WebSphere 5 |
websphere51 | IBM WebSphere 5.1 |
jotm | JOTM |
atomikos | Atomikos |
In addition to specifying the transaction manager name, it is possible to pass arbitrary name/value pairs to the transaction manager instance.
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>
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>
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.
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™.
... <!-- 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> ...
... <!-- 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> ...
... <!-- 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> ...
... <!-- 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> ...
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();
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>
The Castor type mechanism assures proper conversion between Java types and external types.
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.
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.
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 name | Primitive type? | Java Class |
---|---|---|
big-decimal | N | java.math.BigDecimal |
boolean | Y | java.lang.Boolean.TYPE |
byte | Y | java.lang.Byte.TYPE |
bytes | N | byte[] |
char | Y | java.lang.Character.TYPE |
chars | N | char[] |
clob | N | java.sql.Clob |
date | N | java.util.Date |
double | Y | java.lang.Double.TYPE |
float | Y | java.lang.Float.TYPE |
integer | Y | java.lang.Integer.TYPE |
locale | N | java.util.Locale |
long | Y | java.lang.Long.TYPE |
other | N | java.lang.Object |
short | Y | java.lang.Short.TYPE |
string | N | java.lang.String |
strings | N | String[] |
stream | N | java.io.InputStream |
In addition, support for the following Castor-internal field types has been added:
Table 3.4. Castor-internal field types
short name | Primitive type? | Java Class |
---|---|---|
duration | N | org.exolab.castor.types.Duration |
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
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 Type | Java Type |
---|---|
bigint | java.lang.Long |
binary | byte[] |
bit | java.lang.Boolean |
blob | java.io.InputStream |
char | java.lang.String |
clob | java.sql.Clob |
decimal | java.math.BigDecimal |
double | java.lang.Double |
float | java.lang.Double |
integer | java.lang.Integer |
longvarbinary | byte[] |
longvarchar | java.lang.String |
numeric | java.math.BigDecimal |
real | java.lang.Float |
smallint | java.lang.Short |
time | java.sql.Time |
timestamp | java.sql.Timestamp |
tinyint | java.lang.Byte |
varbinary | byte[] |
varchar | java.lang.String |
other | java.lang.Object |
javaobject | java.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.
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,y | yyyy | year |
M | MM | month in year |
D,d | dd | day in month |
h,H | HH | hour in day (0~23) |
m | mm | minute in hour |
s | ss | second in minute |
S | SSS | millisecond |
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>
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.
Release 1.0 M3:
Added collection type 'iterator'.
Added collection type 'enumerate'.
Added additional syntax for specifying the identity of a class.
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.
<!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>
<!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.
<!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.
<!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:
|
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:
|
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 | |
---|---|
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.
<!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.
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.
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.
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.
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:
Download the full SVN snapshot and look into the src/tests/jdo directory.
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).
You should initialize the Collection yourself:
private Collection _children = new ArrayList(); public Collection getChildren() { return _children; }
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.
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.
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.
Probably castor.jar file is in jre/lib/ext directory. In this case you should call:
jdo.setClassLoader(getClass().getClassLoader());
before jdo.getDatabase().
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.
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.
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);
Your JDBC driver is not JDBC 2.0 compliant, upgrade it or find another one.
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.
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.
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.
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.
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>
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'.
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).
Yes. It is available from the Castor website: Advanced JDO —> OQL
We are currently working on Phase 3.
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
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.
No, Castor OQL doesn't support struct. For example, the following query CANNOT be done:
select c.name, c.age from Client c
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%" );
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 )
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.
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.
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(); } // ... }
Yes, as long as the deleted object is the same instance as the one being recreated.
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.
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.
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.
Yes, starting from PostgreSQL 7.1, where outer joins support has been added.
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.
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.
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.
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.
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.
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.
Please see this message from the mailing list. It includes an adapter class that will provide this functionality. (Thanks John!)
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
.
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.
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.
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.
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.
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; }
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.
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.
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();
This document provides object mapping examples and the corresponding Java objects and DDL for the database table.
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 ) { ... } }
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 );
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=?
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 */
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>
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>
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>
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>
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
):
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'.
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.
Download and install JDK 1.5 or newer
Download and install Eclipse 3.x
Install the latest Subclipse eclipse plugin
Optionally install the latest CheckStyle eclipse plugin
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
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.
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.
To execute tests in eclipse, go and right click on
cpa/src/test/java
source folder and select
"Run As -> JUnit Test"
.
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).
Anyone who wants to run the CTF test suite. This document outlines the basic steps to get people unfamiliar with this area started.
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.
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.
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.
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 | |
---|---|
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). |
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"
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.
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.
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.
Path to an alternate configuration file.
Name of the database configuration.
Name of the transaction manager configuration.
Force execution of excluded tests (true/false).
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.
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.
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.
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.
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 ...
An initial question is posted.
An initial reply is posted with some follow-up questions, e.g. request for code fragments, mapping files, etc.
One or more code fragments, mapping files, etc. are being posted.
etc.
This document will establish guidelines with regards to step 3) above.
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 | |
---|---|
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.. |
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 | |
---|---|
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
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 |
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 |
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.
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.
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:
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.
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
".
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.
Here is how to proceed.
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
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.
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.
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
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?
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.
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.
The UNIX manual pages 'man diff' and 'man patch'.
SVN Home - many useful SVN related items
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.
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 ) { ... } }
Here is how to proceed.
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();
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();
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();
The following RDBMS fully/partially support LIMIT/OFFSET clauses.
Table 3.9. Support for LIMIT in RDBMS
RDBMS | LIMIT | OFFSET |
---|---|---|
postgreSQL | Yes | Yes |
mySQL | Yes | Yes |
Oracle 1) 2) | Yes | Yes |
HSQL | Yes | Yes |
MS SQL | Yes | - |
DB2 | Yes | - |
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.
In the case a RDBMS does not support LIMIT/OFFSET clauses, a
SyntaxNotSupportedException
will be thrown.
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.
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>
Here is how to proceed.
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.
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.
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.
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; } }
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>
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.
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.
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.
You should have a valid castor.properties
file as
part of your application.
Here is how to proceed.
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.
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.
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:
Everyone who wants to cascade operations across (any type of) object relation(s).
Everyone who now uses
Database.setAutoStore(boolean)
to have
persistence operations cascaded across relations.
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
|
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>
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>
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.
create: details on create operation
delete: details on delete operation
update: details on update operation
none: cascading operations are disabled.
all: Using the value
all
, you are providing a shortcut specifying
that all three basic operations should be defined at the same
moment. This basically equals to a value of
'create delete update'
.
If no cascading attribute is defined, its default value will be
none
.
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.
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.
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
After
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
After
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'.
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:
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
Example 2: Removing objects
db.begin();
Department department = db.load(Department.class, 23);
department.getEmployees().remove(2);
db.commit();
After
Note | |
---|---|
this of course only works if you allow the employee's foreign
key dept_id to be
|
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:
The note to example 2 also applies here.
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.
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.
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:
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:
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.
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).
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.
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:
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:
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().)
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.
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.
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>
Here is how to proceed.
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.
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.
Anyone who wants to use DBCP as connection pool mechanism with Castor JDO.
Below are defined the steps to configure
Castor JDO to use DBCP's
BasicDataSource
for connection pooling.
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.
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.
Anyone who wants to use Castor JDO with(in) distributed J2EE transactions.
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.
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();
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.
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.
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}.
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.
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.
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.
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>
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.
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 | |
---|---|
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 |
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.
All information related to caching and related concepts supported by Castor has been consolidated into one place, and is available here.
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
.
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
id | name | salary |
---|---|---|
1482 | Smith, Bob | $123,456 |
628 | Lee, John | $43,210 |
1926 | Arnold, Pascal | $24,680 |
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>
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.
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.
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 | |
---|---|
Please note that currently only the java.util.Collection type is supported. |
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.
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
Method | Description |
---|---|
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). |
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.
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.
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.
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.
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.