13.1 Data Modeling


Although JDO does not prescribe any specific technical requirements for Java classes to be made persistent, such as the presence of certain methods or a common superclass, and most arbitrary Java classes can be made persistent using JDO, certain best practices for modeling persistent classes do exist. [1]

[1] As explained earlier, it is not entirely correct to state that all classes can be made persistent; certain system-type classes such as System, Thread, Socket, and File can in fact not be JDO persistence-capable. Most user -defined classes can be however, and so for most practical applications, this subtle point can be ignored. (One would rarely hold references to or subclass a Thread or similar class in a data object class.)

In essence, experience has shown that it is best to separate persistence-capable classes from the application-logic classes. One benefit with this approach is that the data model and more importantly the underlying data can be reused across applications.

13.1.1 Introduction

Translating this into a concrete coding best practice could sound like this: "Model business domain concepts as straightforward entity-type JavaBean-style [2] classes, with private fields and ubiquitous public 'getter/setter' (accessor/mutator) methods for fields." For example:

[2] Note that when we say JavaBean-style we do mean JavaBeans as per the JavaBeans specification (http://java.sun.com/javabeans/docs/spec.html), not Enterprise JavaBeans such as Entity and Session Enterprise beans. In some applications it may make sense to "fa §ade" the plain Java data object classes with Session Enterprise beans, and a separate chapter deals with this in more detail. Whether the JavaBean-style classes have a Session Enterprise bean "fa §ade" does not influence the design of them.

 
 private String name; public void setName(String _name) { this.name = _name; } public String getName() { return this.name; } 

All well-behaved JavaBeans should have a public no-arguments constructor and, as discussed earlier, so must all persistence-capable classes. Persistence-capable classes also always must have a no-argument public constructor, and often this is the only constructor. This is usually the default constructor.

13.1.2 Wrapper versus primitive types

For most applications, it is best to limit the types of fields. In addition to fields of type String as seen above, Java primitive ( boolean , byte , short , int , long , float , double ) or wrapper ( Boolean , Byte , Short , Integer , Long , Float , Double ) types are often used in persistent classes. Wrapper classes are preferable to primitive types if null values need to be expressed explicitly in the model. [3] For example:

[3] The XML metadata descriptor for such fields needs to have the null-value attribute of the field tag set to "none", which is the default for storing Java null values as null in the respective underlying datastore. (So just make sure it does not read <field null-value="exception"> or "default" )

 
 private Integer age;  // Note, CAN be null public void setAge(Integer _age) { this.age = _age; } public Integer getAge() { return this.age; } 

If such fields are always set to a value with true business meaning, and never null (null as in the Java null value, not the zero '0' value), then Java primitive types may equally well be used, because support for persistence of them is mandated by the JDO specification and is well supported in implementations .

 
 /** Confirmed is always 'true' or 'false' values, never   * null. Use uppercase Boolean wrapper if tristate (true,   * false, null) required for this application   */ private boolean confirmed; public void setConfirmed(boolean _c) { confirmed = _c; } public boolean getConfirmed () { return this.confirmed; } 

13.1.3 References to persistent objects

Last but not least, of course, references to other persistent classes are often used:

 
 private Address address; public void setAddress(Address _a) { this.address = _a); } public Address getAddress() { return this.address; } 

The setter/getter pair can be named either after the type of the persistent field, like Address above, to keep things simple in an obvious scenario, or after a field name clearly indicating the significance of the associated persistent object, particularly if there are several fields of the same type. For example:

 
 private Book original; public void setOriginal(Book _orig) { original = _orig; } public Book getOriginal() { return this.original; } 

In the above code snippets, both Address and Book are other persistence-capable classes. Note that, although fields of type java.lang.Object are technically permitted by the JDO specification and some but not all JDO implementations support this, it often makes more sense, makes code more robust, and in many cases is perfectly possible to name a concrete class, possibly a persistence-capable superclass of a completely persistent class hierarchy.

Furthermore, while the JDO specification again permits fields of Java interface types, instead of concrete implementing classes, this possibility is generally not often used. Remember that there is no notion of persistence-capable interfaces in JDO, so it is not possible, for example, to query on all persistent objects implementing a specific interface. More importantly, many JDO implementations ( especially object-relational mapping-based ones) require that the type of the actual implementing class be specified in the XML persistence mapping descriptor. So, in practice, only instances of specific classes (or their subclasses) can be set for such fields; using concrete classes that do implement the interface specified by the field and method signatures will compile, but such JDO implementations will throw runtime exceptions if the classes do not match the contract established in the mapping descriptor. In summary, it is often preferable to use a concrete class, possibly a parent class in a hierarchy of persistent classes, instead of an interface.

