DB Objects for Java

A Java solution for storing objects in relational databases.


Links
Documentation Links
External Links

Introduction

This package provides a lightweight and flexible solution for storing and retrieving java objects in and from relational databases. It is intended for developers who don't want to cope with SQL statements in their code. It is not an alternative to a full-featured application server because of several limitations this package has. But if you want to create a small or midium-sized application with access to a database, with or without a web-tier, this might be what you need.

Although the code has matured by now, it is still work in progress. For me it has been quite useful. If you have any comments and suggestions please feel free to contact me.

DB Objects is under the LGPL. Have a look at GNU Project: List of Licenses for more details.

Credits go to Marc A. Mnich for his class DBConnectionBroker that he allowed me to include into DB Objects and Brad Matlack for his patches that are included in the current version.

New version 0.34 (July 25, 2001): After almost a year with little progress, a new version is available. See version history for details.

Please note that there is a new project available that builds on the ideas of DB Object. It is called jStorm, developed by Gareth Reeves. Future development on his project will be more avtive than on this one.

This project is hosted by

Technical Summary

The approach taken in this package is to view a database only as a storage mechanism. The advantage is that storing and retrieving data can be done generically without any user-specific code. The drawback is that special database features are hard to support from a user's point of view.

One class, the DBManager, is responsible for all access to the database. It uses connection pooling for high performance access to the database, executes pre-generated statements, and creates new objects after they are retrieved from the database.

Each class whose objects have to be made persistent has to extend the abstract class StorableObject and needs to be initialized. During the initialization, an ObjectMapping object is created and all information needed about the class is set by the DBManager.

This object mapping defines all fields of the class that should be stored and their corresponding database table and columns. Each time the DBManager stores or retrieves objects, the object mapping is called up to get all information about the class.

For a better understanding of the architecture please look at the following UML diagram:

A Use Case

Consider an application that needs a storage mechanism for the data of some of its classes. To use the DB Objects package, all classes of the application with persistent data have to extend StorableObject. During initialization of the application, each one of these classes is initialized and an ObjectMapping is created with all the information about the persistent fields. Each field of the class needs getter and setter methods.

Our example application needs to store data about a customer. Therefore, there is a Customer class with fields like name, email address, etc. For each field, there are getter and setter methods.

Once a new customer signs in, a new Customer object is created. This is done using new Customer(). After all fields are set, the object has to be stored in the database. This is done by calling the store() method, which belongs to StorableObject.

Later on, the customer's data is updated. Some values change and the database has to be updated. This is performed by another call to store().

In a different situation the application wants to list all customers in a table. To achieve this, the Customer class is expanded by a new static method called getCustomerList(). This method implements a call to DBManager.getByField() that, in turn, retrieves a list of all customers stored in the database.

If a customer cancels his or her account, the data in the database needs to be deleted. To do this, the Customer object must first be retrieved. A single call to delete() removes the database entry then.

Some source code

To avoid writing complete and in-depth documentation, here is some commented source code that is taken from the dbobjects.test.mysql package.

Getting started - How to start the test application

Before I present an explanation of the test source code here is some information that you need to test the application:

The customer class

The customer class contains all data fields about a customer and also methods to change and access this data. Furthermore, any kind of business method could be added. Here is this example's complete source code for Customer.java.
     1:import org.gjt.tw.dbobjects.*;
     2:import java.util.*;
     3: 
