ObjectDB ObjectDB

[ODB1] Chapter 2 - A Quick Tour

This chapter introduces basic ObjectDB and JDO concepts, using two sample programs. We start with the HelloWorld sample program, which is not JDO portable because it uses some ObjectDB extensions, but it is a good sample to start with because of its simplicity.  We then proceed with the JDO Person sample program, which demonstrates the process of building a minimal JDO portable application, step by step. Both sample programs are contained in ObjectDB's samples directory.

2.1  Hello World

The HelloWorld sample program manages a list of strings in the database. Each time the program is run another string is stored in the database and all the strings in the database are printed to standard output.

The output of the first run is expected to be:

Hello World 0

The output of the second run is expected to be:

Hello World 0
Hello World 1

After two runs, the database contains a list of two strings "Hello World 0" and "Hello World 1".

Program Source Code

The program consists of a single source file, HelloWorld.java, containing a single class:

 1  // A simple program that manages a list of strings in a database.
 2  
 3  import java.util.*;
 4  import javax.jdo.*;
 5  import com.objectdb.Utilities;
 6  
 7  public class HelloWorld {
 8  
 9      public static void main(String[] args) {
10          
11          // Create or open a database and begin a transaction:
12          PersistenceManager pm =
13              Utilities.getPersistenceManager("hello.odb");
14          pm.currentTransaction().begin();
15         
16          // Obtain a persistent list:
17          ArrayList list;
18          try {
19              // Retrieve the list from the database by its name:
20              list = (ArrayList)pm.getObjectById("Hello World", true);
21          }
22          catch (JDOException x) {
23              // If not found - construct and store a new list:
24              list = new ArrayList();
25              Utilities.bind(pm, list, "Hello World");
26          }
27          
28          // Add a new string to the persistent list:
29          list.add("Hello World " + list.size());
30          
31          // Display the content of the persistent list:
32          Iterator itr = list.iterator();
33          while (itr.hasNext())
34              System.out.println(itr.next());
35 
36          // Close the transaction and the database:
37          pm.currentTransaction().commit();
38          pm.close();
39     }
40 }

How it works

 

Lines 3-5

Three import statements are required: for Java collections (line 3), for JDO (line 4) and for ObjectDB extensions (line 5).

Lines 11-14

A PersistenceManager instance representing a local database file, hello.odb, is obtained using the Utilities.getPersistenceManager(...) static method (lines 12-13). If a database file does not exist in that path, a new database file is created automatically.
To enable updating the content of the database, a transaction is begun (line 14), because in JDO, operations that affect the content of the database must always be contained within an active transaction.

Lines 16-26

The data structure of this program is an ArrayList containing String instances. ObjectDB, as a pure object database, can simply store a memory data structure in the database as is. If the database is not empty (not the first run), a previously stored ArrayList instance is expected to be retrieved from the database by its name "Hello World" using the getObjectById(...) method (line 20). You can ignore the second argument of the getObjectById(...) method that is discussed in section 6.3. If such an ArrayList is not found in the database (on the first run for example), an exception is thrown and a new empty ArrayList instance is created (line 24). The new ArrayList instance is bound to the database with the name "Hello World", using the Utilities.bind(...) static method (line 25). An object bound to the database during an active transaction, is expected to be stored physically in the database when the transaction is committed (line 37).

Note: Memory objects that represent database objects (like list in this example) are called persistent objects. An object retrieved from the database is always persistent. A new object becomes persistent when it is bound to the database.

Lines 28-29

A new string ("Hello World 0", "Hello World 1", etc.) is added to the ArrayList.

Lines 31-34

The ArrayList is iterated (by a standard Java iterator) and its content is printed.

Lines 36-38

On transaction commit (line 37), new persistent objects (like a new ArrayList that becomes persistent in line 25), and modified persistent objects (like a retrieved ArrayList, which is modified in line 29) are automatically stored in the database. Objects in memory that are reachable from persistent objects (like the String instance, which is added in line 29) are also stored in the database automatically.
At the end, the database is closed (line 38). Usually, it is best to close the database in a finally block, as demonstrated in the next sample program, to ensure database closing in every situation, including exceptions.

 

ObjectDB Extensions