Last but not least, although the referenced class is not strictly required to be another persistence-capable class, refer to the earlier example showing the use of JDOHelper.makeDirty() when modifying a reference to a non-persistence-capable class; most well-designed real-world domain models have only references to concrete other persistence-capable classes.

13.1.4 Collection-type fields

So far, only cases with references to other classes with "cardinality one" have been shown. However, most real-world applications will also have fields of "cardinality N" multi-references to other classes.

As seen in previous chapters, JDO offers many possibilities to model such a relationship. Java arrays (e.g., Book[] ) could in theory be used, but they require special attention as we saw earlier. In practice, some sort of java.util.Collection is usually the way to go for most applications' persistent data models. People with relational data modeling backgrounds often find a Set the most familiar type, and support for the HashSet implementation is required in all implementations by the JDO specification.

However, note that a JDO-based persistence model can technically use Lists (which retain order) and Maps (key/value pairs) for associations as well. Support for Set is a required JDO feature and is supported by all implementations, while List and Map are optional. For example:

 
 private Set books = new HashSet(); public void setBooks(Set _books) { this.books = _books; } public Set getBooks() { return this.books; } 

If the business requirements for your model mandate that insertion order of associations needs to be maintained , thus making you tempted to use a List , or you need a Map and many application domain models (especially those ported from a relational schema) do not, you may wish to read up on the documentation of and play with your JDO implementation of choice before drawing up a large data model and making a final decision in this matter.

Moreover, simple addXYZ() and removeXYZ() mutator methods are sometimes found on persistence-capable classes, mostly for convenience:

 
 private Set books = new HashSet(); public void setBooks(Set _books) { this.books = _books; } public Set getBooks() { return this.books; } public void addBook(Book _book) { books.add(_book); } public void removeBook(Book _book) { books.remove(_book); } 

Note that for some JDO implementations, the exact class for a Collection -type field may be fixed. Using an assignment-compatible subclass could lead to run-time exceptions when using such implementations. A method like setBooks() could thus lead to trouble in certain cases, e.g., when an application would do something like this:

 
 author.setBooks( new java.util.LinkedHashSet() ); 

If for this or other reasons a developer wanted to enforce usage of the above type-safe add/remove methods and disallow directly modifying the Collection returned by a getter, using Collections.unmodifiableCollection() in the getter and no setter could enforce this, for example:

 
 private Set books = new HashSet(); public Set getBooks() {   return Collections.unmodifiableCollection(books); } public void addBook(Book _book) { books.add(_book); } public void removeBook(Book _book) { books.remove(_book); } 

Note also that an application cannot assume that it knows the exact class actually used for instances of collection-type fields. JDO implementations may choose to treat collections as second-class objects (SCO) and may substitute them by a JDO implementation-specific subclass of the respective type, although it is guaranteed that the actual class is assignment compatible.

Second-class object substitution of Collection -type attributes by JDO implementations at runtime is also the reason why it generally does not make sense (and is asking for trouble or at least for a performance penalty) to actually declare a persistent List field as an ArrayList or LinkedList , instead of just a List or a Set field (a HashSet , for example) instead of just a Set :

 
 // Bad practice, why force a HashSet in field declaration?! private HashSet books = new HashSet(); 

That's because in each case, the JDO runtime will populate a field declared as Set with an object of some internal implementation class that either extends HashSet or implements Set , respectively, yet is backed by the database. For a variety of reasons, a class that extends HashSet has fewer opportunities to optimize its database access than one that fully supplies its own implementation of Set . For example, the contents of a HashSet must always be fully loaded into memory.

In summary, it is generally a best practice to use a high-level interface-type ( Set , List , and so on) instead of a specific implementation in the declaration of fields in persistent classes.

13.1.4.1 Heterogeneous versus homogenous object containers

The standard implementations of Java collection classes are heterogeneous object containers, meaning that objects of any class can be inserted into them. In JDO applications, however, the class of objects that can be added as elements to a Collection is usually constrained by an element-type field of the collection tag in the XML metadata descriptor.

