2. Creating Persistent Classes
Contents
2.1. Introduction
2.2. Defining Attributes
2.2.1. Simple Attributes
2.2.2. Object Reference Attributes
2.3. Optional Interfaces
2.3.1. OPersistable
2.3.2. ConcurrencyAware
2.3.3. CompositeID
2.1. Introduction
This section provides guidelines for creating persistent classes that will use the VBSF persistence layer services. Before your classes can be persisted by the persistence layer, their database mappings must be defined either by using the mapping tool, or by writing Java code. If the mappings are fixed, the easiest way to define mappings is using the mapping tool. For information on how to use the mapping tool see the Mapping Tool Guide. Mappings can also be defined in code by populating VBSF schema objects with mapping information at runtime. The VBSF schema objects are defined in the com.objectmatter.bsf.mapping.schema package. For details on this package refer to the API Reference. An example of how to define mappings in code can be found in the Server and Server1 classes provided in the vcontactdemo sample application.
Persistent classes must define a no argument constructor so the persistence layer can instantiate them. The constructor may be declared public, protected, package, or private. If necessary, you can define default values for class attributes at the time the mappings are defined. The persistence layer will assign any defined default values to the attributes of an object right after instantiation.
2.2. Defining Attributes
VBSF achieves object persistence by saving the state of an object (i.e. the value of its attributes) to relational databases. The persistence layer uses the Java reflection API to get and set the values of the attributes of your objects. The values of attributes can be set or read using direct variable access, or via get and set methods that you specify.
The persistence layer can access private, protected, and package members. However, in order to be able to do this the JDK's java.policy file must be edited to grant VBSF the privilege to access private, protected, and package members. For details on how to do this, see the Notes for Java 2 Platform Users section of the Installation Instructions.
You can defined three types of attributes in your persistent classes:
2.2.1. Simple Attributes
A Simple attribute holds a value of a specific type such us an integer or a date. You can define simple attributes using Java standard primitive or object types. If you need the ability to distinguish between zero and null values in numeric attributes, you should use Java object wrapper types instead of primitive types (e.g., use java.lang.Integer instead of int). For a recommendation of which Java data types to use see section 4.2.2. Simple Attributes in the Mapping Tool Guide.
Below is an example of how the Category class in
the vcontactdemo sample application is defined:
public class Category implements java.io.Serializable {
//simple attributes
public long fID;
public String fNumber;
public String fDescription;
public Category() {
super();
}//attribute accessors and mutators go here
}
There are two special types of simple attributes that can hold an embedded Java object that is
saved to either a column that can store a large binary value (e.g., SQL
LONGVARBINARY) via serialization, or to multiple columns within the container
class. These types
of attributes are defined by setting the attribute type to CONTAINED BLOB OBJECT
and CONTAINED OBJECT respectively when mapping the
attribute in the mapping tool, and by declaring it as the actual Java type it holds in the
persistent class. Shown below are the Employee, Address and Phone
classes from the vemployeedemo
sample application.
public class Employee {
public long fID;
public String fLastName;
public String fSecondName;
public String fFirstName;
public String fTitle;
public Date fEntryDate;
//CONTAINED BLOB OBJECT saved to a BLOB type column
public Address myAddress;
//CONTAINED OBJECT saved to a subset of columns within the EMPLOYEE table
public Phone myPhone;
}
public class Address implements java.io.Serializable {
static final long serialVersionUID = <Some long value>;
public String fAdr1;
public String fAdr2;
public String fCity;
public String fState;
public String fZip;
public String fCountry;
}
public class Phone {
public String areaCode;
public String phoneNumber;
public int extension;
}
The myAddress attribute of the Employee class holds an embedded object of type Address. VBSF transparently serializes the Address object to the underlying binary column when saving an employee, and reconstructs it back when retrieving the employee back from the database. The drawback to this approach is that you cannot perform searches based on the attributes defined in the embedded object. For example, you cannot formulate a query to retrieve all employees that live in a particular city.
Embedded classes saved to a binary column must implement the java.io.Serializable interface so they can be serialized to the column, and should define a serialVersionUID static field to avoid serialization problems related to class evolution. For details on defining a Serial Version UID, refer to serialver command under 'Object Serialization' in the JDK documentation.
The myPhone attribute of the Employee class holds an embedded object of type Phone. VBSF transparently saves the Phone object to the AreaCode, PhoneNumber, and Extension columns in the EMPLOYEE table when saving an employee, and reconstructs it back when retrieving the employee back from the database. The Phone class must be mapped in the mapping tool with a mapping type of CONTAINED in order to specify the columns each attribute maps to. This approach does allow you to perform searches based on the attributes defined in the embedded object. For example, you can formulate a query to retrieve all employees in a particular are code.
2.2.2. Object Reference Attributes
In addition to simple attributes, a persistent class can define attributes that reference another persistent object mapped to another table. When mapping a reference attribute in the mapping tool you must specify an attribute type of REFERENCE. There are two types of object reference attributes:
A contained object is under the control of the object that contains it (the holder). When the holder of a contained object reference is updated in the database, all its contained objects are also automatically updated in the database. This behavior can be overridden when invoking the update method on a Database object.
A referenced object is simply referenced by the holder. The holder does not have the ability to update the referenced object in the database, only to retrieve it.
Object reference attributes may be declared with a Java type of OReference, as the referenced
type itself, or as an interface implemented by the referenced type. The OReference class is a
serializable VBSF attribute class that provides the functionality required to automatically retrieve and
store object references in the database using a lazy approach (i.e. the reference is only
retrieved from the database when it is accessed). The Contact
class in the vcontactdemo sample application defines an attribute that references its
corresponding Category object:
private OReference myCategory;
Below are the accessor and mutator methods for the
public Category getCategory() throws BODBException {
return (Category)myCategory.get();
}
public void setCategory(Category newCategory)
throws BODBException, BOReferenceNotUpdatedException {
myCategory.set(newCategory);
}
For additional information on the
You can also declare a REFERENCE attribute as the referenced type. Below is an example
of how the User class in the companydemo2 sample
application declares its myDepartment attribute
which references the Department object that the user
works in:
public Department myDepartment;
Below are the accessor and mutator methods for the
public Department getDepartment() {
return myDepartment;
}
public void setDepartment(Department newDepartment) {
myDepartment = newDepartment;
}
The biggest drawback to declaring a referenced object as the referenced type
is that lazy retrieval is not supported. In the example above whenever a User object is retrieved from the database, its
corresponding Department object is also retrieved
and instantiated. This happens regardless of whether the myDepartment
attribute is actually accessed or not.
A third approach is to declare a REFERENCE attribute as an interface
implemented by the referenced type. Below is an example
of how the Invoice class in the vposdemo sample
application declares its myCustomer attribute
which references a Customer object that
implements the ICustomer interface:
public ICustomer myCustomer;
Below are the accessor and mutator methods for the myCustomer
attribute:
/**
* When using an interface as the Java data type for a REFERENCE attribute,
* if the object is not found in the database this method will return a
* non-null interface implementation. In that case, any method calls to
* the interface implementation will result in a BOInvalidStateException.
* The only method call that will not throw an exception is toString(),
* which will return null. So you can use toString() == null to check for
* nullability of the referenced object prior to calling any other methods.
*/
public ICustomer getCustomer () {
return myCustomer;
}
/**
* Sets this object's reference to its Customer.
* Use a null value when not referencing any objects.
*/
public void setCustomer ( ICustomer newCustomer ) {
myCustomer = newCustomer;
}
Declaring a referenced object as as an interface implemented by the referenced
type allows lazy retrieval of the referenced object and makes your persistent classes independent of
VBSF, but has the potential drawback that you must define an interface for each
class that is referenced by another class.
VBSF can also maintain a reference relationship without actually defining a reference
attribute in the persistent class. For example, the Invoice
class in the posdemo2 sample application does not declare an fCustomer
attribute to reference its customer. Instead, the client uses the getReference()
and setReference() methods of the Database class to retrieve the Customer
object associated with a particular invoice, as shown below:
Invoice inv = (Invoice)db.lookup(Invoice.class, 4);
Customer cust = (Customer)db.getReference(inv, "fCustomer");
You can also encapsulate the functionality inside the Invoice
class by defining the reference accessor and mutators methods inside the Invoice class as shown below:
public Customer getCustomer(Database db) {
return (Customer)db.getReference(this, "fCustomer");
}
public void setCustomer(Database db, Customer cust) {
db.setReference(this, "fCustomer", cust);
}
The Database object may be passed to the accessor
and mutator methods by the client as shown in the example above, or it may be obtained by
the Invoice object directly from a VBSF Server
object that maintains a pool of Database
objects.
This last approach also provides lazy instantiation of object references and makes your persistent classes independent of VBSF without having to define an interface for the referenced class. When using this approach you must still define a corresponding REFERENCE attribute in the mapping tool with its Java data type left blank. See the posdemo2 sample application schema for details.
2.2.3. Collection Reference Attributes
Collection reference attributes hold a collection of objects of the same class or of multiple classes. When mapping a collection reference attribute in the mapping tool you must specify an attribute type of REFERENCE COLLECTION. There are two types of collection attributes:
Holds a collection of persistent objects owned by the persistent class that holds the collection. An owned collection is used to represent a part-of or one-to-many aggregation relationship. A class that contains owned collections is called an owner class. When an owner class is updated in the database, all its owned objects are also automatically updated in the database. This behavior can be overridden when invoking the update method on a Database object.
Holds a collection of objects of a root class or of a class owned by another persistent class. A referenced collection is used to represent one-to-many or many-to-many associations.
VBSF supports homogeneous (one class) and heterogeneous (multiple class) collections. For details on defining heterogeneous collections see section 8. Heterogeneous Collections of the Mapping Tool Guide.
Collection attributes may be declared as a Java type of OCollection,
as the standard java.util.Collection
interface, as an array of the referenced type, or as a Vector.
The OCollection class is a serializable VBSF attribute class that
provides all necessary management functions to maintain a collection of persistent objects
in the database. The OCollection class implements
the standard java.util.Collection
interface, so it can be treated and
manipulated as such in Java.
The Contact class in the vcontactdemo sample
application declares the two owned collection attributes shown below to keep track of its Locator and Address
objects:
public OCollection currentAddresses;
public OCollection currentLocators;
Below are the collection management methods defined in the Contact
class to manage its Locator objects:
/**
* Accesses the collection of owned Locators.
* @return collection array, or null if no owned locators.
* @exception BODBException if database operation fails.
*/
public Locator[] getLocators() throws BODBException {
return (Locator[])currentLocators.get();
}
/**
* Returns all owned Locators matching a query specification.
* @exception BODBException if database operation fails.
*/
public Locator[] getLocators(OQuery qry)
throws BODBException {
return (Locator[])currentLocators.get(qry);
}
/**
* Adds an owned Locator to its collection.
*/
public void addLocator(Locator loc) {
currentLocators.addOwned(loc);
}
/**
* Removes an owned Locator from its collection.
*/
public void removeLocator(Locator loc) {
currentLocators.remove(loc);
}
The removeLocator() method not only removes the owned object from the collection, but it will also remove the owned object from the database when the owner is updated in the database. This is because an owned object cannot exist without an owner. In referenced collections the remove operations will not result in the referenced object being removed, as referenced objects may exist outside of the referenced collection. For additional information on the OCollection class see the API Reference.
Since the OCollection class implements
the standard java.util.Collection
interface, it can also be declared as such in the persistent class. When doing
this you must specify the Java data type of the REFERENCED COLLECTION as
Collection (instead of OCollection) in the mapping tool. Below is an example of how the
Invoice class in the posdemo sample application declares its
lines
attribute to reference a collection of InvoiceLine objects:
public java.util.Collection lines;
When declaring a collection as a java.util.Collection
you are restricted to the collection operations defined in the interface.
However, since the interface is implemented by the VBSF OCollection
class, you may at any time explicitly cast the attribute to OCollection
in order to perform collections operations not supported by the interface. Below are the collection management methods defined in the
Invoice
class to manage its InvoiceLine objects:
/**
* Accesses the collection of owned lines.
* @return collection array, or null if no owned lines.
*/
public Object[] getLines() {
return this.lines.toArray();
}
/**
* Returns the set of owned lines that match a query specification.
*/
public Object[] getLines( BOP_Query qry ) {
return ((OCollection)this.lines).get( qry );
}
/**
* Adds an owned InvoiceLine to its collection.
*/
public void addLine( InvoiceLine line ) {
this.lines.add(line);
}
/**
* Removes an owned InvoiceLine from its collection.
*/
public void removeLine( InvoiceLine line ) {
this.lines.remove(line) ;
}
You can also declare the collection
attribute as an array of the referenced type, or as a Vector.
Below is an example of how the Company class in the
companydemo2 sample application declares its users
attribute to reference the User objects that work
for a company:
public User[] users;
Below is the accessor method for the users
attribute:
public User[] getUsers() {
return users;
}
To add or remove users from a company, you must use the addToCollection()
and removeFromCollection() methods of the Database class. For example, the code below adds a new
user to an existing company:
Company co = (Company)db.lookup(Company.class, 12);
User user = new User();
user.setName("George");
db.addToCollection(co, "users", user);
Even if the collection is declared as a Vector, you must still use the addToCollection() and removeFromCollection() methods because VBSF does not keep track of changes made to the Vector.
The biggest drawback to declaring a referenced collection as an array or Vector of the referenced type is that lazy retrieval is not supported. In the example above, whenever a Company object is retrieved from the database, its corresponding User objects are also retrieved and instantiated. This happens regardless of whether the users attribute is actually accessed or not.
VBSF can also maintain a collection relationship without actually defining a collection
or array attribute in the persistent class. For example, the Invoice
class in the posdemo2 sample application does not declare an fLineItems
attribute to reference its line items. Instead, the client uses the get()
and list() methods of the Database
class that accept as argument the collection attribute name. For example, the code below
retrieves the line items associated with a particular invoice:
Invoice inv = (Invoice)db.lookup(Invoice.class, 4);
InvoiceLine[] lines = (InvoiceLine[])db.get(inv, "fLineItems");
Adding and removing objects from a collection is accomplished using the addToCollection() and removeFromCollection() methods of the Database class as explained above. As with references, you can also encapsulate the functionality inside the Invoice class by defining the collection accessor and mutators methods inside the Invoice class.
This last approach provides lazy instantiation of referenced collections and makes your persistent classes independent of VBSF. When using this approach you must still define a corresponding REFERENCE COLLECTION attribute in the mapping tool with its Java data type left blank. See the posdemo2 sample application schema for details.
2.3. Optional Interfaces
This section describes optional interfaces that may be implemented by business classes that require additional control or information from the persistence layer. For a more detailed explanation of these interfaces, and when each of the methods is invoked by the persistence layer, see the API Reference.
2.3.1. OPersistable
Persistent classes that need more control over their persistence behavior can implement
the OPersistable interface. By implementing this
interface, a persistent class can place its own hooks at various points in operations
involving creating, reading, and updating objects of that class. The OPersistable
interface declares the following methods:
public void postCreate(Database db);
public void postRead(Database db) throws BODBException;
public int preUpdate(Database db, int operation, boolean updateOwned, boolean
updateContained)
throws BODBException, BOUpdateConflictException, BOReferenceNotUpdatedException;
public int postUpdate(Database db, int operation, boolean updateOwned, boolean
updateContained)
throws BODBException, BOUpdateConflictException, BOReferenceNotUpdatedException;
public int preCommit(Database db, int operation) throws BODBException;
public void postCommit(Database db, int operation);
If for example, an invoice object needs to check that its customer object is not over the credit limit before the invoice is saved to the database, it could implement the OPersistable interface, and in the preUpdate method query the total of all outstanding invoices for that customer. If the customer is indeed over the limit, the method can throw a BODBException to abort the update operation.
2.3.2. ConcurrencyAware
For classes for which concurrency control is implemented by means of a concurrency control column, it might be necessary under some circumstances for a persistent object to be aware of the value of its concurrency column. To demonstrate this, picture a scenario in which a persistent business object is serialized to a client via RMI or XML where it resides for a long time before the client sends the updated object back to the server to write changes to the database. If by the time the client sends the object back to the server the object is not in the VBSF cache anymore, the value of the concurrency column at the time the object was loaded is lost. In this case VBSF cannot reliably perform the update in the database without ensuring that another client did not change the object. If, on the other hand, the object itself carries the value of its concurrency column when it was loaded, then VBSF automatically performs a concurrency integrity check prior to performing the update. VBSF performs this check by reloading the same object from the database, and comparing the value of the existing concurrency column value with the value just loaded. If they match, then VBSF proceeds with the update operation since the update can be performed reliably. If they do not, it means another user changed the object, and VBSF throws a BOUpdateConflictException.
Classes that need to be aware of the value of their concurrency column can implement
the ConcurrencyAware interface. The persistence
layer automatically performs a concurrency integrity check on all objects that implement
this interface if the object is not in the cache when the update operation is performed.
The ConcurrencyAware interface defines the following
two methods:
public void setConcurrencyColumn(Object val);
public Object getConcurrencyColumn();
The persistence layer invokes the setConcurrencyColumn method on each object after the object has been newly instantiated or retrieved from the database, and passes as argument the value of the concurrency column. A typical implementation will simply save the value into an object attribute. The getConcurrencyColumn method is invoked when the object is ready to be written back into the database, in order to retrieve the stored value so it can be compared against the current value in the concurrency column.
For examples of implementation refer to the persistent classes defined in the vcontactdemo2 sample application.
2.3.3. CompositeID
The CompositeID interface must be implemented by persistent classes that have a composite ID (i.e. multiple ID attributes), have no OCollection or OReference attributes, and for which not all ID attributes are present as attributes in the object. Note that if a persistent class has these characteristics, then this interface is not optional. VBSF requires this because it has no way of determining the absolute identity of the object unless all the values of all ID attributes are present in the object.
This interface defines the following two methods:
public void setCompositeID( Object[] compositeID );
public Object[] getCompositeID();
The persistence layer invokes the setCompositeID method when an object is initially instantiated, and passes it the values of the object ID attributes. The implementation of this method must save these values to an attribute. Later when the object is passed back to the persistence layer to be updated in the database, the getCompositeID method is invoked so the absolute identity of the object can be determined.
For an example of implementation refer to the persistent classes defined in the vcontactdemo3.engine2 sample application.