To use the package you need to import org.gjt.tw.dbobjects.*.
     4:  public class Customer extends StorableObject {
     5:  private static ObjectMapping mapping;
     6:  private long id;
     7:  private String name;
     8:  private String password;
     9: 
To become a storable object, the Customer class extends the class StorableObject. The declaration of an object of class ObjectMapping must be part of each class extending StorableObject. In this example, the field "id" becomes the object's key; name and password are two normal fields.
    10:  public Customer() {
    11:    super();
    12:    name = "";
    13:    password = "";
    14:  }
    15: 
The constructor of StorableObject needs to be called. Afterwards all fields that should be stored, have to be initialized.
    16:  public long getId() {
    17:    return id;
    18:  }
    19:
    20:  public String getName() {
    21:    return name;
    22:  }
    23:
    24:  public String getPassword() {
    25:    return password;
    26:  }
    27:
    28:  public void setId(long newValue) {
    29:    this.id = newValue;
    30:  }
    31:
    32:  public void setName(String newValue) {
    33:    this.name = newValue;
    34:  }
    35:
    36:  public void setPassword(String newValue) {
    37:    this.password = newValue;
    38:  }
    39: 
For each field that is part of the object mapping, there must be a getter and a setter method.
    40:  protected ObjectMapping getMapping() {
    41:    if (mapping == null)
    42:      mapping = new ObjectMapping ();
    43:    return mapping;
    44:  }
    45: 
This method has to be copied, as is, into each class extending StorableObject. It returns a reference to the static ObjectMapping object of this class.
    46:  public static void init () throws ObjectException, IncompleteDefinitionException {
    47:    mapping = new ObjectMapping ();
    48:    mapping.setTableName ("customer");
    49:    mapping.setObjectClass (Customer.class);
    50:    mapping.addField ("id", Long.TYPE, "id");
    51:    mapping.addField ("name", String.class, "customer_name");
    52:    mapping.addField ("password", String.class, "customer_password");
    53:    mapping.setKeyField ("id");
    54:    mapping.setAutoIncrement (ObjectMapping.AUTOINCREMENT_MYSQL);
    55:    mapping.prepareSQLStatements ();
    56:  }
    57: 
Each class needs a static init() method that is called once during initialization of the application.

First, a new mapping object is created. This object contains all the information needed by DBManager to store the fields of this class into the database. Therefore, several calls are needed: The name of the table in which all fields are stored and the current class has to be set explicitly. For each field you want to store, you have to define the name of the field, the class, or type of the field, and the name of the database column. Finally, you have to define the name of the field that is the key for this class.

This package supports MySQL's auto_increment feature. This means, if you use an integer or long as key and this database field is defined as auto_increment, the key of your object is set automatically when the object is stored into the database for the first time. For this to work, you need to use the JDBC driver from org.gjt.mm.mysql. You can get this driver here.

If you prefer not to use MySQL you can use any other jdbc complient database and use the generic id generating feature provided by DB Objects.

The last thing to do is to call prepareSQLStatements(). This checks the completeness and validation of the given information.

    58:  public static Customer getByName (String name) throws DatabaseException, ObjectException {
    59:    StorableObject[] objects = DBManager.getByField (mapping, "name", name);
    60:    if (objects == null)
    61:      return null;
    62:    else
    63:      return (Customer) objects[0];
    64:  }
    65:}
    
This is an example of how to retrieve an object that is already stored in the database. Calling DBManager.getByField() returns a list of StorableObject objects. If the list is not empty, it can be casted directly to Customer[]. The three parameters include the reference to the mapping object, the name of the field, and the value of that field. In this example, this call is translated to the SQL statement "SELECT * FROM customer WHERE name = xxx" where xxx is the name of the current customer.

Note that there are two kinds of getByXXX-methods. In this example, an array of objects is retrieved that can be cast directly to the objects' class. This is helpful if you only want to have a fixed list of objects (for example, if you are developing a web application). If you want to store the objects permanently in memory and want to add and remove objects, there are getByXXXVector-methods that return a vector of the objects. To add a new object to your application you have to create the object, store it, and add it to the vector. To remove an object, is has to be both removed from the vector and deleted from the database manually. Keep in mind that you are not using Enterprise Java Beans.

A test application

