ObjectDB ObjectDB

[ODB1] Chapter 5 - JDO Connections

This chapter describes three essential interfaces for using JDO:

The PersistenceManagerFactory interface represents a factory of database connections. Its main role is to provide PersistenceManager instances. The PersistenceManager interface represents a database connection. Every operation on a database requires a PersistenceManager instance. The Transaction interface represents a transaction on a database. Every operation that modifies the content of the database requires an active transaction.

The focus of this chapter is on setting up a runtime environment for using JDO. Explanations on how to use this environment to do database operations, such as storing, retrieving, updating and deleting database objects, are provided in the next chapter (Chapter 6).

5.1  javax.jdo.PersistenceManagerFactory

Most applications require multiple database connections during their lifetime. For instance, in a web application it is very common to establish a separate database connection for every web request. In general, holding a database connection open for longer than necessary is not recommended in multi user applications because of the resources that every connection consumes.

Database connections are managed in JDO by the javax.jdo.PersistenceManagerFactory interface. A PersistenceManagerFactory instance represents a specific database address (local or remote), and connections to that database are obtained using its getPersistenceManager() method. Each time this method is called, a PersistenceManager instance representing a connection to the database is obtained. To improve efficiency, a PersistenceManagerFactory instance may manage a pool of free PersistenceManager instances. From the developer's point of view, connection pool management is transparent. The functionality of PersistenceManager instances returned by the getPersistenceManager() method is the same for both new database connections and database connections obtained from a pool.

Both PersistenceManagerFactory and PersistenceManager are defined as JDO interfaces, not as classes. Every JDO implementation, including ObjectDB, defines classes that implement these interfaces. When using ObjectDB you are actually working with instances of ObjectDB classes, but to make your application JDO portable these objects are accessed through the standard JDO interfaces.

Obtaining a PersistenceManagerFactory

The following code demonstrates how to obtain a PersistenceManagerFactory instance:

import java.util.Properties;
import javax.jdo.*;
    :
    :
  Properties properties = new Properties();
  properties.setProperty(
    "javax.jdo.PersistenceManagerFactoryClass", "com.objectdb.jdo.PMF");
  properties.setProperty(
    "javax.jdo.option.ConnectionURL", "local.odb");

  PersistenceManagerFactory pmf =
    JDOHelper.getPersistenceManagerFactory(properties);