This subtle difference does not usually cause any issues in real-world applications, but is an interesting point to keep in mind. Although some JDO implementations that support fields of type java.lang.Object are likely to support fully heterogeneous collections, it is not recommended to rely on such an implementation's feature for applications intended to be portable across JDO implementations. It often makes more sense, makes code more robust, and is in many cases perfectly possible, to name a concrete class, possibly a persistence-capable superclass of a completely persistent class hierarchy, as an element-type for collection fields.

In a way, this discussion is not specific to JDO as such, but really applies to developing under Java in general. If you think about it, many of your applications actually store only elements of a certain class in a collection. However, this is only implied , you know about it, don't usually make a mistake with this, may mention it in the JavaDoc, but Java does not currently provide a way to enforce this. [4] (Short of writing your own Collection wrapper classes, including type-safe Iterator wrappers, with new methods, named differently because Java does not currently provide covariant return types. This is relatively cumbersome and not typically done; we generally rely on the collections framework as it comes out of the box, which, thanks to polymorphism and all objects implicitly deriving from one common system Object superclass, works fairly well. People with a background in the programming language Smalltalk often find this a non-issue and don't understand all the fuss about compile-time checking, while people with a C++ background used to template syntax and the STL find this weird.)

[4] One could technically of course write a Collection wrapper with add(), remove() and other methods overloaded to be type-safe and take the specific class as argument, instead of the generic Object. This is however rarely done in practice, one reason being that without real templates this quickly becomes cumbersome to hand-write, and another more important one making this difficult is Java's lack of "covariant return types," meaning overridden methods in subclasses are not allowed to have a return type that is a subclass of the parent method return type.

Because this is an issue with the Java language in general, preparations are under way to include a solution to this, known as Java Generics, see JSR 14, [5] in a future JDK release, possibly JDK 1.5.

[5] See http://jcp.org/aboutJava/communityprocess/review/jsr014/index.html, and http://developer.java.sun.com/developer/earlyAccess/adding_generics/

It would be perfectly logical for JDO to take advantage of and leverage a standard type-safe collections framework for persistence.

13.1.5 Inverse relationships modeling

In Java, and in most other programming languages, associations between objects are directed. For example, an Author may have a books field. However, the pointed-to persistent Book objects generally do not have inverse associations back to the Author . This may seem an obvious and trivial statement, particularly from a Java developer's point of view; however, this is not as natural as it initially looks: In datastores such as relational databases, and thus particularly from your DBA's point of view, a relationship between two persistent entities can be navigated and identified from either way around.