The com.objectdb.Utilities class implements ObjectDB extensions. Storing an ArrayList directly in the database with a specified name (line 25), is supported by ObjectDB but not by JDO. In JDO, only instances of special user defined classes (which are called persistent classes) can be stored in the database directly, and other types (such as Java collections) can be stored only as fields of persistent classes. In addition, objects are stored without names in JDO. Another extension is used to obtain a PersistenceManager instance (line 13). Obtaining a PersistenceManager in a portable JDO way, which is slightly more complicated, is demonstrated in the next program sample.

2.2  Defining a Persistent Class

As noted above, in a portable JDO application, only instances of persistent classes are stored directly in the database. Predefined Java types like ArrayList, String and Date can be stored in the database only as fields of persistent classes.

Persistent Classes

Persistent classes are user defined classes whose instances can be stored in the database using JDO. The JDO specification refers to persistent classes as persistence capable classes, but both terms are equivalent. Chapter 3 is dedicated to persistent classes.

To become persistent, a class has to:

  • be declared in a JDO metadata file in XML format.
  • include a no-arg constructor.
  • implement the javax.jdo.spi.PersistenceCapable interface.

The PersistenceCapable interface includes more than 20 abstract methods, so implementing it explicitly is far from trivial. Therefore, persistent classes are usually defined without implementing that interface explicitly, and a special tool, the JDO enhancer, adds the implementation automatically.

The Person Persistent Class

The following Person class serves as a persistent class:

 1 // The Person persistent class
 2 
 3 public class Person {
 4 
 5     // Persistent Fields:
 6     private String firstName;
 7     private String lastName;
 8     private int age;
 9     
10     // Constructors:
11     public Person() {}
12     public Person(String firstName, String lastName, int age) {
13         this.firstName = firstName;
14         this.lastName = lastName;
15         this.age = age;
16     }
17     
18     // String Representation:
19     public String toString() {
20         return firstName + " " + lastName + " (" + age + ")";
21     }
22 }

The Person class is an ordinary Java class with a no-arg constructor (line 11). If it is declared as persistent in a JDO metadata file, it can easily become persistent using the JDO enhancer.

JDO Metadata

Every persistent class must be declared as persistent in a special XML file, called the JDO metadata file. The Person class must be declared in a metadata file named either package.jdo (generic name) or Person.jdo (name of the class). More details about metadata files, including their location and naming rules, are provided in chapter 4, which is devoted to JDO metadata.

The following package.jdo file is located in the same directory as person.class:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE jdo SYSTEM "http://java.sun.com/dtd/jdo_1_0.dtd">
 3 
 4 <jdo>
 5     <package name="">
 6         <class name="Person" />
 7     </package>
 8 </jdo>

Every JDO metadata file contains a single <jdo> element (lines 4-8). Each package is represented by a <package> sub element (e.g. lines 5-7) containing <class> elements for all the persistent classes in the package (e.g. line 6). Both <package> and <class> elements must have a name attribute. An empty name is specified for the <package> element to represent the default package (in which the class is defined). More information can be provided in the metadata file (as shown in chapter 4), but this minimal form is sufficient here.

2.3  Storing and Retrieving Objects

The JDO Person sample manages a collection of Person instances in the database.
Three arguments have to be specified in order to run the program Main class:

> java Main George Bush 57

Each time the program is run, another Person instance is constructed (according to the specified first name, last name and age) and stored in the database.

Program Source Code

In addition to the two files from the previous section (Person.java and package.jdo), the program contains the following Main.java source:

 1 // Main of the JDO Person sample.
 2 
 3 import java.util.*;
 4 import javax.jdo.*;
 5 
 6 public class Main {
 7 
 8     public static void main(String[] args) {
 9         
10         // Check the arguments:
11         if (args.length != 3)
12         {
13             System.out.println(
14                "Usage: java Main <first name> <last name> <age>");
15             System.exit(1);
16         }
17
18         try {             
19             // Obtain a database connection:
20             Properties properties = new Properties();
21             properties.setProperty(
22                 "javax.jdo.PersistenceManagerFactoryClass",
23                 "com.objectdb.jdo.PMF");
24             properties.setProperty(
25                 "javax.jdo.option.ConnectionURL", "persons.odb");
26             PersistenceManagerFactory pmf =
27                 JDOHelper.getPersistenceManagerFactory(properties);
28             PersistenceManager pm = pmf.getPersistenceManager();
29         
30             try {
31                 // Begin the transaction:
32                 pm.currentTransaction().begin();
33             
34                 // Create and store a new Person instance:
35                 Person person = new Person(
36                     args[0], args[1], Integer.parseInt(args[2]));
37                 pm.makePersistent(person);
38     
39                 // Print all the Persons in the database:
40                 Extent extent = pm.getExtent(Person.class, false);
41                 Iterator itr = extent.iterator();
42                 while (itr.hasNext())
43                     System.out.println(itr.next());
44                 extent.closeAll();
45
46                 // Commit the transaction:
47                 pm.currentTransaction().commit();
48             }
49             finally {
50                 // Close the database and active transaction:
51                 if (pm.currentTransaction().isActive())
52                     pm.currentTransaction().rollback();
53                 if (!pm.isClosed())
54                     pm.close();
55             }
56         }
57
58         // Handle both JDOException and NumberFormatException:
59         catch (RuntimeException x) {
60             System.err.println("Error: " + x.getMessage());
61         }
62     }
63 }

