Persistent Classes
Copied from Hibernate Reference Manual - please re-write.
Persistent classes are classes in an application that implement the entities of the business problem (e.g. Customer and Order in an E-commerce application). Not all instances of a persistent class are considered to be in the persistent state - an instance may instead be transient or detached.
Topaz works best if these classes follow some simple rules, also known as the Plain Old Java Object (POJO) programming model. However, none of these rules are hard requirements. Indeed, Topaz assumes very little about the nature of your persistent objects. You may express a domain model in other ways: using trees of Map instances, for example. (Note: Entity Modes other than POJO is not implemented yet.)
A simple POJO example
Most Java applications require a persistent class representing felines.
package eg; import java.util.Set; import java.util.Date; public class Cat { private String id; // identifier private Date birthdate; private Color color; private char sex; private float weight; private int litterId; private Cat mother; private Set kittens = new HashSet(); private void setId(String id) { this.id=id; } public String getId() { return id; } void setBirthdate(Date date) { birthdate = date; } public Date getBirthdate() { return birthdate; } void setWeight(float weight) { this.weight = weight; } public float getWeight() { return weight; } public Color getColor() { return color; } void setColor(Color color) { this.color = color; } void setSex(char sex) { this.sex=sex; } public char getSex() { return sex; } void setLitterId(int id) { this.litterId = id; } public int getLitterId() { return litterId; } void setMother(Cat mother) { this.mother = mother; } public Cat getMother() { return mother; } void setKittens(Set kittens) { this.kittens = kittens; } public Set getKittens() { return kittens; } // addKitten not needed by Hibernate public void addKitten(Cat kitten) { kitten.setMother(this); kitten.setLitterId( kittens.size() ); kittens.add(kitten); } }
There are four main rules to follow here:
Implement a no-argument constructor
Cat has a no-argument constructor. All persistent classes must have a default constructor (which may be non-public) so that Topaz can instantiate them using Constructor.newInstance(). We strongly recommend having a default constructor with at least package visibility for runtime proxy generation in Hibernate.
Provide an identifier property
Cat has a property called id. This property is used as the subject-uri in the RDF statements that Topaz creates. The property might have been called anything, and its type might have been any type that can be converted to a URI.
Prefer non-final classes (optional)
A central feature of Topaz, proxies, depends upon the persistent class being either non-final, or the implementation of an interface that declares all public methods.
You can persist final classes that do not implement an interface with Topaz, but you won't be able to use proxies for lazy association fetching - which will limit your options for performance tuning. (Note: final class support is yet to be implemented)
You should also avoid declaring public final methods on the non-final classes. If you want to use a class with a public final method, you must explicitly disable proxying by setting lazy="false". (Note: final method support is yet to be implemented)
Declare accessors and mutators for persistent fields
Cat declares accessor methods for all its persistent fields. Topaz persists JavaBeans? style properties, and recognizes method names of the form getFoo, isFoo and setFoo. Direct field access for properties, are no longer supported in Topaz as of r6374.
Properties need not be declared public - Topaz can persist a property with a default, protected or private get / set pair. (Note: non-public accessor method support is yet to be implemented)
Implementing inheritance
A subclass must also observe the first and second rules. It inherits its identifier property from the superclass, Cat.
package eg; public class DomesticCat extends Cat { private String name; public String getName() { return name; } protected void setName(String name) { this.name=name; } }
Implementing equals() and hashCode()
You have to override the equals() and hashCode() methods if you
- intend to put instances of persistent classes in a Set (the recommended way to represent many-valued associations) and
- intend to use reattachment of detached instances
Topaz guarantees equivalence of persistent identity (subject-uri) and Java identity only inside a particular session scope. So as soon as we mix instances retrieved in different sessions, we must implement equals() and hashCode() if we wish to have meaningful semantics for Sets.
The most obvious way is to implement equals()/hashCode() by comparing the identifier value of both objects. If the value is the same, both must be the same database row, they are therefore equal (if both are added to a Set, we will only have one element in the Set). Unfortunately, we can't use that approach with generated identifiers! Topaz will only assign identifier values to objects that are persistent, a newly created instance will not have any identifier value! Furthermore, if an instance is unsaved and currently in a Set, saving it will assign an identifier value to the object. If equals() and hashCode() are based on the identifier value, the hash code would change, breaking the contract of the Set. Note that this is not a Topaz issue, but normal Java semantics of object identity and equality.
We recommend implementing equals() and hashCode() using Business key equality. Business key equality means that the equals() method compares only the properties that form the business key, a key that would identify our instance in the real world (a natural candidate key):
public class Cat { ... public boolean equals(Object other) { if (this == other) return true; if ( !(other instanceof Cat) ) return false; final Cat cat = (Cat) other; if ( !cat.getLitterId().equals( getLitterId() ) ) return false; if ( !cat.getMother().equals( getMother() ) ) return false; return true; } public int hashCode() { int result; result = getMother().hashCode(); result = 29 * result + getLitterId(); return result; } }
Note that a business key does not have to be as solid as a subject-uri. (see [<insert-link> “Considering object identity”]). Immutable or unique properties are usually good candidates for a business key.
Dynamic models
Note: Currently the only support in Topaz is for EntityMode?.POJO. However other entity modes are planned to be added.
Persistent entities don't necessarily have to be represented as POJO classes or as JavaBean? objects at runtime. Topaz also supports dynamic models (using Maps of Maps at runtime) and the representation of entities as DOM4J trees. With this approach, you don't write persistent classes, only mapping files.
Binder
org.topazproject.otm.mapping.Binder, and its sub-interfaces, are responsible for managing a particular representation of a piece of data, given that representation's org.topazproject.otm.EntityMode?. If a given piece of data is thought of as a data structure, then a binder is the thing which knows how to create such a data structure and how to extract values from and inject values into such a data structure. For example, for the POJO entity mode, the correpsonding binder knows how create the POJO through its constructor and how to access the POJO properties using the defined property accessors. There are two high-level types of Binders, represented by the org.topazproject.otm.mapping.EntityBinder? and org.topazproject.otm.mapping.PropertyBinder? interfaces. EntityBinders? are responsible for managing the above mentioned contracts in regards to entities, while PropertyBinders? do the same for properties.
Users may also plug in their own binders. Perhaps you require that a java.util.Map implementation other than java.util.HashMap? be used while in the dynamic-map entity-mode; or perhaps you need to define a different proxy generation strategy than the one used by default. Both would be achieved by defining a custom binder implementation. Binder definitions are attached to the entity or property mapping they are meant to manage. Going back to the example of our customer entity:
ClassBinding binding = sessionFactory.getClassBinding("Customer"); binding.bind(EntityMode.POJO, new MyCustomCustomerBinder()); public class MyCustomCustomerBinder extends org.topazproject.otm.mapping.java.ClassBinder { ... ... }
Similarly a specific property binder can be customized by:
ClassBinding binding = sessionFactory.getClassBinding("Customer"); binding.addBinderFactory(new BinderFactory() { public String getPropertyName() { return "Customer:account"; } public EntityMode getEntityMode() { return EntityMode.POJO; } public PropertyBinder createBinder(SessionFactory sf) { return new MyCustomCustomerAccountBinder(); } }); class MyCustomCustomerAccountBinder extends org.topazproject.otm.mapping.java.AbstractFieldBinder { ... ... }