It is, of course, perfectly possible to write an explicit JDOQL query on a datastore and retrieve, for example, the Authors that point to a given book. This query may even be implemented in a convenience method of the Book class, in code that would look something like this:

 
 public class Author {    private Set books;    // ... } public class Book {    // ...    public Author getAuthor() {       PersistenceManager pm = JDOHelper.                                getPersistenceManager(this);       Extent e = pm.getExtent(Author.class, true);       Query q = pm.newQuery(e, "books.contains(thisBook)");       q.declareParameters("the.package.of.Book thisBook");       Collection results = (Collection)q.execute( this );       Author author = null;       if ( results.iterator().hasNext() ) {         author = results.iterator().next();         }       q.close(results);       return author;    } } 

It is equally possible to have an explicit mirror field in Book for the Author (or Authors, depending on the logical model) that point to it. Such a mirror field would be redundant from a data management point of view, but it may still make sense for a completely portable JDO application. However, either an application or the model classes themselves would have to take extra care to always update the field on Book author field and the Author books field together. (This approach is similar to how a traditional double-linked list is implemented.)

 
 public class Author {    private Set books;    // ...    public void addBook(Book newBook) {       newBook.getAuthor().removeBook(newBook);       books.add(newBook);       newBook.setAuthor(this);    }    public void removeBook(Book aBook) {       books.remove(Book aBook);    }    // ... } public class Book {    private Author author;    // ...    /* package-local */ void setAuthor(Author author) {       this.author = author;    }    public Author getAuthor() {       return author;    }    // ... } 

If the domain model of your application could benefit from modeling such inverse relationships and you are willing to trade in portability to other JDO implementations, it is worth reading to determine if your JDO implementation of choice already supports this out of the box. The JDO specification itself shows an example of how this may look:

 
 class Employee {    Department dept;    (...) } class Department {    Collection emps;    (...) } <class name="Employee" (...)>    <field name="dept"> <extension vendor-name="sunw" key="inverse" value="emps"/>    </field> (...) </class> <class name="Department" (...)>    <field name="emps">      <collection element-type="Employee"> <extension vendor-name="sunw" key="element-inverse" value="dept"/>       </collection>    </field> (...) </class> 

Note again that, at this point, this is an implementation-specific extension. If your implementation of choice supports something like this, it most likely either generates or calls some code similar to the one shown above (likely if it is an O/R mapping-based implementation), and/or transparently implements a mirrored field (possible, if it is an object datastore) as a performance enhancement.

Future versions of JDO may standardize this functionality with a standard tag for the XML persistence mapping descriptor, and well-defined semantics for this feature, for example, related to updating inverse fields, and so on. [6]

[6] Note JDO Spec. Chapter 24.7, "Items deferred to the next release, Managed (inverse) relationship support: ( ) The intent is to support these semantics when the relationships are identified in the metadata as inverse relationships. ( )"

13.1.6 Inheritance hierarchies

Using JDO, as opposed to some other persistence frameworks, deriving from some common "root" persistence superclass is not a technical requirement. As we have seen, most classes can be declared persistent.

However, when using inheritance hierarchies, it is a best practice to declare classes all along the entire inheritance tree persistent, up to but not including the ultimate java.lang.Object superclass. Again, in simple and clear words, and thinking both ways up and down an inheritance hierarchy: Do not create persistent subclasses of superclasses that are not persistent, and do not create subclasses that are not persistent of superclasses that are persistent. Although doing it anyway is not explicitly prohibited by JDO and will not be flagged by some enhancers , it is definitely asking for trouble.

Of course, depending on the business application being developed, you may wish to create all subclasses from one persistent domain class (application domain specific, not framework specific) root class, which in the case of a collaboration platform, for example, may have fields such as createdOn and lastModified , and so on. This is not to say that you shouldn't do that; but if you do it, make sure that the application domain specific root class is enhanced and thus persistent, and so are all its subclasses.

13.1.7 Don't rely on persistence-aware (not persistence-capable) classes

Keeping fields private and accessible only via respective public accessor and mutator methods not only adheres to the common best practice of encapsulation in object-oriented programming (OOP), but it also has special importance in JDO-based development: It allows developers to compile and enhance the persistence-capable classes separately from the business logic and other classes of a project.

Imagine for a moment an environment where business logic classes would make direct access to public fields of persistent classes, without using a getter or setter method of the persistent object. A build system would need to ensure that absolutely all those classes are enhanced by JDO as well, even though they do not contain a persistent application state. Otherwise, such classes may read stale fields, or attempt to access non-default fetch- group fields that have not yet been read from the datastore, or inversely modify fields without giving JDO a chance to update its (internal) dirty flag, which would ensure that the field's new value was written back to the datastore, at some point. [7]

[7] A somewhat related issue would possibly arise if code (from the application itself or in some third-party library) accessed private fields of JDO enhanced persistence-capable classes via the Java reflection mechanism. As it is possible to access private fields using reflection, given the right ReflectPermission security privilege, JDO may be "bypassed." So if you see " funny things" happening, disabling the respective permission could help to discover code that uses reflection to access private fields instead using the public accessors and mutators.

The JDO specification refers to this issue by calling the persistent entity-type classes we described above as persistence-capable , as we have seen, and other classes persistence-aware, and mandates that JDO implementations need to be able to enhance arbitrary classes to persistence-awareness without changing their semantic behavior. This works for third-party code as well, because source code is not required to enhance classes at the byte-code level. Although this could be useful in some situations and technically works perfectly well, many JDO-based applications may prefer to enhance only persistence-capable entity-type classes, and compile business logic and other classes of a project as usual, often by an Integrated Development Environment (IDE), without subsequent enhancement. This works very well as long as all access to persistent data happens through public accessor and mutator methods of the persistence-capable class (which will be enhanced) and the persistent state remains in private fields.

In summary, to keep life simple, make all your persistent classes use private fields with public accessor and mutator methods, enhance them to become persistence-capable, and then for most real-world projects, forget about persistence-aware ”after you have read and conceptually understood them, of course.



Core Java Data Objects
Core Java Data Objects
ISBN: 0131407317
EAN: 2147483647
Year: 2003
Pages: 146

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