Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

How Do I Create Persistent Java Objects?


Dr. Dobb's Journal April 1997: Java Q&A

Cliff is vice president of technology and Steve president of Digital Focus. They can be contacted at [email protected] and [email protected], respectively.


Persistence may well be the most important capability of a computing environment. Without it, every calculation would have to start anew. Databases and file systems could not exist. We would have a memory-less computing landscape.

Unfortunately, most of today's popular operating systems (except for the Macintosh, to an extent) still adhere to the model of flat files developed in the early days of computing, and leave it up to the application to provide any required structure. We can save data, but the application has to contain logic for writing its data to a flat file in a way that will permit it to be reconstructed later. Relational databases provide a way to structure persistent data, but are best suited for transaction-oriented applications, since you wouldn't want to develop and store a relational model of an entire program.

On the other hand, imagine if you could write programs that always picked up where you left them. Such environments exist -- most modern electronic organizers function this way. With the help of various toolkits and techniques, you can write Java programs that are persistent.

Granularity

The Java Developer's Kit (JDK) 1.1 (version beta 3 at the time of this writing) has built-in support for object persistence. This is provided in the form of classes with methods that will convert an arbitrary object (and all connected objects) to a transportable stream of bytes that can be stored in a file, sent around the world as an e-mail attachment, or transported to a remote client or server for later reconstitution within the context of an entirely different Java run-time environment (virtual machine). Using this facility, the entire collection of objects must be packaged, then stored or sent, all at one time. The granularity, or the amount of data you must make persistent at any one time, is therefore the entire set of interconnected data that may possibly be of interest to the ultimate recipient of the data.

In contrast, various vendors have created object-oriented database products that let you save objects on an object-by-object basis, without losing context between those objects. Granted, objects in such a database cannot be e-mailed around the world without sending the entire database, but at least the objects can be accessed independent of the virtual machine in which they were created. In this way, it is possible to design persistent data in the same way one designs the objects in a normal Java program: A separate data modeling and access language (such as SQL) is not needed.

The Task Manager

