Osage

Persistence Plus XML

SourceForge Logo

What is Osage?

Overview

Plus XML

Why this tool?

Requirements

Data Types

Usage

Saving and Deleting

Initializing

Configuring Connections

Key Generation

Editing Class Maps

Generating Classes

Generating Maps

Generating DDL

Connection Pooling

Contributing

MySQL and Text Datatype

Acknowledgements

Todo


What is Osage?

Osage Persistence Plus XML features JDBC-based object-relational mapping that allows experienced Java developers to quickly implement database access in their applications. It generates SQL for retreiving, saving, and deleting objects. With the mapping language, you can describe relations, so the system will handle dependent objects. With configuration, the system will generate keys for new objects. It maintains relations for dependent objects. It implements the criteria, mapping, and SQL statement classes described by Scott Ambler in his white paper The Design of a Robust Persistence Layer for Relational Databases.

Overview

With Osage, objects which represent data from a relational database may be easily saved and retrieved. Osage provides a mapping of objects to a JDBC relational database. With this approach, minor changes to the relational schema do not affect the object-oriented code. That object-oriented code is not hardwired to the schema of the relational database is one of the advantages of this approach. Another advantage is that the application will have significantly less code becaue much of the SQL code which would be statically embedded in the application is generated. In most cases, less code equals fewer bugs. Another benefit is that you do not need to code for a particular database vendor's syntax. The implementation for syntax differences is in an Osage interface. The disadvantage is that there is a performance impact to the applications, a minor one if the persistence layer is built well, but there is still an impact. For a complete discussion of the advantages of using an object-relational mapping system, please read the aforementioned white paper by Scott Ambler.

Plus XML

It's easy to generate XML with Osage. This is because a simple data model is used in Osage. The type of each data member of a data class is one of three. These are simple ones which may be represented as columns in a relational database, classes which may contain the three types, or collections of classes which hold these types. The distribution includes XMLTest.java, a sample of producing XML with Osage. Osage can be used for the reverse of this; that is, extracting the data objects from an XML stream. See TestParser.java in the distribution. No pre-compilation of files is needed to write objects or read objects from XML.

Why this tool?

Some advantages of Osage are small footprint, low-level access, ease of programming with criteria objects, and built-in support of XML. It does not have some enterprise features such as locking, and modification detection. Because of this, it is smaller than some systems. If you are more interested in enterprise features, I recommend Castor. When you need to generate a list which includes information from hundreds of records, you don't want to create objects. Osage allows low-level access while still allowing use of the mapping system. The criteria objects are very easy to use. Please see the examples. Osage generates XML directly from a result set using the mapping. This is quicker than using objects. It also includes a parser for restoring or creating objects from an XML stream. Soon, it should support updates to the DBMS directly from XML, skipping objects.

Requirements

By default, transactions are used for all interactions with the database. With configuration, Osage can be used with databases that do not support transactions. This is all or none. Either all interactions use transactions or none use them. In addition to the generic interface, there are database specific ones for Interbase, MySQL, Oracle, PostgreSQL, SQL Server, and Sybase. In addition to these databases, Osage has been used with HypersonicSQL and InstantDB. Osage should work on any database which has a JDBC driver. For many situations, the driver must support multiple open statements for a single connection. To use Osage with a database which doesn't support transactions, you must replace the default DatabaseImpl. This can be accomplished with configuration. The example configuration follows below.

<database>
<implement>net.sourceforge.osage.engine.DBNoTransImpl</implement>
</database>

JDK 1.2 is required.

Data Types

Osage maps SQL data types as shown below.

java.sql.TypesJava type
bitjava.lang.Boolean
tinyintjava.lang.Byte
smallintjava.lang.Short
integerjava.lang.Integer
bigintjava.lang.Long
floatjava.lang.Double
doublejava.lang.Double
realjava.lang.Float
numericjava.math.BigDecimal
decimaljava.math.BigDecimal
charjava.lang.String
varcharjava.lang.String
longvarcharjava.lang.String
datejava.sql.Date
timejava.sql.Time
timestampjava.sql.Timestamp
binarybyte[]
varbinarybyte[]
longvarbinarybyte[]

Currently, there's no support for storing character or binary streams.

Usage

Before proceding, you should read Scott Ambler's paper.

Consider a database that consists of two tables. One for products and the other for product groups.

Product can be described for Osage as shown below.

Java
Attribute Name
Database
Column Name

