Java Data Objects

JDO is a standard for making Java objects persistent. As a product of Sun's Java Community Process, it represents the collaboration of developers and vendors with interests in relational databases, object databases, and object-relational mapping technologies.

JDO is not an object-relational mapping technology per se, but rather specifies an API that can be used to manage the persistence of objects without regard to the persistence mechanism, which can be anything from a file-based system to a true object database. Because most JDO implementations use a relational database, this means that these implementations are, in fact, providing object-relational mapping. The beauty of JDO is that Java developers don't have to worry about any of these details JDO provides transparent persistence. With JDO, you can design everything, including persistent classes, within an object model using standard object design techniques, including inheritance and composition.

No extra Java code is needed to implement persistence. But there is no magic. Instead, a metadata file in XML format is used to declare persistent classes and, optionally, their fields. A special tool called an enhancer is used to process the Java classes after compilation to add the methods and attributes, based on the metadata file, that JDO requires in order to provide persistence behind the scenes, as shown in Figure 12-1.

Figure 12-1. JDO class enhancement.

graphics/12fig01.gif

JDO Implementation Setup

The examples in this chapter use LIBeLIS LiDO Professional Edition JDO version 1.2 and Oracle 9i. LIBeLIS and most other JDO vendors offer either limited-time evaluations of their products or community editions that are free for personal and development purposes. Any of these products supporting the JDO 1.0 specification should work with the code presented but see the product information for specifics on the tools that they provide, such as the enhancer and runtime library, and how to set them up and run them.

After installing LiDO in the directory of your choice, open a DOS window and locate and run the lidoEnv.bat file in the LiDO bin directory. If you've installed LiDO in c:\dev\LiDOPro, for example, you would enter the following command:

 d:\dev\LiDOPro\bin\lidoEnv.bat 

This will add the LiDO JDO development tool and runtime classes to your CLASSPATH, as well as set up several other environment variables that LiDO needs. You will need to run this batch file anytime you open a new DOS window to compile or run Java programs using LiDO JDO.

Creating a Persistent Class

There are no special steps required to create the persistent class itself. We'll begin by taking a simple example, a simplified Person class, with attributes for name, city, and contact information. We'll use the JavaBean convention of making our attributes private and providing getter and setter methods.

 // Person package example; public class Person {   private String name;   private String city;   private String contactInfo;   public Person()   {   }   // Constructor   public Person (String name, String city, String contactInfo)   {     this.name = name;     this.city = city;     this.contactInfo = contactInfo;   }   // getter methods   public String getName()   {     return name;   }   public String getCity()   {     return city;   }   public String getContactInfo()   {     return contactInfo;   }   // setter methods   public void setName(String name)   {     this.name = name;   }   public void setCity(String city)   {     this.city = city;   }   public void setContactInfo(String contactInfo)   {     this.contactInfo = contactInfo;   } } 

Assuming we are using a directory c:\mywork, this file should be in a subdirectory called example because it is declared to be in a package called example:

 c:\mywork\example\Person.java 

Compile it normally using the following command in the c:\mywork directory:

 javac example\Person.java 
Creating a Metadata File

Before we can run the enhancer to alter the class file and make it persistent, we need to create an XML metadata file that describes the class. This file contains several standard lines indicating the XML version, the DTD file that is used to validate the format of the XML file, and the <jdo> root element that must contain our metadata. The <jdo> element contains the metadata for our class and includes the package name, the class name, and the field names:

 <?xml version="1.0"?> <!DOCTYPE jdo SYSTEM "jdo.dtd"> <jdo>   <package name = "example">     <class name="Person">         <field name="name"/>         <field name="city"/>         <field name="contactInfo"/>     </class>   </package> </jdo> 

This file should be saved in the c:\mywork directory, as person.jdo, for example.

Running the Enhancer

To run the LiDO enhancer, at a DOS prompt in the c:\mywork directory, run the following command:

 java com.libelis.lido.Enhance -metadata person.jdo 

Notice that it is unnecessary to specify the class file that is to be enhanced or its location; the enhancer works this out from the information in the metadata file.