This is a small test application that uses the customer class. Here is the complete source code for TestApplication.java .
     1:import org.gjt.tw.dbobjects.*;
     2:import java.util.*;
     3:import java.io.*;
     4:
     5:public class TestApplication {
     6:
     7:  public static void main(String args[]) {
     8:
     9:    // Loading properties
    10:    Properties properties = new Properties();
    11:    try {
    12:      properties.load(new FileInputStream("dbobjects.properties"));
    13:    }
    14:    catch (FileNotFoundException f) {
    15:      System.out.println ("Error during loading of properties: File not found!");
    16:      System.exit (-1);
    17:     }
    18:    catch (IOException e) {
    19:      System.out.println ("Error during loading of properties: IOException!");
    20:      System.exit (-1);
    21:    }
    22:
First, a property file containing information about the database and how to access it needs to be read. Such a property file is part of the distribution. For explanation of the properties, please look at the javadoc documentation for DBManager.
    23:    // Initialize database connection broker    
    24:    try {
    25:      DBManager.init (properties);
    26:    }
    27:    catch (DatabaseException e) {
    28:      System.out.println ("Error during initializing of connection to database:\n"+e);
    29:      System.exit (-1);
    30:    }
    31:
The next thing to do is to initialize the DBManager which, in turn, initializes the connection broker that takes care of the database connections.
    32:    // Initialization of storable objects.
    33:    try {
    34:      Customer.init ();
    37:    }
    38:    catch (IncompleteDefinitionException e) {
    39:      System.out.println ("Definition of object mapping is not complete!");
    40:      System.exit (-1);
    41:    }
    42:    catch (ObjectException e) {
    43:      System.out.println ("Definition of object mapping is inconsistent with data structure!");
    44:      System.exit (-1);
    45:    }
    46:
Now, all classes that extend StorableObject need to be initialized.
    47:    // Do some tests.
    48:    try {
    49:      Customer customer = new Customer ();
    50:      customer.setName ("Test Name");
    51:      customer.setPassword ("Test password");
    52:      customer.store ();
    53:      customer.print ();
    54:
    55:      customer = Customer.getByName ("Test Name");
    56:      customer.print ();
    57:
    58:      customer.delete ();
    59:    }
    60:    catch (Exception e) {
    61:      System.out.println (e);
    62:      System.exit (-1);
    63:    }
    64:
This is the fun part of the application. A new customer object is created and its values are set. Afterwards the object is stored and its content is printed to the console.

Then, the customer object is retrieved from the database using Customer.getByName(name).

Finally, this customer object is removed from the database using customer.delete().

    65:    // Shut down database connection broker   
    66:    try {
    67:      DBManager.destroy ();
    68:    }
    69:    catch (Exception e) {
    70:      System.out.println ("Error during destruction of database connectivity!");
    71:      System.exit (-1);
    72:    }
    73:  }
    74:}
    
Before quitting the application, you should call DBManager.destroy() that, in turn, cleans up and shuts down the database connections.

Even more examples

There is more example and test code in the test packages. I'd like to ask you to have a look at it to get an better impression of how to use this package. In particular you will find an example that copes with relations.

Additional Features

Generic ID generation

There are two different ways to let DB Objects create unique IDs. You could either use the database MySQL and its auto_increment feature or depend on DB OBjects' own implementation of such a feature.

That implementation queries the database for the highest ID currently used, increases that value, and uses the increased value as the new ID.

DbConnectionBroker

This class has been written by Marc A. Mnich. It maintains a connection pool that reduces the number of connections that have to be initiated. An interface is provided to make use of other existing connection pooling classes.

DOM tree

Each class extending StorableObject inherits a method called getDOMTree(document). Taking an existing XML document, this method returns an XML element containing a tree of all mapped fields of this object. This feature is neat if you have to create XML output from your data.

DBManager get methods

Only the method getByField has been introduced so far. There are more methods that help to find the objects searched for. You can also define a WHERE clause, or you can just fetch all objects that are stored in the database.

Frequently Asked Questions

On which databases has DB OBjects been tested?

DB Objects is reported to work on the following databases: Because no database specific features are used (except MySQL's auto_increment), it should run on more or less all databases for which jdbc drivers are available. There is a separate test package (test.generic) with classes that can be used for testing with these databases. In the ./sql sub-directory of the distribution you find appropriate sql scripts for several databases.

Is the generic ID generation thread safe?

Yes, it is, as long as there is only one application using the database at a time. The method that queries that database and creates a new entry is synchronized. Therefore, running a multi-threaded application is not a problem.

However, there are cases where this isn't sufficient: Either you could create a client application that is run by several people at the same time, or you could build a server of which several instances exist. Both times, there might be race conditions that make one application's insert fail. If this happens, an exception is thrown that has to be handled by the application.

As an alternative, you could turn the automatic key generation off and do it manually. You could use, for example, a file that always holds the highest key given so far.

How are 1:n and m:n relations covered?

For each relation that takes a table of its own you need to implement a class that maps to that table. Using this approach, you can use arbitrary n:m relations. 1:n relations are even easier: You just have to store the foreign key in your class. If, for example, you'd like to store the address of a customer, you would add a field address_id to the Customer class that maps to the respective column and add a getAddress method to the Customer class that calls Address.getById to retrieve the address object. Both kinds of relations are implemented in the test packages.

Are there any known projects that use DB Objects

I know of several people that use DB Objects for their purposes. Unfortunately, I cannot provide any URLs of their projects. If you understand German, look at Olympia 2000, a program that I wrote for the Olympic summer games in Sydney, Australia, in 2000.

Am I allowed to use DB Objects in commercial applications?

DB Objects is under the LGPL. Basically, that means that all modifications of this library must be made available as open source. But you are allowed to use it in any closed source projects.

Version History