Type
ididinteger
namenamevarchar
groupIdgroup_idinteger
pricepricenumeric

Code snipped from the Product class generated by Osage for the attributes of Product is shown below.

public static String FOR_NAME = "yourapp.db.Product";

public static String NAME = "name";
public static String GROUP_ID = "groupId";
public static String PRICE = "price";
public static String ID = "id";

private String	name;
private int	groupId;
private double	price;
private int	id;

public String getName() { return name; }

public int getGroupId() { return groupId; }

public double getPrice() { return price; }

public int getId() { return id; }

public void setName(String name) {
    this.name = name;
}

public void setGroupId(int groupId) {
    this.groupId = groupId;
}

public void setPrice(double price) {
    this.price = price;
}

public void setId(int id) {
    this.id = id;
}

Assume that product has the following database definition:

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

Product group is described for Osage as below.

Java
Attribute Name
Database
Column Name

Type
ididinteger
namenamevarchar

The database definition for product group is below.

create table prod_group
{
id integer,
name varchar,
};

With Osage, XML is used for the external representation of maps for classes such as Product and ProductCategory. XML for the mapping of Product is below.

<class name="yourapp.db.Product">
<map-to database="some_database"/>
<field name="id" type="integer">
<sql primary-key="True"  table="prod"/>
</field>
<field name="name" type="string">
<sql table="prod"/>
</field>
<field name="groupId" type="integer">
<sql name="group_id" table="prod"/>
</field>
<field name="price" type="double">
<sql type="numeric" table="prod"/>
</field>
</class>

Bill la Forge's Quick is used for the schema of the maps. The QJML markup language is the keystone of Quick. This markup language is used to describe relationships between XML data models and Java classes. The distribution includes a file, maps.qjml, which is the QJML description for the class maps. Please visit Bill la Forge's Quick for details.

Perhaps, it's useful to have a list of products having the same product group. For this, we could use an object ProductGroupList. Its attributes are:

Java NameJava Type
idinteger
nameString
productsarray list of Product

In the above, id and description are from product group. Each of the product members of some product group list has a group_id equal to the product group's id. To obtain the these products using SQL, we could use the SQL below.

select * from prod where group_id = ?

With Osage, you don't need to write this code. Instead, you provide a description of the relation of products to product groups in the class map for ProductCategoryList. A relation map for this is below.

<relation name="products" multi="true" retrieve="auto" type="yourapp.db.Product">
<key name="id" foreign="groupId"/>
</relation>

The above is one way to model a relation with Osage. Another method is illustrated with the Group member of the class yourapp.comp.Product. For this, the relation map follows:

<relation name="group" multi="False" retrieve="auto" type="yourapp.db.Group">
<sql-key foreign="id">
<sql name="group_id" table="prod"/>
</sql-key>
</relation>

The first method could be used to model this relation for product. But this would require a member in yourapp.comp.Product for group_id. This duplicates the data that is in the id member of the Group.

Some relations cannot be modeled with this second method. See the relation for ProductDetails in yourapp.comp.Product.

At last, we're ready to write some code. To retrieve an object, you use the RetrieveCriteria class. Let's retrieve all of the products.

// Obtain a new database
Database db = persistenceManager.getDatabase();

// Begin a transaction
db.begin();

RetrieveCriteria critera = new RetrieveCriteria(Product.FOR_NAME);

Iterator iter = critera.perform(db);

while (iter.hasNext())
{
    product = (Product) iter.next);
.. . .
}

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

To retrieve the list for products having a price greater than 50.0, you code the following:

RetrieveCriteria critera = new RetrieveCriteria(Product.FOR_NAME);
criteria.addSelectGreaterThan(Product.PRICE, 50.0);

Iterator iter = critera.perform(db);
if (iter.hasNext())
{
    product = (Product) iter.next();
.. . .
}

Saving and Deleting

If the class holds a column which is a key column, you can save or delete it. To use delete, you first retreive the object. To save some changes to Product, use the code below.

product.setDescription("new text");
db.save(product);

For a given criteria, Osage also has UpdateCriteria for updating relational data and DeleteCriteria for deleting objects.

For example, to update the price of all the Products named "Engine" to 5000, use the code below:

UpdateCriteria criteria = new UpdateCriteria(Product.FOR_NAME);     // specify
the object type to update
criteria.addSelectEqualTo(Product.NAME, "Engine");                  // specify
the objects to act upon
criteria.addAttribute(new Attr(Product.PRICE, 5000));              // specify
the update to be made
criteria.perform(db);                                               // perform
the update

