How Do I Create Persistent Java Objects?

Persistence may well be the most important capability of a computing environment, and Cliff shows how you can take advantage of built-in support for object persistence in the Java Developer's Kit 1.1.


April 01, 1997
URL:http://www.drdobbs.com/jvm/how-do-i-create-persistent-java-objects/184410176

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

Dr. Dobb's Journal April 1997: How Do I Create Persistent Java Objects?

How Do I Create Persistent Java Objects?

By Cliff Berg

Dr. Dobb's Journal April 1997

public void setName(String name);
public String getName();
public void setDesc(String desc);
public String getDesc();
public void setDate(java.util.Date
date);
public java.util.Date getDate();

Example 1: (a) Beginning a transaction; (b) making changes permanent; (c) reestablishing a database context; (d) TaskServer remote methods.


Copyright © 1997, Dr. Dobb's Journal

Dr. Dobb's Journal April 1997: How Do I Create Persistent Java Objects?

How Do I Create Persistent Java Objects?

By Cliff Berg

Dr. Dobb's Journal April 1997

(a)	COM.odi.ObjectStore.initialize(getInitThread());
	COM.odi.Transaction tr = 
COM.odi.Transaction.begin(COM.odi.Transaction.update);
(b)	tr.commit(COM.odi.Transaction.RETAIN_UPDATE);

(c)	persistentTasks = 
(COM.odi.util.OSVector)(database.getRoot("PersistentTasks"));
(d)	public java.util.Vector getTasks() throws java.rmi.RemoteException;
	public RemoteTask addTask(SerializableTask task) throws 
java.rmi.RemoteException;
	public boolean delTask(RemoteTask task) throws 
java.rmi.RemoteException;

Example 2: Defining the accessor methods.


Copyright © 1997, Dr. Dobb's Journal

Dr. Dobb's Journal April 1997: How Do I Create Persistent Java Objects?

How Do I Create Persistent Java Objects?

By Cliff Berg

Dr. Dobb's Journal April 1997

public void connectToTaskServer(String serverURL) throws Exception;
public void closeConnection();
public TaskServer getServer();

Example 3: TaskClient methods.


Copyright © 1997, Dr. Dobb's Journal

Dr. Dobb's Journal April 1997: How Do I Create Persistent Java Objects?

How Do I Create Persistent Java Objects?

By Cliff Berg

Dr. Dobb's Journal April 1997

java.io.FileOutputStream ostream = 
new java.io.FileOutputStream(appletConfigFile);
java.io.ObjectOutputStream p = new java.io.ObjectOutputStream(ostream);
System.out.println("Saving applet state...");
p.writeObject(taskClient);
p.writeObject(serverURL);
p.flush();
ostream.close();

Example 4: save() performs these calls.


Copyright © 1997, Dr. Dobb's Journal

Dr. Dobb's Journal April 1997: How Do I Create Persistent Java Objects?

How Do I Create Persistent Java Objects?

By Cliff Berg

Dr. Dobb's Journal April 1997

rem CLASSPATH needs to contain the JDK class (for Java 1.1, or for
rem 1.02 and RMI), and the ODI class zip files (pse.zip and 
rem tools.zip). RMIHOME needs to be defined to point to the location
rem of the RMI installation, if using 1.02.
del *.class
javac -nowarn TaskClientApplet.java
rmdir /q /s .\temp
mkdir .\temp
java COM.odi.filter.OSCFP -dest .\temp .\PersistentTaskImpl.class
copy .\temp\*.class .
del .\temp\*.class
java COM.odi.filter.OSCFP -dest .\temp -pa .\RemoteTaskImpl.class
copy .\temp\*.class .
del .\temp\*.class
java COM.odi.filter.OSCFP -dest .\temp -pa .\TaskServerImpl.class
copy .\temp\*.class .
del .\temp\*.class
java -Drmi.home=%RMIHOME% sun.rmi.rmic.Main -d . TaskServerImpl
java -Drmi.home=%RMIHOME% sun.rmi.rmic.Main -d . RemoteTaskImpl
start /min java -Drmi.home=%RMIHOME% sun.rmi.registry.RegistryImpl

Example 5: DOS script.


Copyright © 1997, Dr. Dobb's Journal

Dr. Dobb's Journal April 1997: How Do I Create Persistent Java Objects?

How Do I Create Persistent Java Objects?

By Cliff Berg

Dr. Dobb's Journal April 1997

Figure 1: Task manager system architecture.


Copyright © 1997, Dr. Dobb's Journal

Dr. Dobb's Journal April 1997: How Do I Create Persistent Java Objects?

How Do I Create Persistent Java Objects?

By Cliff Berg

Dr. Dobb's Journal April 1997

Figure 2: Applet UI.


Copyright © 1997, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.