From an application developers point of view JDO serves the same role as JDBC
but it has two main advantages over writing programs using JDBC:
JDO uses a datastore, which represents whatever is the source of the data that JDO will be using. This could be a relational or object database, for example. To access the datastore, JDO uses a class which implements the PersistenceManager interface. This class will be vendor specific. A class that implements the PersistenceManagerFactory interface is used to get the correct PersistenceManager for the datastore that you are accessing. Getting the correct PersistenceManagerFactory is dependent on properties that are supplied to the JDOHelper class.
An example of the JDO properties (used in the example code):
javax.jdo.PersistenceManagerFactoryClass=com.sun.jdori.fostore.FOStorePMF javax.jdo.option.ConnectionURL=fostore:database/fostoredb javax.jdo.option.ConnectionUserName=root
The example above specifies that the JDOHelper should find a PersistenceManagerFactory named com.sun.jdori.fostore.FOStorePMF, that it will access a datastore named fostoredb, which is found in the database directory, and that it should connect with the user id of root.
The properties are passed to the getPersistenceManagerFactory( ) static method of the JDOHelper class in the form of a Properties object. An example:
PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory(properties);
A PersistenceManager can then be obtained from the PersistenceManagerFactory. An example:
PersistenceManager pm = pmf.getPersistenceManager();
JDO uses an implementation of the Transaction interface to control transaction flow for all of its functions so you will need to obtain a Transaction object in order to use JDO. A Transaction object can be created using the PersistenceManager.
Transaction tx = pm.currentTransaction();
The download contains three jar files that you will need to add to your classpath:
In addition to these you will also need three other jar files to use the JDORI:
In order to create the datastore used in the examples, you will need to write a simple program that creates the datastore. If you are using a real implementation then you would not do this step and the properties you use to access the datastore will be different. Assuming that you have stored the properties listed above in a file called datastore.properties, your program would look something like this:
import java.io.*; import java.util.*; import javax.jdo.*; public class CreateDataStore { public static void main(String[] args) { try { Properties properties = new Properties(); InputStream stream = new FileInputStream("datastore.properties"); properties.load(stream); properties.put("com.sun.jdori.option.ConnectionCreate", "true"); PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory(properties); PersistenceManager pm = pmf.getPersistenceManager(); Transaction tx = pm.currentTransaction(); tx.begin(); tx.commit(); } catch (Exception e) { System.out.println("Problem creating database"); e.printStackTrace(); } } }
Notice that we are importing the package javax.jdo. All programs that use JDO will import this package. Notice that in addition to the properties listed above we added one more entry to the Properties object that causes the datastore to be created. As per the javax.jdo.option.ConnectionURL entry in the datastore.properties file, the datastore will be created in a directory called datastore and will be given the name fostoredb.
package com.javaranch.jdotest; public class Book { private String title; private String author; private String isbn; private float price; public Book() {} public Book(String title, String author, String isbn, float price) { this.title = title; this.author = author; this.isbn = isbn; this.price = price; } public String getTitle() { return title; } public String getAuthor() { return author; } public String getIsbn() { return isbn; } public float getPrice() { return price; } public void setTitle(String title) { this.title = title; } public void setAuthor(String author) { this.author = author; } public void setIsbn(String isbn) { this.isbn = isbn; } public void setPrice(float price) { this.price = price; } }
Before we can use this class we need to run the class file through a JDO enhancer. The enhancer will add methods and variables to the class in order to make it manageable by the JDO implementation. Running through the enhancer can be done from the command line or as part of an ANT script. I will show the commands for each method.
Before you can enhance the class file you will need to create an XML file that is used by the enhancer. This file identifies the classes that you will use and also allows you to specify special field requirements if required. The file should be stored in the same directory as the class file and should be named package.jdo. For our example, the file would look like this:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE jdo PUBLIC "-//Sun Microsystems, Inc.//DTD Java Data Objects Metadata 1.0//EN" "http://java.sun.com/dtd/jdo_1_0.dtd"> <jdo> <package name="com.javaranch.jdotest" > <class name="Book" /> </package> </jdo>
An entry must be placed in this file for each class you wish to enhance. Once you have created this file and compiled your source, you are ready to start the enhancement. Enhancement in JDORI is done using a class named com.sun.jdori.enhancer.Main. The enhancer in another implementation will be different (or may not be required).
From the command line, you would do the following:
java com.sun.jdori.enhancer.Main -s classes classes/com/javaranch/jdotest/Book.class
This assumes that we have stored our class file in a directory called classes. The result will be that the class file has been overwritten with an enhanced version.
To do the same thing from an ANT script we would add an ANT task:
<!-- ===================================== --> <!-- Enhance --> <!-- ===================================== --> <target name="enhance" depends="compile" > <java fork="true" classname="com.sun.jdori.enhancer.Main"> <arg line="-s classes classes/com/javaranch/jdotest/Book.class" /> </java> </target>
I have found that you need to run the task with fork="true" because of a problem with Xerces that causes the task to fail otherwise.
InputStream stream = new FileInputStream("datastore.properties"); Properties properties = new Properties(); properties.load(stream); PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory(properties); PersistenceManager pm = pmf.getPersistenceManager(); Transaction tx = pm.currentTransaction();
This code will be the same for any JDO implementation. The only difference will be the properties used to create the PersistenceManagerFactory. We can now look at some examples of using JDO.
First, let's create a Book object and add it to our datastore:
Book book = new Book("Benjamin Franklin : An American Life", "Walter Isaacson", "0684807610", 18.00f); tx.begin(); pm.makePersistent(book); tx.commit();
The makePersistent( ) method will cause the Book object to be stored in our datastore.
Suppose we want to fetch this object from the datastore:
tx.begin(); Query q = pm.newQuery(Book.class, "isbn == \"0684807610\""); Collection result = (Collection) q.execute(); Book book = (Book)result.iterator().next(); q.close(result);
A Query object is used to select objects from the datastore. Creating the Query object requires that we pass a Class object representing the table we wish to select from and a filter representing the criteria used to select objects from the datastore. JDO Query Language (JDOQL) is used to create filters.
Notice that we did not commit the transaction. This was deliberate. Since we have the object, we can make changes to the object and when we commit, those changes will be persisted to the database. For example, suppose we want to update the title of this book:
book.setTitle("Paul Wheaton : An American Life"); tx.commit();
This change will be automatically written out to our datastore. Deleting our book is also simple. While we have an active transaction we can simply do this:
pm.deletePersistent(book); tx.commit();
In addition to the commit( ) method of the Transaction object, there is also a rollback( ) method that can be used to cancel any transaction.