Class Relationships and Persistence

One of the strengths of JDO is that it supports the persistence of related classes transparently. As mentioned previously, this lets us develop object models in Java as we normally would without having to make any special concessions for persistence.

There are two types of class relationships in Java:

  • Inheritance One class is a subclass of another

  • Composition One class contains another

Composition and Aggregation

Of the two types of class relationships, composition is the most common. Composition can take many forms; it can represent one-to-one relationships between classes, one-to-many relationships, and many-to-many relationships. The latter two cases, where the composition involves a collection, are also called aggregation.

An example of a one-to-one relationship is that a person may, on our model, have a single principal address. We could model that with a Person class that contains a reference to an Address class.

An example of a one-to-many relationship is that a person could have various hobbies. We could model that with a Person class that contains a vector to hold multiple instances of a Hobby class.

An example of a many-to-many relationship is that a person may belong to various clubs and a club can include many members. We could model that with Person class that contains a vector to hold multiple instances of a Club class and a Club class that contains a vector to hold multiple instances of the Person class.

As long as we create and manage them properly in Java, persisting object graphs that include complex relationships such as these present no problems for JDO. When we call the JDO PersistenceManager's makePersistent() method to persist an instance of the top-level class, all its child objects, the children of child objects, and so on that is, the object graph are persisted, as well. We do not need to persist each object individually.

Inheritance

Inheritance, as you are probably aware, is one of the key features of object-oriented languages. Inheritance promotes reuse because it allows us to take a class and create a new one that builds on the original class. For example, if we have a class, Person, we can make a subclass, Employee, that inherits from Person. Employee will automatically have all the attributes and methods of Person, so all we have to add in the Employee class are any additional methods and attributes specific to Employee, such as title and department.

Like the support for composition, the support for inheritance is transparent in JDO. If we have a Person class and create an Employee subclass, we can make the Employee class persistent, regardless of whether Person is persistent. The methods and attributes that Employee inherits from Person will be persisted together with those that are specific to Person.

A Photo Collection Example

We'll create a more complicated example in order to explore how we create and manage a more complicated object model using JDO.

Let's suppose that the members of a family are avid shutterbugs and have hired us to create a database to keep track of all the photographs they create. At the highest level, we'll group their photographs into PhotoSessions. This class will represent all the photos taken by one photographer at one time and place and, like the other classes in our model, will follow the conventions of a JavaBean.

 // PhotoSession package example; import java.util.Vector; import java.util.Date; public class PhotoSession {   private Photographer photographer;   private String location;   private Date sessionDate;   private Vector photos;   // getter methods   public Photographer getPhotographer()   {     return photographer;   }   public String getLocation()   {     return location;   }   public Date getSessionDate()   {     return sessionDate;   }   public Vector getPhotos()   {     return photos;   }   // setter methods   public void setPhotographer(Photographer photographer)   {     this.photographer = photographer;   }   public void setLocation(String location)   {     this.location = location;   }   public void setSessionDate(Date sessionDate)   {     this.sessionDate = sessionDate;   }   public void setPhotos(Vector photos)   {     this.photos = photos;   } } 

The PhotoSession object model is represented in Figure 12-2.

Figure 12-2. PhotoSession object model.

graphics/12fig02.gif

The PhotoSession class contains an instance of the class Photographer, which is pretty much like the Person class we created earlier in this chapter. We'll subclass Person to create the Photographer class and add a little information related to photography, such as the equipment used and a short description of the photographer's photographic experience.

 // Photographer, inherits from Person package example; public class Photographer extends Person {   private String equipment;   private String bio;   // getter methods   public String getEquipment()   {     return equipment;   }   public String getBio()   {     return bio;   }  // setter methods   public void setEquipment(String equipment)   {     this.equipment = equipment;   }   public void setBio(String bio)   {     this.bio = bio;   } } 