To demonstrate persistent Java objects, I'll illustrate uses of both persistence techniques. Task Manager is a client/server application that lets multiple users enter, retrieve, and delete tasks from a central task list. This is accomplished by using a persistent database to store the task data. For this, I have used Object Design's PSE toolkit. (This is the same product that will be incorporated into the next major release of Netscape. Object Design's web page can be found at http://www.odi.com/.) Further, when a user exits, I will store the user's state in a Java serialized object stream file, which is later used to automatically restore the application to the way it was when the user exited.

I have designed the client component as an applet that can run as an application. Object serialization is available in JDK 1.1, but the popular browsers do not yet incorporate 1.1. The source code provided here can be compiled with JDK 1.02, as long as you also place the relevant 1.1 extensions (object serialization, for instance) in your class path. This will still not enable a browser to run it, however, since these classes, which are part of package java, must be in the browser. Figure 1 shows the basic design of the system.

TaskServer

TaskServerImpl is a remote server object (see my column "How Do I Use Java Remote Method Invocation from an Applet?," DDJ, March 1997) that extends java .rmi.server.UnicastRemoteServer. Its remote methods are defined in its interface, TaskServer.

The constructor for TaskServerImpl first attempts to find the database:

database = COM.odi.Database.open
	("taskmaster.odb",
	COM.odi.Database.openUpdate); 

If the constructor does not find the database, it creates it:

database = COM.odi.Database.create
	("taskmaster.odb",
	COM.odi.Database.ownerWrite); 

It then initializes the persistentTask task list:

COM.odi.util.OSVector persistentTasks = 
	new COM.odi.util.OSVector(); 

The persistent task list is a persistent vector of task entries. The odi.util package contains a class, called OSVector, which mimics the java.util class Vector, except that it is persistent, whereas java.util.Vector is not.

These operations, as with all persistent operations, must be done in the context of a transaction, and so before this you execute Example 1(a) to begin a transaction and afterwards you commit the transaction, making the changes permanent; see Example 1(b). The RETAIN_ UPDATE code tells the ODI database system to keep database references alive after the commit has been performed. Without this, you would have to reestablish a database context anew at the beginning of each transaction. In this case, this would amount to calling Example 1(c). TaskServer has the remote methods in Example 1(d), which are available to the client.

The getTasks() method returns a list of remote references to tasks on the server. Thus, a client can call this method, and then use each task entry in the list to remotely get information about that task.

The addTask() method lets a client add a task to the current set of tasks. The client must prepare a SerializableTask object, which is a complete description of a task, and which implements java .io.Serializable and contains only objects that are also serializable (implement java.io.Serializable). An object that is serializable can be converted into a stream by the RMI system, and sent across the network and reconstructed at the other end. When a remote client calls addTask(), the serializable task argument is converted to a stream, sent to the server, where it is reconstructed into a SerializableTask object; and then addTask() uses it to construct an equivalent PersistentTask object, which it then adds to its list of tasks. It then constructs a RemoteTask proxy object for this new task, and returns it to the client.

The delTask() method allows a client to delete a specified task. To identify the task, however, the client must pass to the task server a remote reference to the task that is to be deleted. When the server receives this remote reference, it identifies the associated persistent task object, and removes it from its list of tasks.

Each remote method starts a transaction, performs its database interaction, and then commits the transaction. These transactions are the units of processing that must be made atomic. The Java synchronized keyword does not address this need, since it has to do with threads within a single virtual machine; our objects persist beyond the life of a virtual machine instance. You must rely on the underlying database to provide synchronization, unless you are sure that our server process instance is the only one accessing the data.

Tasks

Task objects are the information objects that are stored and transported in this system. I have defined three varieties: PersistentTask, RemoteTask, and SerializableTask. PersistentTask is the object type that is stored in the database. It is the "real" task object, and every real world task that we want to represent in our task management program should have a PersistentTask instance. PersistentTask implements Task, which defines the accessor methods in Example 2.

What makes PersistentTask persistent is that you will run PersistentTask's implementation class (PersistentTaskImpl) through ODI's persistence post-processor that will modify persistent object references and convert them into method calls that access the database. This is transparent to the programmer, at the source code level. The resulting class file is still a valid class file, and conforms to all of the rules for class files, as required by the Java run-time system.

At first glance, you would like to make PersistentTask serializable, so you could send copies of it to the client; and you would also like to make it remote, so that you could remotely invoke its accessor methods from the client. You cannot cleanly make PersistentTask either serializable or remote, however, because one of the effects of making a class persistent is that it is made to extend ODI's Persistent class type, which may or may not be serializable -- you need to assume not, since this is an implementation dependence. (I am told by ODI that its Persistent class will be made serializable when JDK 1.1 becomes the official release, and also that eventually this class will be replaced with an interface.)

For the same reason, PersistentTask implementations cannot extend the Java UnicastRemoteObject type, because Java does not have multiple inheritance, and so you cannot both extend this and Persistent as well. However, it may not even make sense to make PersistentTask either serializable or remote: Serializing a persistent object and removing it from its environment would probably break it, unless it was designed to be serializable; and making it remote is probably better done with a wrapper object anyway.

Finally, RemoteTask could extend from Task, but then each method in Task would have to throw Exception, because each remote method must throw java.rmi.RemoteException, and when you override a method, its throws must be subclasses of at least one throw type of the original method. Instead of doing this, I chose to simply create a separate type, RemoteTask, which does not extend Task.

The Client

The client portion of this program actually consists of an applet (TaskClientApplet), a client object that encapsulates interaction with the server (TaskClientImpl, with interface TaskClient), and a frame for instantiating the applet into. (The source code for the TaskClientApplet applet is available electronically; see "Availability," page 3 and http://www.digitalfocus .com/ddj/code.) TaskClient has methods in Example 3. The applet provides a user interface for the user to invoke these functions; see Figure 2.

The actual URL the user would enter in the Task Server URL field must match the URL used to start the server program (given as its only parameter), and must conform to the rules for Java RMI URLs.

Saving Client State

The save() and restore() methods demonstrate how a client's state can be saved when the user exits, and restored later when the program is restarted. This is achieved without any special logic to preserve the structure or meaning of data when writing it to disk: This logic is already built into the java.io.ObjectOutputStream class.

When the user clicks on the "X" on the TaskClient applet's frame to close the window, the frame catches the WINDOW_DESTROY event and calls the applet.destroy() method, which in turn calls the save() method. The save() method performs the calls in Example 4. Thus, save() writes two objects to the configuration file: the entire TaskClient object, and the string containing the current URL of the task server. As it turns out, TaskClient has no data in it, except for a handle to the task server, and this handle has the "transient" attribute, which tells the serialization mechanism to skip over it and not include it in the stream. I elected to make this handle transient because the handle cannot be expected to have any meaning after the user exits the program, and so there is no point in storing it.

When the client applet is started, the init() method calls restore(), which reads the saved configuration data back in, using the ObjectInputStream.readObject() method. When the applet start() method is later called, it attempts to connect to the task server using this URL. Thus, whenever the user starts the applet program, it automatically tries to reestablish a connection with the last known URL of the task server.

Compiling All This

Compiling the source file is only the first step in building this application. You then have to run the ODI class file post-processor, which modifies those classes you want to make persistent into actual persistent implementations. Also, classes which reference persistent classes must also be processed, to be made "persistent-aware."

Finally, those classes that have remote methods must be processed by the RMI compiler, to generate the remote stubs and skeletons that provide the remote calling capabilities. Example 5 is the DOS script file for doing all this.

Conclusion

Using an object database is arguably easier than using embedded SQL. For one thing, your structures are defined in the same language as your program. You can use object-oriented techniques to model business or application processes, and convert the model directly into code. And while there is no query engine, you have the full power of the Java language available to do queries. In fact, object database vendors such as ODI typically provide query packages and services in addition to basic object persistence functionality.

Object serialization is a very different technique, and is applicable for other purposes. In the demonstration program, I used it to save the applet's state between user sessions. You could also use it to store large objects that tend to be accessed as a unit; or to send an object from one place to another. This is in fact the mechanism that the Java RMI system uses to serialize method parameters and return values.

Object persistence, in these various forms, provides a powerful set of capabilities, and a different model for computing that is more user friendly and easier to develop. At last, you can view the computer as an assistant that will let you work, rest, and then simply pick up where you left off.

DDJ


Copyright © 1997, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.