How it works

 

Lines 3-4

Two import statements are required: for Java collections (line 3), and for JDO (line 4). ObjectDB extensions are not used in this program.

Lines 19-28

A PersistenceManager instance representing a local database file, person.odb, is obtained using JDO portable code (slightly more complicated than the equivalent code in section 2.1). A detailed explanation of this code is provided in Chapter 5.

Lines 31-32

A transaction on the database is begun.

Lines 34-37

A new Person instance is constructed (lines 35-36) and becomes persistent (line 37). The makePersistent(...) method binds the object to the database, but without specifying a name (as done by the bind(...) method in section 2.1). This is the JDO portable way to add objects to the database. The new persistent object is physically stored in the database only when the transaction is committed (line 47).

Lines 39-44

The Person instances in the database are represented in JDO by an Extent instance (line 40). The second argument of the getExtent(...) method indicates whether the Extent should also represent instances of subclasses (changing false to true has no effect here, because Person has no subclasses). The Extent of the class can be iterated using an ordinary Java Iterator (lines 41-43). Unlike ordinary Java iteration, when the end of Extent iteration is reached it is recommended to use closeAll() (line 44) or close(...) to release resources.

Lines 46-47

Updates are physically applied to the database when the transaction is committed (line 47).

Lines 49-55

The database is closed (lines 53-54) in a finally block, to ensure it is always properly closed, even in case of an error (when an exception is thrown). If a transaction is still active (e.g. exception is thrown in lines 34-44), it should be ended before closing the database. Ending the transaction with rollback() discards all the changes done during its activity (line 52).

Lines 58-61

When working with JDO, instances of JDOException and its subclasses may be thrown as exceptions. Because JDOException is a subclass of RuntimeException, the compiler does not enforce the throws declaration if a catch block does not exist in the method. Therefore, extra caution is required by the developer to make sure that at some level a proper catch block (like in lines 58-61) exists.

 

An attempt to run the program now results in the following error:

Error: Class 'Person' is not PersistenceCapable

Something is still missing to make this program work - the JDO enhancement.

2.4  On The Fly JDO Enhancement

On the fly JDO enhancement is the easiest way to use the JDO enhancer. To use it, an additional main class, named eMain (enhancer Main) is defined:

 

 1 // An enhancer main class for the JDO Person Sample
 2 
 3 public class eMain {
 4 
 5     public static void main(String[] args) {
 6         
 7         // Enhance the Person class if necessary:
 8         com.objectdb.Enhancer.enhance("Person");
 9 
10         // Now run the real main:
11         Main.main(args);
12     }
13 }

Class eMain replaces the original Main class as a program entry point at development time. It starts by enhancing necessary classes (line 8). JDO enhancement includes modifying class files in order to add implementation of the PersistenceCapable interface, where necessary, at the byte code level. When this process is completed the real main is called (line 11). Of course, this arrangement should only be used during development. Eventually, the application should be deployed with ready to use enhanced classes and without the eMain class.

The enhancer should print the following message (unless Person.class is already enhanced):

[ObjectDB Enhancer] 1 new persistence capable class has been enhanced.

If you see the following message, something went wrong:

[ObjectDB Enhancer] 1 new persistence aware class has been enhanced.

The enhancer did not find the metadata file in which Person is declared as persistent. Therefore, it was not able to enhance the Person class as a persistent class (a persistence aware class is not a persistence capable class). Additional information on JDO enhancement, persistence capable classes and persistence aware classes is provided in section 3.3.