PhotoSession also contains a collection of photographs: a vector of Photo instances. The Photo class looks like this:

 // Photo package example; import java.util.Vector; public class Photo {   private String title;   private Vector subjects;   private String description;   private String ucDescription;   private String notes;   private PhotoSession photoSession;   // getter methods   public String getTitle()   {     return title;   }   public Vector getSubjects()   {     return subjects;   }   public String getUcDescription()   {      return ucDescription;   }   public String getDescription()   {      return description;   }   public String getNotes()   {     return notes;   }   public PhotoSession getPhotoSession()   {     return photoSession;   }   // setter methods   public void setTitle(String title)   {     this.title = title;   }   public void setSubjects(Vector subjects)   {     this.subjects = subjects;   }   public void setDescription(String description)   {      this.description = description;      ucDescription = description.toUpperCase();   }   public void setNotes(String notes)   {     this.notes = notes;   }   public void setPhotoSession(PhotoSession photoSession)   {     this.photoSession = photoSession;   } } 

The Photo class contains information about the photograph: title, description (including an uppercase version of the description to enable case-insensitive searching), and notes. It also contains a collection for information about the subjects in the photograph and a reference to the PhotoSession instance it belongs to, so we can navigate from any photo to the PhotoSession instance that contains it.

The Subject class is subclassed from Person but adds information relevant to being the subject in a photograph, specifically, age and a description of the subject's position in the photo.

 // Subject package example; public class Subject extends Person {   private String position;   private int age;   // getter method   public String getPosition()   {     return position;   }   public int getAge()   {     return age;   }   // setter method   public void setPosition(String position)   {     this.position = position;   }   public void setAge(int age)   {     this.age = age;   } } 

Notice again that there is nothing in these classes that is specific to JDO. After creating these source code files, we compile them normally using javac.

The Metadata File

To add persistence capabilities to our classes, as we've seen before, we need to create the metadata file that the enhancer will use. It contains descriptions of each class in our model, including information about inheritance and aggregation relationships.

 <?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>     <class name = "Subject"             persistence-capable-superclass = "example.Person">         <field name = "position"/>         <field name = "age"/>     </class>     <class name= "Photo">         <field name = "title"/>         <field name = "subjects">             <collection element-type = "example.Subject"/>         </field>         <field name = "description"/>         <field name = "notes"/>     </class>     <class name = "Photographer"             persistence-capable-superclass = "example.Person">         <field name = "equipment"/>         <field name = "bio"/>     </class>     <class name = "PhotoSession">         <field name = "photographer"/>         <field name = "location"/>         <field name = "sessionDate"/>         <field name = "photos">             <collection element-type = "example.Photo"/>         </field>     </class>   </package> </jdo> 

Note that to indicate inheritance, we add an attribute, persistence-capable-superclass, to the class tag in addition to the existing name attribute.

 <class name = "Photographer"        persistence-capable-superclass = "example.Person"> 

To indicate that a field is a collection, we use the container form of the field tag and include a collection tag indicating the type of objects in the collection.

 <field name = "photos">        <collection element-type = "example.Photo"/>       </field> 

graphics/note_icon.gif

You may remember that, in XML, an empty tag is one that has nothing between the opening and closing tag, such as <tagname></tagname> or the equivalent <tagname/>, whereas a container tag is one that includes data, text, or other tags between the open and close tags.

To create the persistence-capable classes, we follow the same steps as before: Compile the Java source files using javac.

 javac example\PhotoSession.java javac example\Photo.java javac example\Subject.java 

graphics/note_icon.gif

We don't explicitly have to compile all our Java files because javac can figure out some of the class dependencies and compile them automatically.

Next, we enhance the resulting .class files. The following command will enhance all the classes mentioned in the metadata.jdo file:

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

One of the drawbacks of the JDO 1.0 specification is that it does not specify support for evolution, that is, the ability to modify or extend an existing object model. Depending on the JDO implementation, the easiest (if not the only) thing to do may be to delete the existing schema and rebuild it from scratch.