The requested PersistenceManagerFactory is specified using a Properties instance. Each property consists of two strings, a name that identifies the property, and a value for that property. The two properties that are set in the code above are usually essential:

  • javax.jdo.PersistenceManagerFactoryClass
    Specifies the real type of the constructed PersistenceManagerFactory instance.
    When using ObjectDB, the value should always be "com.objectdb.jdo.PMF" (the name of ObjectDB's class that implements the PersistenceManagerFactory interface).
  • javax.jdo.option.ConnectionURL
    Specifies a database location. To access a database file directly using embedded mode, specify either its absolute path or its relative path.

The JDOHelper.getPersistenceManagerFactory(...) static method constructs and returns a new PersistenceManagerFactory instance, based on the specified properties.

Using a Properties File

Properties can also be specified in a file, as demonstrated by the following properties file:

javax.jdo.PersistenceManagerFactoryClass=com.objectdb.jdo.PMF
javax.jdo.option.ConnectionURL=local.odb

Assuming the name of the file is jdo.properties, it can be loaded by a class in the same directory using:

import java.io;
import java.util.Properties;
import javax.jdo.*;
      :
      :
  InputStream in = getClass().getResourceAsStream("jdo.properties");
  try {
    Properties properties = new Properties();
    properties.load(in);
    PersistenceManagerFactory pmf =
      JDOHelper.getPersistenceManagerFactory(properties);
  }
  finally {
    in.close();   
  }

Notice that the code above may throw an IOException if the jdo.properties file is not found. In addition, the call to getClass() should be replaced by some other expression (e.g. MyClass.class) in a static context (i.e. a static method and a static initializer) in which this is undefined.

Connection Properties

The javax.jdo.option.ConnectionURL property specifies the database location and whether embedded mode or client server mode should be used to access that database. To use embedded mode, an absolute path or a relative path of a local database file has to be specified.
To use client server mode, a url in the format objectdb://host:port/path has to be specified. In this case, a database server is expected to be running on a machine named host (could be domain name or ip address) and listening to the specified port (the default is 6136 when not specified). The path indicates the location of the database file on the server, relative to the server root path (see chapter 8 for more details). Client server mode is only supported by the ObjectDB server database edition.

The following code demonstrates a connection to a database located at path /my.odb in a database server running on localhost (the same machine) and listening to the default port (6136):

  Properties properties = new Properties();
  properties.setProperty(
    "javax.jdo.PersistenceManagerFactoryClass", "com.objectdb.jdo.PMF");
  properties.setProperty(
    "javax.jdo.option.ConnectionURL", "objectdb://localhost/my.odb");
  properties.setProperty("javax.jdo.option.ConnectionUserName", "john");
  properties.setProperty("javax.jdo.option.ConnectionPassword", "itisme");

  PersistenceManagerFactory pmf =
    JDOHelper.getPersistenceManagerFactory(properties);

The javax.jdo.option.ConnectionUserName and javax.jdo.option.ConnectionPassword properties are usually required in a client server connection to enable user identification and permission checking. Username and password are optional in a client server connection only when the server configuration allows anonymous access to the database. If not specified here, username and password can also be specified later when obtaining a PersistenceManager instance, as explained in the next section. In embedded mode, in which permissions are not managed, these properties are always optional and ignored if specified.

Connection properties can also be managed later using methods of the PersistenceManagerFactory:

  // Using the setter methods:
  pmf.setConnectionURL("objectdb://localhost/data.odb");
  pmf.setConnectionUserName("john");
  pmf.setConnectionPassword("itisme");

  // Using the getter methods:
  System.out.println("Database URL: " + pmf.getConnectionURL());
  System.out.println("Username: " + pmf.getConnectionUserName());
    // No getter for the password because of security considerations.

Transaction Properties

JDO defines five optional modes for working with transactions, represented by five flags that can be set or cleared. All these optional modes are supported by ObjectDB. Flags can be specified for a specific Transaction (as shown in section 5.3) or at the PersistenceManagerFactory level as a default for all the transactions of PersistenceManager instances obtained from that factory.

Setting these flags for a PersistenceManagerFactory can be done using boolean properties:

  properties.setProperty(
    "javax.jdo.option.Optimistic", "false");
  properties.setProperty(
    "javax.jdo.option.NontransactionalRead", "true");
  properties.setProperty(
    "javax.jdo.option.NontransactionalWrite", "false");
  properties.setProperty(
    "javax.jdo.option.RetainValues", "true");
  properties.setProperty(
    "javax.jdo.option.RestoreValues", "false");

  PersistenceManagerFactory pmf =
    JDOHelper.getPersistenceManagerFactory(properties);

Actually, the settings above are redundant because they are the default flag values in ObjectDB (but not in JDO, which does not specify a default). Opposite values should be specified in order to change the default.

In addition, the PersistenceManagerFactory class includes getter and setter methods:

  // Using the setter methods:
  pmf.setOptimistic(true);
  pmf.setNontransactionalRead(false);
  pmf.setNontransactionalWrite(true);
  pmf.setRetainValues(false);
  pmf.setRestoreValues(true);

  // Using the getter methods:
  boolean flag1 = pmf.getOptimistic();
  boolean flag2 = pmf.getNontransactionalRead();
  boolean flag3 = pmf.getNontransactionalWrite();
  boolean flag4 = pmf.getRetainValues();
  boolean flag5 = pmf.getRestoreValues();

Transaction modes are discussed in section 5.3, which deals with transactions.

5.2  javax.jdo.PersistenceManager

The javax.jdo.PersistenceManager interface represents database connections. A PersistenceManager instance is required for every operation on a database. It may represent either a remote connection to a remote database server (in client server mode) or a local connection to a local database file (in embedded mode). The functionality in both cases is the same.

PersistenceManager instances are usually obtained for short term use (for example, per web request in a web application). In some cases, however, a single PersistenceManager instance can be used during the entire application run. This may be a good option in a single-user desktop application using an embedded ObjectDB database (for example, a personal organizer). In multi-user applications (using client server mode to connect to the database), short term connections are preferred because every open connection consumes essential server resources.

Obtaining a Database Connection

Given a PersistenceManagerFactory instance pmf, a PersistenceManager instance can be obtained by:

  PersistenceManager pm = pmf.getPersistenceManager();

If the connection requires username and password other than the PersistenceManagerFactory's username and password (irrelevant when using embedded mode), new values can be specified:

  PersistenceManager pm = pmf.getPersistenceManager(userName, password);

The settings of the PersistenceManagerFactory at the time of this operation are used to initialize the new PersistenceManager instance. The PersistenceManagerFactory's connection url specifies the location of the database. Using an ObjectDB extension, a PersistenceManager instance can also be obtained directly, without a PersistenceManagerFactory instance:

  PersistenceManager pm = Utilities.getPersistenceManager("local.odb");

Note: These operations can be used for both opening a database and creating a new one. If a database file is not found at the specified connection url, ObjectDB tries to create a new, empty database at that location. This behavior is an ObjectDB extension. JDO does not define a standard method for creating a new database.

Closing a Database Connection

A typical short term database connection has the following structure:

  PersistenceManager pm = pmf.getPersistenceManager();
  try {
    // Operations on the database should come here. 
  }
  finally {
    if (pm.currentTransaction().isActive())
      pm.currentTransaction().rollback();
    if (!pm.isClosed())
      pm.close();
  }

The finally block ensures database connection closure (and rolling back an active transaction if any) regardless of whether the operations succeeded or threw an exception.

Exception Handling

The code fragment above, as with any other code that uses JDO, may throw instances of JDOException (and its subclasses) as exceptions. Therefore, it is important to wrap JDO method calls in a try-catch block:

  try {
    // Code containing database operations should be loacated here.    
  }
  catch (JDOException x) {
    // Error handling code should come here.
  }

Because JDOException is a subclass of RuntimeException the compiler does not force a throws declaration if code that uses JDO is not wrapped by a try-catch block (unlike code working with files and I/O, for example, in which handling a checked exception, such as IOException, is forced by the compiler). Therefore, extra caution is required by the developer to make sure that at some level a proper try-catch block exists.

5.3  javax.jdo.Transaction

The javax.jdo.Transaction interface is used to represent and manage transactions on a database. In JDO, operations that affect the content of the database (store, update, delete) must always be performed within an active transaction. On the other hand, reading from the database does not require an active transaction unless the NontransactionalRead mode is off, as explained below.

Working with Transactions

The code from the section above is now adjusted to work with a transaction:

  PersistenceManager pm = pmf.getPersistenceManager();
  try {
    pm.currentTransaction().begin();
    // Operations that modify the database should come here. 
    pm.currentTransaction().commit();
  }
  finally {
    if (pm.currentTransaction().isActive())
      pm.currentTransaction().rollback();
    if (!pm.isClosed())
      pm.close();
   }

Every PersistenceManager instance holds a reference to an attached Transaction instance (i.e. there is a one to one relationship between these two instances). The currentTransaction() method can be called on any PersistenceManager instance, in any context, to return its associate Transaction object. The strong one to one relationship implies that, theoretically, transaction management could be done directly in PersistenceManager. But, the designers of JDO preferred to define a separate interface for managing transactions.

A transaction is started by a call to begin() and ended by a call to commit() or rollback(). All the operations on the database within these boundaries are associated with that transaction and are kept in memory until the transaction is ended. If the transaction is ended with rollback(), all the modifications to the database are discarded. On the other hand, ending the transaction with commit() propagates all the database modifications physically to the database. If for any reason a commit() fails, the transaction is rolled back automatically (including rolling back modifications that have already been propagated to the database prior to the failure) and an exception is thrown. The type of the exception is a JDOException subclass that reflects the specific error that occurred.

A transaction is always expected to be atomic, so a situation in which only some of the changes are applied to the database is not acceptable. Before commit() starts writing to the database it stores the changes in a related temporary file in the same directory as the database file. For instance, a database file named db.odb, would have a corresponding temporary file named .$db.odb$. After the transaction is completed the temporary file is marked as obsolete (and for efficiency, it is deleted only when the database is closed). If the system crashes during a commit() (e.g. power failure), and before the changes are able to be completely written to the database, the transaction is expected to be completed, using the temporary file, the next time the database is opened. This feature, which is called auto recovery, is not supported by the free edition of ObjectDB. To ensure database consistency, it is recommended that write caching be disabled - at least for the database file and the recovery temporary file. Consult your operating system documentation for instructions on how to accomplish this.

It is mainly a matter of style whether or not to use a separate variable to hold the Transaction instance. The following code is equivalent to the code above:

  PersistenceManager pm = pmf.getPersistenceManager();
  Transaction tr = pm.currentTransaction(); 
  try {
    tr.begin();
    // Operations that modify the database should come here. 
    tr.commit();
  }
  finally {
    if (tr.isActive())
      tr.rollback();
    if (!pm.isClosed())
      pm.close();
  }

Automatic Lock Management

ObjectDB manages an automatic lock mechanism in order to prevent a database object from being modified by two different users (which are represented by two different PersistenceManager instances) at the same time. In an active transaction, on every retrieval of an object from the database a READ lock on that object is obtained by the PersistenceManager. On every attempt to modify a persistent object, within an active transaction, a WRITE lock is obtained on that object by the PersistenceManager. Multiple READ locks of different PersistenceManager instances on the same object at the same time are allowed, but a WRITE lock is expected to be exclusive. Therefore, any attempt to modify an object that is currently in use by some other PersistenceManager instance (for read or write), as well as any attempt to read an object that is currently being modified by some other PersistenceManager, blocks the thread until the previous lock is released. When a transaction ends, all the locks held by that transaction are automatically released.

To avoid a deadlock, threads do not wait for a lock forever. When a lock request cannot be granted, the requesting thread waits 50 milliseconds and then makes another attempt to obtain the lock. After 40 retries (about 2 seconds) a JDOUserException (a subclass of JDOException) is thrown. These constants (50 milliseconds and 40 retries) can be changed using System properties (before the database is opened) as so:

 // Maximum 10 tries before throwing an exception: 
 System.setProperty("com.objectdb.lock.retry.max", "10");
  
 // Wait 100 milliseconds between every two tries:
 System.setProperty("com.objectdb.lock.retry.wait", "100");  

Transactions that use the lock mechanism described above are called, in JDO, datastore transactions. This type of transaction is used in ObjectDB by default, but ObjectDB also supports another type of transaction called optimistic transactions. When using optimistic transactions, objects are not locked. Therefore, two transactions may update the same persistent object at the same time, and conflicts are detected at commit time. If a conflict happens, the first committed transaction succeeds while the second fails, throws an exception, and is rolled back automatically.

Optimistic transactions may be a good choice when long running transactions are required. This would avoid the excessively long object locking periods that datastore transactions may cause. On short running transactions, however, or when the probability for lock conflicts is high, datastore transactions are preferred.

 

JDO Transaction Flags

Transaction modes can be set at PersistenceManagerFactory level (as shown in section 5.1), or at the PersistenceManager or Transaction levels, using getter and setter methods:

  // Using the setter methods:
  pm.currentTransaction().setOptimistic(true);
  pm.currentTransaction().setNontransactionalRead(false);
  pm.currentTransaction().setNontransactionalWrite(true);
  pm.currentTransaction().setRetainValues(false);
  pm.currentTransaction().setRestoreValues(true);

  // Using the getter methods:
  boolean flag1 = pm.currentTransaction().getOptimistic();
  boolean flag2 = pm.currentTransaction().getNontransactionalRead();
  boolean flag3 = pm.currentTransaction().getNontransactionalWrite();
  boolean flag4 = pm.currentTransaction().getRetainValues();
  boolean flag5 = pm.currentTransaction().getRestoreValues();

Here is a short explanation of each one of these modes:

javax.jdo.option.Optimistic

Indicates whether optimistic locking (optimistic transaction) or pessimistic locking (datastore transaction) is used. The default in ObjectDB is false, indicating that transactions are datastore transactions by default.

javax.jdo.option.NontransactionalRead

Indicates whether or not database operations that do not modify the database content (queries, object retrieval, reading) can be done without an active transaction. The default in ObjectDB is true, indicating that only operations that modify the database require an active transaction. A false value enforces the use of an active transaction when reading from the database.

javax.jdo.option.NontransactionalWrite

Indicates whether or not persistent objects (i.e. objects that represent database objects) can be modified when no transaction is active. The default in ObjectDB is false, indicating that any attempt to modify a persistent object without an active transaction is not allowed and will cause a JDOUserException (a subclass of JDOException) to be thrown. A true value enables modifying persistent objects without an active transaction, but in this case changes are only performed in memory (they are never propagated to the database).

javax.jdo.option.RetainValues

Indicates whether or not persistent objects preserve their content after transaction commit. The default in ObjectDB is true, indicating that persistent object fields are the same before and after commit(). A false value causes persistent objects to become hollow on commit, causing all persistent fields to be assigned default values. The content of a hollow object is automatically reread from the database by JDO at the time the application tries to access it (unless NontransactionalRead is false and there is no active transaction - in which case an exception is thrown). Usually the only difference between the two modes is in performance, where the default true value is expected to be slightly more efficient.

javax.jdo.option.RestoreValues

Indicates whether or not, on transaction rollback, persistent objects are restored to their original content at the beginning of the transaction. The default in ObjectDB is false, indicating that persistent objects become hollow on rollback(). In this case, their content is reread from the database by JDO only when the application tries to access them again. A true value causes persistent objects to be restored to their original value at the time of transaction rollback. Usually the only difference between the two modes is in performance. If rollback() is not so common, turning this flag off (using the default false value) is expected to be more efficient.