After the enhancer has been run the Person class file no longer corresponds to the Java source file. Most importantly, the enhancer modifies the class bytecode so that it implements the interface PersistenceCapable. This is one of the most controversial things about JDO. It's possible to accomplish the same end by using a preprocessor to modify the source code, but this approach is messier and is discouraged by the JDO specification.

Creating the Database Schema

Unlike the enhancer, the tools for creating the database schema are not specified in the JDO specification. This is the step that will vary most from vendor to vendor and that will become easier in the future as JDO implementations continue to evolve and innovate.

We'll create the database schema based on the metadata file by using a LiDO-provided command-line tool, DefineSchema.

First, we'll create a new user in Oracle. In SQL*Plus, log in as database administrator or have the database administrator perform the following steps for you. Create a user jdoexample with a password jdoexample, as follows:

 CREATE USER JDOEXAMPLE IDENTIFIED BY JDOEXAMPLE; 

Grant the user the following rights:

 GRANT CONNECT, RESOURCE TO JDOEXAMPLE; 

To create the schema, at the DOS prompt, enter the following command on one line (it's broken up here for legibility):

 java com.libelis.lido.ds.jdbc.DefineSchema -dv oracle.jdbc.driver.OracleDriver -db jdbc:oracle:thin:@noizmaker:1521:osiris -u jdoexample -p jdoexample -m person.jdo 

Note that you have to modify -db, the option that specifies the database URL, in accordance with your database's hostname and database name. After entering this command, a message will be displayed, indicating whether the schema was successfully created. You can verify this by logging in as jdoexample in SQL*Plus and entering the following query to see the tables that were created:

 SQL> connect jdoexample/jdoexample Connected. SQL> select * from cat; TABLE_NAME                     TABLE_TYPE ------------------------------ ----------- E_PERSON                       TABLE LIDOIDMAX                      TABLE LIDOIDTABLE                    TABLE 

We can see that LiDO has created a table, E_PERSON, to hold the Person class data, as well as a couple of other tables to hold identity information about objects.

Creating and Persisting an Object

Once we've created a persistent class, we can instantiate objects of the class just as we normally would. But to make them persistent, so they are saved in the database, we need to obtain a reference to a PersistenceManager and call its makePersistent() method.

The PersistenceManager

The principal JDO class is the PersistenceManager. This class has methods that allow us to manage persistent classes, as we'll see now, and methods for obtaining Query and Transaction objects, as we'll see later.

We obtain a PersistenceManager from a PersistenceManagerFactory. Like other such factories, PersistenceManagerFactory can accept settings either from a properties file with name-value pairs or from a variety of setter methods.

Here, we'll use the setter methods. The exact settings that PersistenceManagerFactory requires depend on the configuration but here are the settings corresponding to LiDO and my Oracle database:

 import java.util.Iterator; import java.util.Collection; public class PersonTest {   public static void main(String [] args)   {     // Configuration parameters     String PMFCLASS =            "com.libelis.lido.PersistenceManagerFactory";     String DBDRIVER = "oracle.jdbc.driver.OracleDriver";     String DBURL    = "jdbc:oracle:thin:@noizmaker:1521:osiris";     String DBUSERNAME = "jdoexample";     String DBPASSWORD = "jdoexample";     try     {       // Obtain PersistenceManager from PersistenceManagerFactory       PersistenceManagerFactory pmf = (PersistenceManagerFactory)           Class.forName(PMFCLASS).newInstance();       pmf.setConnectionDriverName(DBDRIVER);       pmf.setConnectionURL(DBURL);       pmf.setConnectionUserName(DBUSERNAME);       pmf.setConnectionPassword(DBPASSWORD); 

Once the PersistenceManagerFactory is set up we can obtain the PersistenceManager.

 PersistenceManager pm = pmf.getPersistenceManager(); 
Transactions

Persistence operations in JDO are grouped into a unit of work or transaction. Unlike JDBC, this is not an option that we can enable to occur automatically; in JDO, we must explicitly manage our transactions. To do this, we first need to obtain a Transaction object that will allow us to mark the transaction boundaries by calling the object's begin() and commit() methods.

We obtain the transaction object and begin the transaction as follows:

 // Create and begin transaction Transaction tx = pm.currentTransaction(); tx.begin(); 
Creating the Person Object

This is the code for creating the object:

 // Create and persist Person object Person person = new Person(); person.setName("Bruce"); person.setCity("Asbury Park"); person.setContactInfo("theBoss@example.com"); 
Persisting the Object

This is the code that makes the object persistent:

 pm.makePersistent(person); tx.commit(); 

Note that the call to makePersistent() makes the object persistent but it is not made permanent until the transaction is committed. If this were part of a larger set of code, tx.commit() might not be called until later, pending the success of the other operations.

At this point, you might want to query the E_PERSON table in the database by using SQL*Plus to see that this has, in fact, been added.

Locating and Using Existing Objects

Every persistent object has a unique identifier that is maintained, generally behind the scenes, by JDO. This identifier, the ObjectId, is like a table's primary key in a relational database. Although a JDO implementation for a relational database may, in fact, use a primary key in a table for the ObjectID, it is not required to do so.

If we already have a persistent object, we can obtain its ObjectId by calling the getObjectId() method.

 Object objId = person.getObjectId(); 

If we have an ObjectId, we can use it to obtain the object.

 Person person = (Person) pm.getObjectById(); 

However, except for cases where the application manages ObjectIds itself, this presents a chicken-and-egg problem. ObjectIds are best left as an internal detail for JDO to manage. It is useful to know that every persistent object has an ObjectId, but the details of what ObjectIds are and how they are managed by JDO are not important from an application point of view.

We typically will use queries to locate our objects instead. To execute queries, there are two classes that we need to be familiar with: Extent and Query.

Extents and Queries

An extent represents a set of objects in the database and is similar to a collection except that it has been optimized for use in JDO, where the number of objects that it represents can be arbitrarily large. Because an extent does not retrieve all objects from the database at once, it does not have all the methods of the collection interface such as, for example, a size() method.

Extent has an iterator() method that we can use to retrieve objects from the database like this:

 // Extent Extent ext = pm.getExtent(Person.class, false); Iterator iter = ext.iterator(); while(iter.hasNext()) {   Person p = (Person) iter.next();   System.out.println("   Name: " + p.getName());   System.out.println("   City: " + p.getCity());   System.out.println("Contact: " + p.getContactInfo()); } 

An extent is an easy way to obtain all the objects of a given type. More often, however, we are interested in retrieving only objects meeting certain criteria. To do that, we need to use a query.

Queries and the JDO Query Language: JDOQL

The job of a JDO query is to take a set of candidate objects and filter them, returning only those that meet our criteria. We can specify the candidate objects for a query by providing either an extent or a collection of objects (which perhaps we've obtained from a previous query).

In addition to providing an extent or a collection, we usually also provide a filter. (If we don't provide a filter, the query will return all instances of the specified object type.) The filter is a string representing a Boolean value using syntax specified by JDOQL. The rules for forming a JDOQL expression are the same as those for Java and they use standard Java syntax, with a few exceptions.

Equality and inequality operators are the same as Java but they can be applied not only to primitives, such as int and float, but also to instances of the wrapper classes for primitives, such as Int and Float. If we have a class, Circle, which had an attribute radius, we could locate all instances of Circle with radius greater than 3 by using the following filter:

 String filter = "radius > 3"; 

Equality and inequality operators can also be applied to strings. For example, we could use the following filter to locate all instances of Person named Bruce:

 String filter = "name ==\"Bruce\""; 

Although the JDO specification does not indicate how string comparisons are to be implemented, it is clearly intended to have different semantics than standard Java comparisons using String. A comparison between two strings using the equality operator is valid in Java but it's not usually what we mean to do and often has surprising results. The Java equivalent of the JDOQL statement above is name.equals("Bruce").

We cannot use assignment operators, post- or pre-increment operators, or post- or pre-decrement operators.

We cannot call Java methods, with the following exceptions:

  • Collection.contains(Object o)

  • Collection.isEmpty()

  • String,startsWith(String s)

  • String.endsWith(String s)

The JDO specification notes that String.startsWith() and String.endsWith() support wildcard characters but does not define what they are. In an implementation that is based on a relational database that uses SQL, it's safe to assume that the underlying implementation uses the SQL LIKE operator for comparison, which accepts (%) to match any character or character and (_) to match any single character. The following JDO filter and SQL WHERE clauses are equivalent:

 String filter = "name.startsWith(\"abc\")"; ...WHERE name LIKE 'abc%' 

Likewise, the following are equivalent:

 String filter = "name.endsWith(\"abc\")"; ...WHERE name LIKE '%abc' 

Knowing this, we can include wildcards of our own. We can define a JDO filter like this:

 String filter="name.startsWith(\"%efg\")"; 

This will match a string that contains efg anywhere within it, such as abcdefghijk.

Filter Parameters

In the filter examples above, the comparisons contained the hard-coded values 3 and Bruce. We wouldn't normally do this, of course. We would instead use a JDOQL parameter an identifier that acts as a placeholder for a value that we pass in when we execute the query at runtime. We might use the parameter selectedName (we'll see in a minute where this is declared) in a filter like this:

 String filter = "name == selectedName"; 

We need to declare our parameter (or parameters, if there are more than one), but first we need to obtain our Query object. We do that with our extent and filter, as follows:

 Query q = pm.newQuery(ext, filter); 

To declare a parameter, we call Query's declareParameters() method with the declaration in a string, using the same syntax we would use to declare a parameter list for a Java method. This is the declaration for the parameter selectedName:

 q.declareParameters("String selectedName"); 

If our query used multiple parameters, the parameters would be separated by commas, like this:

 q.declareParameters("String selectedName, int age"); 

When we execute a query it returns a collection, but it returns it as type Object so we need to cast it to Collection. (This is to leave open the possibility that a future version of JDO may return something other than a collection.) Assuming that the query is expecting a single string parameter, we can execute it like this:

 Collection result = (Collection) q.execute("Bruce"); 

You may wonder how we would pass multiple parameters. If there are three or fewer parameters, we can simply pass them directly to execute() methods with the following signatures:

  • execute()

  • execute(Object o1)

  • execute(Object o1, Object o2)

  • execute(Object o1, Object o2, Object o3);

The parameters in the call to execute() must correspond to the parameters we declared in the declareParameters() method. For example, if our filter uses two parameters, name and city, in that order, the declareParameters() and execute() methods must correspond as in the following example where name and city are presumed to be valid Java variables in our application:

 q.declareParameters("String selectedName, String selectedCity"); Collection result = (Collection) q.execute(name, city); 

If we want to pass more than three parameters, we can pass any number by putting them in an object array and calling the executeWithArray() method:

 executeWithArray(Object [] objs) 

The query execute() method returns a collection of Java objects in the case of this sample code, instances of the Person class. We can iterate through them just like we did for the instances we retrieved using the extent's iterator.

 iter = result.iterator(); while(iter.hasNext()) {   Person p = (Person) iter.next();   System.out.println("   Name: " + p.getName());   System.out.println("   City: " + p.getCity());   System.out.println("Contact: " + p.getContactInfo()); } query.close(result); // takes reference to Collection pm.close(); 

Note that we need to close the Query and the PersistenceManager because they may have acquired resources that need to be freed.

Variables

In addition to parameters, there is a second special type of element that we can use in a filter: the variable. The semantics of JDOQL variables do not correspond to variables in Java because JDOQL is declarative and not procedural. JDOQL variables are perhaps better thought of as a way of correlating parts of a query and, in particular, queries that include classes containing collections and the individual members of those collections. We'll take a look at variables in the next section when we consider querying nested classes.

Ordering Results

To order the results of queries, we can use the query's setOrdering() method. This takes a String parameter that lists the attributes to use as sort keys, each followed by either ascending or descending, in descending order of priority.

Multiple attributes are separated by commas. Valid attribute types are:

  • Primitive types such as int and float, but not boolean

  • Wrapper classes, such as Int and Float, but not Boolean

  • BigDecimal

  • BigInteger

  • String

  • Date

The following example selects all persons whose names begin with the letter A and lists them, sorted with city as primary sort key and name as the secondary sort key:

 // Query ordering String filter = "name.startsWith(letter)"; Query q = pm.newQuery(ext, filter); q.declareParameters("String letter"); q.setOrdering("city ascending, name ascending"); Collection coll = (Collection) q.execute("A"); 


Java Oracle Database Development
Java Oracle Database Development
ISBN: 0130462187
EAN: 2147483647
Year: 2002
Pages: 71

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net