The attributes of a class can span a table. In this case, it's unlikely that the saving or deleting is appropriate.

Initializing

The Osage SQL engine requires class maps and a source of database connections. You can load the class maps and configure the database connections in one step. To intialize and terminate Osage, the samples use the code below.

PersistenceManager persistenceManager = PersistenceManagerFactory.create();
Configuration conf = ConfigurationBuilder.build("conf.xml");

persistenceManager.setConfiguration(conf);// Load class maps and configure database connections.
.. .. // Use Osage 
persistenceManager.destroy();// We're done with Osage. Free all connections.

Configuring Connections

XML is also used to configure database connections. Again, QJML is used to describe the loading of mapping classes from xml. The QJML description of the database connection maps is in database.qjml. Some sample setups are in database.xml.

Key Generation

Osage features automatic generation of keys. A table that summarizes the key generators follows.

NameImplementationExample
maxSELECT MAX(key) + 1<key-generator type="max" field="id"/>
identityauto-increment<key-generator type="identity" field="id"/>
sequenceSELECT sequence.nextval<sequence name="sequence" field="id"/>
highlowUPDATE keys SET key=key+GRAB_SIZE WHERE tablename=?<highlow field="id" table="keys" tableColumn="tablename" keyColumn="key" />

To use 'max', the database must implement locking for the SELECT statement. In Osage, this is implemented for Interbase, Oracle, and Postgres. It should work for Informix but this has not been tested. 'Identity' is implemented for MySQL, SQL Server, and Sybase. For other databases, you maby use 'highlow'.

Editing Class Maps

Osage doesn't have a tool for creating and editing class maps. One possibility is to write a schema for the class maps, and use an XML editor for this.

Generating Classes

There's a tool which reads class maps and generates Java classes. Generation of the Java classes can not be customized. A code snippet for generating classes from maps is shown below.

String [] args = { "net.sourceforge.osage.util.builder.ClassBuilder"",
"conf.xml" };
net.sourceforge.osage.util.builder.Main.main(args)	;	

Generating Maps

There's a tool which reads a database and generates class maps. A code snippet for generating classes from maps is shown below.

String [] args = { "net.sourceforge.osage.util.builder.MapBuilder"",
"conf.xml" };
net.sourceforge.osage.util.builder.Main.main(args)	;	

Generating DDL

There's a tool which reads class maps and generates DDL for the tables. A code snippet for generating DDL from maps is shown below.

String [] args = { "net.sourceforge.osage.util.builder.SQLBuilder"",
"conf.xml" };
net.sourceforge.osage.util.builder.Main.main(args)	;	

Connection Pooling

Osage does not provide connection pooling. To obtain connections, you have several choices. Firstly, you may use the JavaExchangeBroker class included in the examples. For this, you must obtain DbConnectionBroker which is authored by Marc Mnich. Secondly, you can write your own implementation of ConnectionPool using your favorite package for allocating connections. Thirdly, you can configure a driver without using pooling. In addition, you can configure a DataSource. An example configuration file, database.xml, is provided with the distribution.

Contributing

Osage should support more datatypes, more database specific syntax, and should have better error checking. Bug fixes are certainly welcome.

For enhancements or implementing items in the Todo list, I encourage you to contact me first. I will respond to items posted in the Open Discussion at SourceForge.

MySQL and text datatype

Osage uses the getObject() method of java.sql.ResultSet to retrieve objects. With MySQL the "text" datatype returns byte[] for this method. To get a string, use the example field mapping below.

<field name="someText" type="string" 
convertor="net.sourceforge.osage.convertor.TypeConvertorFactory">
<sql name="some_text" type="longvarbinary" />
</field>

Acknowledgements

This project uses sources authored by Assaf Arkin of ExOffice Technologies, Inc. Assaf Arkin's original code is in Castor JDO, an excellent object-relational mapping system. I also used the class and method names for key generation from Oleg Nitz's work in Castor JDO. Obviously, I like Castor JDO. The reasons for developing Osage are enumerated above. Bill la Forge's Quick is used for loading the class maps. Quick offers a way to convert XML files into a structure of objects, using the classes of your choice. Quick also facilitates the reverse operation; that is, expressing the structure of objects with an XML file. I wish to thank Scott Tavares and others at Turbine for introducing me to Scott Ambler's design.

Todo