3. The Database Class
Contents
3.1. Introduction
3.2. Creating Objects
3.3. Retrieving Objects
3.4. Saving Objects
3.5. Concurrency Issues
3.1. Introduction
The Database class is the heart of the persistence layer. It represents an individual session with the database and provides the methods necessary to save and retrieve objects from the database. A Database object is not thread safe.
A Database object cannot be directly instantiated.
It must be obtained from a Server object.
For details on the Server class refer to
section 11. The Server Class. Below
are the statements used by the ContactApp client
application to obtain a Database
object:
Server srvr = new
com.objectmatter.bsf.Server("contactdemo");
Database db = srvr.getDatabase();
A Database object returned from a Server
is already opened and ready for use if a connection pool or login parameters
have been predefined in the mapping tool DB Configuration, or via a DBConfiguration
object passed to the Server constructor.
If this is not the case, then the open() method
must be called with applicable URL, username and password parameters, or with a
JDBC connection object. An opened Database
is ready to be used for persistence operations using the API outlined in the following sections. After the session is
terminated the Database object should be closed by invoking
its close()
method to release its resources and to return it back to the Database
pool. The best place to
call close() is in the finally
clause of a try statement in order to guarantee that
resources are properly freed in the event of an error.
The persistence layer provides the ability to open and close a database connection for the same Database object multiple times within the same session. This is useful if VBSF is being used in conjunction with a middleware based product that manages connection pools, such as the WebLogic application server. A connection may also be re-opened under a different URL. A connection can be closed while maintaining all persistent objects in the cache by calling the Database closeConnection() method. This allows you to open a connection, cache objects in memory, close the connection, work with the objects in memory, re-open the connection (possibly to another database), and save all changes. For an example of this and some of the other dynamic features of VBSF see the dynamicdemo sample application.
3.2. Creating Objects
The Database class provides a create() method which can be used to create new persistent objects. This method should be used to instantiate persistent objects if any of the following conditions apply:
OPersistable,
CompositeID, or the ConcurrencyAware
interfaces so the appropriate interface methods to can be called during
initialization. Below is an example of the usage
of the create() method:
Contact newContact = (Contact)db.create(Contact.class);
In the example above we use the create()
method instead of the new operator
(i.e. Contact newContact = new Contact()) because
the Contact class contains OReference
and OCollection attributes that must constructed and
properly initialized by the persistence layer. It is not a requirement
that an object with OReference
and OCollection attributes be constructed
using the create() method. It may be
instantiated via the new operator.
However, in that case it is the responsibility of the persistent class to
initialize all OReference
and OCollection attributes in its
constructor. For an example of how this is accomplished see the Contact
class in the vcontactdemo sample application. It is also possible to initialize
all OReference
and OCollection attributes of a new object
not yet inserted to the database by using the Database.initRelationships()
method.
If a persistence class that is created via the create() method requires its attributes to be initialized with specific values during construction, you can specify these default values in the mapping schema. The persistence layer will initialize all attributes with any defined default values.
3.3. Retrieving Objects
The table below summarizes the most common methods provided by the Database class to
retrieve objects from the database (see the API Reference for additional retrieval methods
not shown below):
| Database API for Object Retrieval | |
|---|---|
| Method Signature | Description |
| Object lookup(idObject) | Retrieves an object using the supplied ID object as template |
| Object lookup(className,id) | Retrieves an object of the specified class and with the specified id |
| Object[] get(className) | Retrieves all objects of the specified class |
| Enumeration getEnumeration(className) | Enumerates all objects of the specified class |
| Object[] get(className,OQuery) | Retrieves all objects of the specified class matching a query specification |
| Enumeration getEnumeration(className,OQuery) | Enumerates all objects of the specified class matching a query specification |
| Object[] getForClass(hCollName,className) | Retrieves all objects of the specified class from the supplied heterogeneous collection |
| Object[] getForClass(hCollName,className,OQuery) | Retrieves all objects of the specified class from the supplied heterogeneous collection matching a query specification |
All get() methods listed above also have list() equivalents. The list()
methods
are used to force objects to always be retrieved from the database, by-passing the object
cache. All get() methods, in contrast, return objects from the cache, first
loading the cache from the database if the cache is empty. See section 4.6. Object
Caching for details on caching.
Multiple objects can be retrieved in two formats:
The getEnumeration methods return a BORandomEnumeration object, which can be traversed sequentially, or randomly. A random enumeration is useful when a client cannot receive a full array of objects because of resources limitations. The listEnumeration methods return a BOEnumeration object, which can be traversed sequentially (individually or in groups). A BOEnumeration instantiates persistent objects from the database result set as they are being fetched. This is useful when both client and server cannot instantiate all returned objects at the same time because of the collection size or resource limitations. Both BORandomEnumeration and BOEnumeration objects can be casted to standard java.util.Enumerations.
The example below retrieves all contacts from the database and prints their IDs and
last names:
Contact[] allContacts = (Contact[])db.get(Contact.class);
for ( int i = 0; i < allContacts.length; i++ ) {
System.out.println(allContacts[i].getID() + " " +
allContacts[i].getLastName());
}
The get() and list() methods return castable arrays. This means you can cast the array of objects returned by these methods directly into an array of the expected type, as shown above.
3.4. Saving Objects
The table below summarizes the most common methods provided by the Database class to
update objects in the database (see the API Reference for additional update methods not
shown below):
| Database API for Object Updates | |
|---|---|
| Method Signature | Description |
| int update(Object) | Updates the supplied object in the database. Can be used for inserts, updates, or deletions. |
| int insert(Object) | Inserts the supplied object in the database. |
| boolean delete(Object) | Deletes the supplied object from the database. |
| boolean markDelete(Object) | Marks the status of the object and all its owned objects as deleted. The actual deletion from the database is performed when the update() method is called. |
| boolean unDelete(Object) | Undeletes an object that has been marked as deleted and all its owned objects. |
| boolean markUpdate(Object) | Marks the status of the supplied object as updated. |
| Object undoAllChanges(Object) | Restores the values of all attributes of the supplied object to the values originally loaded from the database or as of the last successful database update. |
| Object refresh(Object, refreshOwned) | Refreshes all attribute values of the supplied object by reloading the object from the database. |
The persistence layer defaults to also automatically updating (inserting, deleting, or
updating) the complete object graph of owned and contained objects of the object being
updated in the database. This is known as persistence by reachability. The default
persistence by reachability behavior can be suppressed by invoking a special version of
each of the above methods that accepts two boolean flags as parameters. One flag is used
to specify whether owned objects are to be updated automatically, and the other is used to
specify whether contained objects are to be updated automatically.
Below is a code sample that retrieves the contact with object ID = 7 from the database,
modifies it, creates a new owned locator for it and saves all changes to the database:
Contact currContact = (Contact)db.lookup(Contact.class, 7);
currContact.setLastName("STALL");
Locator loc =(Locator)db.create(Locator.class);
loc.setLocatorNo("225-7870");
loc.setDescription("HOME");
currContact.addLocator(loc);
int rowCount = db.update(currContact); //rowCount is 2
The last statement updates the Contact object in the database and also inserts the new Locator object in the database.
If the object ID is assigned by the database during the insert operation,
then VBSF automatically sets the ID attribute directly on the object right after
the insertion. For example:
Contact newContact = (Contact)db.create(Contact.class);
newContact.setLastName("PICARD")
int rowCount = db.insert(newContact); //ID assigned by db
System.out.println("ID is " + newContact.getID()); //displays ID
3.5. Concurrency Issues
VBSF utilizes an optimistic concurrency control mechanism that maximizes concurrency by not locking rows in the database. This model assumes a low number of update conflicts. An update conflict occurs when two users attempt to make changes in the database that conflict with each other (e.g. two users trying to change the last name of the same customer at the same time).
VBSF can reliably detect update conflicts and provides a way to recover from them. In the event that an update conflict occurs, the first user to issue the update method (or any other method that updates an object in the database) will be able to save all changes, but the second user's invocation of the update method will throw a BOUpdateConflictException, and the changes will not be written to the database. The second user must then reload the object from the database in order to reapply the changes to the object.
Reloading the object is accomplished by invoking the refresh method of the Database class passing it as argument the object to be refreshed. If necessary, the refresh method can also automatically reload all owned collections and contained references of the supplied object from the database. The refresh method returns a boolean value of true if successful, or of false if the object was not found in database (e.g. another user deleted it). If false, the object should be set to null because it is no longer a valid object.
3.6. Modifying Mappings at Runtime
The persistence layer provides the ability to modify class to table
mappings dynamically at runtime. This is accomplished by obtaining a reference
to the SchemaManager object at runtime
by calling the Server.getSchemaManager()
method. The table below summarizes the most common methods
provided by the SchemaManager class to modify mappings (See the API Reference for any
additional methods not shown below):
| Database API for Modifying Mappings | |
|---|---|
| Method Signature | Description |
| AppSchema getSchema() | Returns the current application mapping schema. |
| updateSchema(AppSchema) | Updates the application schema from the supplied object. |
| setSchema(AppSchema) | Replaces the application schema from the supplied object. |
| ClassSchema getClassSchema(className) | Returns the mapping schema for the supplied class. |
| setClassSchema(packageName,ClassSchema) | Updates a class mapping schema from the supplied object. |
| setSQLCommand(SQLCommand) | Adds or updates a SQL command from the supplied object. |
| setValidityCheck(ValidityCheck) | Adds or updates a validity check from the supplied object. |
| setHCollection(HCollection) | Adds or updates a heterogeneous collection from the supplied object. |
| DBConfiguration getDBConfiguration() | Returns the current database configuration. |
| setDBConfiguration(DBConfiguration) | Updates the current database configuration from the supplied object. |
All the above methods are subject to server security restrictions. To be specific, a
client can only invoke getXxx() methods if the
schema has been set as Exposed in the application schema options form of the
mapping tool. Also, in order for a client to be able to modify the application schema
using any setXxx(x) methods, the application schema
must has been set as Updatable in the same form. Security restrictions
for the getDBConfiguration and setDBConfiguration
methods are controlled by the Exposed and the Updatable
options specified in the General options panel of the mapping tool DB Configuration form.
For examples on the use of these methods see the dynamicdemo sample application.
VBSF includes a sophisticated debugging feature that can provide extensive insight of what is going on inside the persistence layer. Debugging is turned on by using the static method setDebugging of the Debug class. This method supports multiple overloads for different options. The method signature that supports specifying all possible options is shown below:
/**
* Turns on debugging mode for all Database objects.
* @param level debugging level. Debug Level constants are listed in the Debugger interface.
* @param timer set to true to display elapsed time between debug messages.
* @param memory set to true to display memory usage in debug messages.
* @param showTime set to true to display the date and time of each debug message.
* @param showThread set to true to display information about the thread that generated each debug message.
*/
public static void setDebugging(int level,boolean timer,boolean memory,boolean showTime,boolean showThread);
The first argument allows you to specify the debug level to use. The supported debug
levels are declared in the Debugger
interface and listed below:
/**
* Forces deep debugging by printing additional messages.
* This constant cannot be used by itself.
*/
public static final int DEEP;
/**
* Print all Database related messages: open/close connections,
* SQL, parameter assignments, transaction start/stop, etc.
*/
public static final int DATABASE;
/**
* Print all Persistence Layer related messages: Object creation,
* object loading, object actions, cache retrieval, etc.
*/
public static final int PERSISTENCE;
/**
* Prints messages that allows monitoring the state of VBSF Database and
* connection pools. This setting may be enabled on a production application.
* This setting cannot be combined with any of the other Debug Level constants.
* Monitoring messages appear for all other Debug Level constants.
*/
public static final int MONITOR;
Below is an example of how to display basic database debugging messages:
Debug.setDebugging(Debugger.DATABASE, false, false);
Debug levels can be combined. For example, the statement below turns on extensive
database debugging messages:
Debug.setDebugging(Debugger.DATABASE + Debugger.DEEP, false, false);
Debug messages are by default sent to the standard output device, usually the
console. If you want them redirected to a file or another device, you can use the
Debug.traceToFile()
or Debug.traceToDevice()
methods to do so. The example below redirects the debugging messages to a file and prints all possible debug messages:
Debug.setDebugging(Debugger.DATABASE + Debugger.PERSISTENCE + Debugger.DEEP, false, false);
Debug.traceToFile("c:\\vbsf\\vcontactdemo.txt"); //enable to redirect debug output to a file