Before using the LiDO DefineSchema utility to create the extended schema in Oracle, we'll drop the existing schema by dropping the user jdoexample. Log into Oracle as database administrator using SQL*Plus and enter the following command:

 DROP USER JDOEXAMPLE CASCADE; 

Next, recreate user jdoexample and grant rights to that user as before.

 CREATE USER JDOEXAMPLE IDENTIFIED BY JDOEXAMPLE; GRANT CONNECT, RESOURCE TO JDOEXAMPLE; 

To create the PhotoSession schema, run the DefineSchema tool using the metadata.jdo file.

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

Now we can write a program to test the PhotoSession classes. The first task is to acquire a Persistence Manager.

 package example; import javax.jdo.PersistenceManagerFactory; import javax.jdo.PersistenceManager; import javax.jdo.Transaction; import javax.jdo.Extent; import javax.jdo.Query; import java.util.Iterator; import java.util.Collection; import java.util.Vector; import java.util.Calendar; public class PhotoTestAdd {   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 persistence manager from persistence manager factory       PersistenceManagerFactory pmf = (PersistenceManagerFactory)           Class.forName(PMFCLASS).newInstance();       pmf.setConnectionDriverName(DBDRIVER);       pmf.setConnectionURL(DBURL);       pmf.setConnectionUserName(DBUSERNAME);       pmf.setConnectionPassword(DBPASSWORD);       PersistenceManager pm = pmf.getPersistenceManager(); 

We'll create several subjects.

 // Create subjects: name, city, contactInfo, position, age Subject rover = new Subject(); rover.setName("Rover"); rover.setCity("Anytown"); rover.setContactInfo(""); rover.setPosition("Center, front"); rover.setAge(8); Subject mom = new Subject(); mom.setName("Mom"); mom.setCity("Anytown"); mom.setContactInfo("mom@example.com"); mom.setPosition("Left"); mom.setAge(30); Subject dad = new Subject(); dad.setName("Dad"); dad.setCity("Anytown"); dad.setContactInfo("dad@example.com"); dad.setPosition("Right"); dad.setAge(30); Subject jr = new Subject(); jr.setName("Junior"); jr.setCity("Anytown"); jr.setContactInfo(""); jr.setPosition("Center, back"); jr.setAge(3); 

Now some Photo objects that include these subjects in vectors.

 // photo 1 // - includes subjects, title, description, notes Vector subjects1 = new Vector(); subjects1.add(rover); subjects1.add(mom); subjects1.add(dad); subjects1.add(jr); Photo photo1 = new Photo(); photo1.setTitle("Family at park"); photo1.setSubjects(subjects1); photo1.setDescription(    "Family standing next to picnic table"); photo1.setNotes("No flash, auto-timer"); // photo 2 Vector subjects2 = new Vector(); rover.setPosition("Left"); jr.setPosition("Right"); subjects2.add(rover); subjects2.add(jr); Photo photo2 = new Photo(); photo2.setTitle("Junior and Rover"); photo2.setSubjects(subjects2); photo2.setDescription(    "Junior and Rover playing ball"); // photo 3 Vector subjects3 = new Vector(); mom.setPosition("Center"); subjects3.add(mom); Photo photo3 = new Photo(); photo3.setTitle("Mom - the diva"); photo3.setSubjects(subjects3); photo3.setDescription(    "Mom singing that Shania Twain song"); photo3.setNotes("Flash"); Vector photoset1 = new Vector(); photoset1.add(photo1); photoset1.add(photo2); photoset1.add(photo3); 

Then a Photographer object.

 // Create Photographer - Dad Photographer photog1 = new Photographer(); photog1.setName("Dad"); photog1.setCity("Cambridge"); photog1.setContactInfo("dad@example.com"); photog1.setEquipment("Sony digital camera"); photog1.setBio("Amateur photographer"); 

At this point, we have all the pieces to create a PhotoSession object.

 // Create Photosession 1 PhotoSession sess1 = new PhotoSession(); sess1.setPhotographer(photog1); sess1.setLocation("Lakeside Park"); Calendar cal = Calendar.getInstance(); cal.set(2002, 3, 23); sess1.setSessionDate(cal.getTime()); sess1.setPhotos(photoset1); 

We also need to update the photos now that they've been assigned to a PhotoSession.

 // Add session reference to photos photo1.setPhotoSession(sess1); photo2.setPhotoSession(sess1); photo3.setPhotoSession(sess1); 

To make the PhotoSession object persistent, including all the child objects, we need to save only the parent.

 // Make PhotoSession 1 persistent tx.begin(); pm.makePersistent(sess1); tx.commit(); 

We'll add one more PhotoSession with a couple of photographs so we will have more material to query.

       // Photos - set 2       Photo mt = new Photo();       mt.setTitle("Cactus mountain and clouds");       mt.setDescription("Mountain,  salt flats, " +                          "cactus, and storm clouds");       mt.setNotes("About 30 minute exposure on handmade " +                    "photosensitive paper");       Photo still = new Photo();       still.setTitle("Cactus flower");       still.setDescription("Cactus with flower and pear");       still.setNotes("About 8 minute exposure on handmade " +                    "photosensitive paper");       Vector photoset2 = new Vector();       photoset2.add(mt);       photoset2.add(still);       // Create Photographer 2 - Mom       Photographer photog2 = new Photographer();       photog2.setName("Mom");       photog2.setCity("Cambridge");       photog2.setContactInfo("therealboss@example.com");       photog2.setEquipment("oatmeal box pinhole camera");       photog2.setBio("Kodak Instamatic School of Photography");       // Create Photosession 2       PhotoSession sess2 = new PhotoSession();       sess2.setPhotographer(photog2);       sess2.setLocation("Southern New Mexico");       cal.set(2002, 4, 10);       sess2.setSessionDate(cal.getTime());       sess2.setPhotos(photoset2);       // Add session reference to photos       mt.setPhotoSession(sess2);       still.setPhotoSession(sess2);       // Make persistent       tx.begin();       pm.makePersistent(sess2);       tx.commit();       pm.close();     }     catch (Exception e)     {       System.out.println("Caught e: " + e);     }   } } 
Querying Nested Classes

Querying classes that contain child classes is not very different than querying a simple class, such as the Person class earlier in this chapter. We usually start by specifying the extent.

 Extent extent = pm.getExtent(PhotoSession.class, true); 

The main difference is that when the class in question has attributes that are themselves objects, we may wish to refer to the attributes of the child objects in our query. In the case of single-value attributes that is to say, attributes of the parent class that can refer only to a single object we use dot notation. For example, suppose that we want to find a specific photographer's photo sessions. Each PhotoSession object has one instance of the Photo-grapher class, photographer, and we refer to the name attribute as photo-grapher.name. We can use this in a query filter like this:

 String filter = "photographer.name == selectedName"; 

If we want the query to include an attribute of PhotoSession that is a collection, on the other hand, such as the vector photos, we need to use one of the collection methods, contains() or isEmpty(). We would use isEmpty() to find PhotoSessions that have no photographs.

 String filter = "photos.isEmpty(); 

To find photographs that meet specific criteria, however, we need to use the contains() method together with a JDOQL variable. This is how we would locate a PhotoSession that contains a photograph with a specific title:

 String filter = "photos.contains(p) " +                 "&& p.title == selectedTitle"; 

In essence, the filter uses the JDOQL variable p to pluck out each Photo object from the vector photos and test to see whether that Photo object's title field matches the variable selectedTitle.

The rest of the code necessary to perform this query obtains the query object and declares the parameter selectedTitle and the variable p.

 Query q = pm.newQuery(ext, filter); pm.declareParameters("String n"); pm.declareVariables("Person person"); Collection sess = (Collection) pm.execute("Dad"); 

This method of locating specific objects in a collection can be extended to any arbitrary depth. For example, the Photo class contains a collection for the subjects. We can locate a PhotoSession that contains a photo that includes a specific person by using the following query:

 String filter = "photos.contains(p) " +                 "&& p.subjects.contains(s) " +                 "&& s.name == selectedPerson"; 

Here, we need to use two variables one, p, to obtain each photo from the photos vector in PhotoSession and another, s, to obtain each subject from the subjects vector in Photo. Again, we need to declare the variables and parameters to perform the query.

 q = pm.newQuery(ext, filter); pm.declareParameters("String selectedPerson"); pm.declareVariables("Photo p, Subject s"); Collection sess = (Collection) pm.execute("Cactus flower"); 
Navigating the PhotoSession Object

Once we've obtained our results in the form of a collection, we can iterate through the collection to obtain each individual PhotoSession object and print out information specific to that PhotoSession.

 Iterator iter = result.iterator(); while(iter.hasNext()) {   PhotoSession ps = (PhotoSession) iter.next();   System.out.println("Location: " + ps.getLocation());   System.out.println("Photographer: " +                       ps.photographer.getName()); 

To obtain information about the photos, we obtain an iterator for the vector photos and use it to iterate through them.

 Vector p = ps.getPhotos();  Iterator piter = p.iterator();  int i = 1;  while(piter.hasNext())  {    Photo currphoto = (Photo) piter.next();    System.out.println(" " + (i++) +". " +                       currphoto.getTitle());    System.out.println("    " +                        currphoto.getDescription()); 

Likewise, if we want to obtain information about the subjects, we need to obtain an iterator for the Photo class attribute subjects.

       Iterator siter = currphoto.getSubjects().iterator();       while(siter.hasNext())       {         Subject subject = (Subject) siter.next();         System.out.println("       " + subject.getName() +                              ", " + subject.getAge() +                              ", " + subject.getPosition());       }   } } 
Querying Dependent Objects

The queries in the previous section used the PhotoSession class for their extent. Even though we were able to query based on dependent objects, such as photos, the objects that were returned were PhotoSession instances. If we want to locate individual photos instead, we need to use the Photo class as the extent.

For example, to locate all photographs that contain the word Rover in their description, we would use the following extent and query:

 // Find specific photos: Extent extent = pm.getExtent(Photo.class, true); String filter = "ucDescription.startsWith("%" + desc)"; Query query = pm.newQuery(extent, filter);       query.declareParameters("String desc");     String searchString = "Rover";       Collection result =         (Collection) query.execute(searchString.toUpperCase()); 

Notice that the filter uses the SQL wildcard character (%), together with the startsWith() method to allow locating a string anywhere within the description. Also notice that the search is case-insensitive because it converts the search string to uppercase prior to comparing it with the uppercase version of the description, ucDescription. (See the Photo class above, especially the setDescription() method.)

Because the Photo class contains a reference to the PhotoSession object that contains it, we can locate the PhotoSession object, too, once we have the Photo object. Here, we display information about the photo obtained from both objects:

 Iterator iter = result.iterator();       Photo photo;       PhotoSession sess = null;       while(iter.hasNext())       {         photo = (Photo) iter.next();         System.out.println();         System.out.println("Title:    "  + photo.getTitle());         System.out.println("Desc:     "  + photo.getDescription());         sess = photo.getPhotoSession();         System.out.println("Location: "  + sess.getLocation());         DateFormat df = DateFormat.getDateInstance();         System.out.println("Date:     "  +                            df.format(sess.getSessionDate()));       } 
Using Java for Queries

JDOQL is adequate and easy to use for simple, common queries but for complex queries, it may be necessary to use JDOQL to perform a preliminary query in order to thin down the number of candidates, then use Java to perform the final selection from the resulting collection.

Be aware that performing queries using Java is significantly slower than using the underlying database via JDOQL. In some cases, you may be able to avoid using Java by planning ahead for your querying needs and including calculated fields that are maintained by the class's setter methods, such as the uppercase ucDescription field in the Person class above.

Modifying and Deleting Objects and Object Graphs

After obtaining an object reference by using a query, we can obviously use the information in it in the usual ways: to display it to the user, to perform calculations, or to locate other objects, such as child objects or parent objects, depending on how we've designed the object model. Once we've obtained an object reference to a persistent object, either from a query or by navigating from one object to another object, we can also change the information in the object or delete the object altogether.

Changing a Persistent Object

The only thing special we need to do to ensure that changes are reflected in the database when we change an object by using our class setter methods, for example is make sure that the change occurs within the boundaries of a transaction. To do that, we call the Transaction method begin() before the change and commit() at the end.

For example, suppose that we want to change the first instance of Rover to Rover the wonder dog wherever it appears in the description of a photograph. Assuming that we've obtained a collection result, as shown above, we could change each matching Photo object as follows:

 // Change "Rover" to "Rover the wonder dog" iter = result.iterator(); Transaction tx = pm.currentTransaction(); while(iter.hasNext()) {   tx.begin();   photo = (Photo) iter.next();   String s = photo.getDescription();   int i = s.indexOf("Rover");   int j = s.indexOf("the wonder dog");   if(i>=0 && j != i + 6)   {     s = s.substring(0, i + 5) + " the wonder dog" +     s.substring(i + 5);  }   System.out.println("Changed line to : " + s);   photo.setDescription(s);   tx.commit(); } 
Deleting a Persistent Object

As you might expect, the process of deleting a persistent object is basically the reverse of creating a persistent object. First, we need to call a method, deletePersistent(), to delete the object from the database, then we null out the object reference in our object model. Suppose that we've obtained a reference to a Photo object. We could delete the object with the following steps:

 tx.begin(); pm.deletePersistent(photo); photo = null; tx.commit(); 

But there is a major difference between persisting and deleting objects. When we created a persistent object, JDO dealt with the whole object graph for us, persisting any dependent objects automatically. This is not the case when deleting a persistent object with dependents, however. Deleting an object such as this Photo object will likely cause problems because it is probably owned by a PhotoSession object. Deleting the dependent Photo object will leave a PhotoSession object with a bad reference. To delete a persistent object properly, we need to take our object model into account, removing references in parent objects and deleting dependent objects as necessary.

In our PhotoSession application, because of the many-to-many relationships involved, we may wish to manage photographers and photo subjects separately from photo sessions. If this is the case, when we delete a PhotoSession object, we would want to delete the Photo objects in the collection of photographs that it contains (in vector photos) but not the Photographer object it references. We also would not want, when we delete the Photo objects, to delete the Subject objects that each Photo object references (in vector subjects). Given that, here is the code for deleting a PhotoSession object, assuming that we've already obtained a reference to one using a query:

 // Delete PhotoSession sess and dependent photos Vector  photos = sess.getPhotos(); Iterator phiter = photos.iterator(); Photo p; while(phiter.hasNext()) {   tx.begin();   p = (Photo) phiter.next();   pm.deletePersistent(p);   tx.commit(); } tx.begin(); pm.deletePersistent(sess); sess = null; tx.commit(); 

Another way of doing this is to put the code to delete the Photo objects in a callback method, jdoPreDelete(), in the PhotoSession class. (See the JDO specification for more information about callback methods.) When the PersistenceManager is preparing to delete an instance of the PhotoSession, it will call this method first. This callback method, together with the other callbacks such as jdoPreStore(), can also be used to maintain constraints in class relationships.

In addition, some JDO implementations may offer vendor-specific extensions that allow specifying cascading deletes in the metadata file so that when an object is deleted, dependent objects are deleted as well.

JDO implementations vary in the extensions they offer beyond the JDO 1.0 specification. There are ways in which JDO 1.0 can be found lacking and it's tempting to choose an implementation that makes things easier. But there is also a value to using only standard features, in case you later find other insurmountable problems, limitations or bugs in the implementation you've chosen, and need to change to another. In fact, if you decide to develop an application using JDO, it's probably not a bad idea to test it on more than one implementation, to make sure you're using only standard JDO.



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