When people talk about object persistence, they usually mean the
storage of objects or their states in a database. This will usually
be a relational database, or possibly an alternative, in the form of
an object database such as db4objects, described in a
previous
article.
For most applications, interfacing with a DBMS is desirable, necessary, or both. However, it is possible to achieve object persistence without using a database at all. The requirements are that it is acceptable to have a very close coupling between the data and the application, and that the amount of data involved is small enough to fit into working memory.
In fact, Java already has a well-established, built-in persistence mechanism in the form of serialization. However, serialization is limited in terms of ability to query specific data, and, crucially, in its lack of fault tolerance. The first problem is not an issue if the entire data set for an application can be held in memory--and querying an in-memory data set will usually be faster than a database query. However, this is not of much use if the system experiences a failure while running--the state of the data on restart will be the last serialized data set, and any changes to the data since then will be lost.
A prevalent system makes use of serialization, and is again useful only when an in-memory data set is feasible. A serialized snapshot of a working system can be taken at regular intervals as a first-line storage mechanism. Fault-tolerance and data consistency are provided by the use of command objects to perform all transactions that change the state of the data. All commands are stored using serialization.
If a fault occurs, an up-to-date data set can be rebuilt by taking the last snapshot and applying all of the subsequent commands to it. Consistency is assured as each command represents a transaction--no changes to the data can be made to the data without using such a transaction, and transactions are applied sequentially. This clearly requires that the behavior of all business objects is deterministic.
Persistence using a prevalent system is transparent, as
transactions are applied directly to the business objects with no
need to use SQL either directly or through object-relational
mappings.
In this article, I will show how to create a simple prevalent system in Java using open source tools. The main tool you need is the popular Prevayler framework. Prevayler, created by Klaus Wuestefeld, provides a prevalence layer for Java. Prevayler 1.0 was awarded a JOLT Productivity award in 2004. The recent version, 2.0, has many improvements, including a simpler API.
Prevayler itself is really all that is required to build a prevalent system. However, the task is made easier by the Preclipse plugin for Eclipse, which provides extensive support for Prevayler in terms of code generation, refactorings, and visualization. The current version of Preclipse is based on Prevayler 2.0.
Note that there are also prevalence tools available or under development for other languages, such as Bamboo for .NET.
The example code on the Preclipse site shows the creation of a basic system with a GUI interface. The system described here deals with a slightly more complex domain model. Before we look at any code, we need to define what the component parts of our system are. There are three components:
The business objects in this example represent customer
Orders, and Items that can be added to
Orders. An Order comprises
OrderLines, each of which contains a reference to an
Item and a quantity. Just to make things a bit more
interesting, Items can be of different types
(BookItem, SoftwareItem) or can contain
other items (MultipackItem). Mapping this composite
hierarchy to a relational database would require a bit of effort,
but the prevalent system will handle this easily. The class diagram
for the business classes is shown in Figure 1.

Figure 1. Business classes
A wide range of transactions on these classes would be possible. The following transactions that will be implemented here:
Item to the system.Item.Order.Orderline to an
Order.Now that you know what the component parts of the system will
be, you are ready to start creating some code.
Before you can use Preclipse, you need to download and install Eclipse 3.0. To install the plugin, use the Software Updates feature in Eclipse and go to the Preclipse download center. When you create a new project in Eclipse, you should now have the option to create a New Prevayler-based Project, as shown in Figure 2. Select this option and follow the steps of the wizard to create a new project.

Figure 2. Creating a new Prevayler-based project
Call the project ordersystem and accept the defaults for Java settings. The final step allows you to create a skeleton, which consists of a basic prevalent system class and a main class that kicks off an instance of the prevalent system. Enter the values shown in Figure 3. For simplicity, I have chosen here not to put source files into a separate folder.

Figure 3. Creating the skeleton
Click Finish and the project structure shown in Figure 4 should be created. Note that the Prevayler library is included.

Figure 4. Initial project structure
At this point, PrevalentOrderSystem doesn't have
much code in it, as no business objects have been defined.
Main contains code to simply start a new thread that
creates an instance of PrevalentOrderSystem, and takes
a snapshot of it at intervals specified in the project wizard (see
Figure 3).
package ordersystem;
import org.prevayler.Prevayler;
import org.prevayler.PrevaylerFactory;
import java.io.IOException;
public class Main {
public final static String DATA_FOLDER = "data";
public Main() {
super();
}
public static void main(String[] ignored)
throws Exception {
final Prevayler prevayler =
PrevaylerFactory.createPrevayler(
new PrevalentOrderSystem(), DATA_FOLDER);
Thread snapShotThread = new Thread() {
public void run() {
while (true) {
try {
Thread.sleep(500);
prevayler.takeSnapshot();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
snapShotThread.start();
}
}
|
The prevalent system is now ready to have some business objects
associated with it. The first business class to add is
Item. Right-click on the
PrevalentOrderSystem class in the Eclipse
Package Explorer and select Prevayler ->
Create Business Object. Note that you will only see the
Prevayler option on the context menu if you select
the correct object in the Package Explorer: you need to select the
class, not the source file (see Figure 5).

Figure 5. Adding a business object
The Create Business Object dialogue opens. This allows you to specify the name of a BO class, and to specify whether the prevalent system will contain a single object or a list of objects. Enter Item in the BO Class Name box and select List, as you want the system to be able to contain many items (see Figure 6). Enter items in the "field name" box.

Figure 6. Specifying details of the business object
Click Finish and a new class, Item,
is added to the project. The PrevalentSystem class is
altered so that it has a field of the type ArrayList, and
methods to add a new Item, retrieve an
Item by id, and remove an
Item. The addItem method is shown in the
listing below. This method uses the nextItemID field
to ensure that a unique id is added to the
Item before storing in the ArrayList. You
will need to modify the auto-generated code slightly, as shown in
the listing below--this version passes a reference to an
Item into the method and then sets its
id. The listing also shows a method,
getItems, which returns the whole list--this is not
generated by Preclipse, so you need to add it yourself.
public class PrevalentOrderSystem implements Serializable {
private List items = new ArrayList();
private long nextItemID = 1;
public PrevalentOrderSystem() {
super();
}
public Item addItem(Item newItem) {
newItem.setID(nextItemID++);
this.items.add(newItem);
return newItem;
}
public List getItems(){
return items;
}
// other methods omitted for brevity...
}
The Item class, at this stage, has only an
id field, which will be used to identify particular
objects stored in the prevalent system. You need to add additional
fields and methods to match the specification in Figure 1. The
completed Item class is as follows (with
getters/setters omitted for brevity). Note that the id
field is not set in the constructor--in this example,
ids are only set when an object is made persistent by
a transaction.
public class Item implements Serializable {
private long id;
private int currentStock;
private double price;
private long itemCode;
public Item() {
this.setCurrentStock(100);
}
public double getPostage() {
return 0.0;
}
public void reStock(int quantity) {
setCurrentStock(getCurrentStock() + quantity);
}
public void order(int quantity)
throws InsufficientStockException{
if (getCurrentStock() >= quantity) {
setCurrentStock(getCurrentStock() - quantity);
} else {
throw new InsufficientStockException
(currentStock);
}
}
public String toString() {
return (id + " - " + this.getClass() +
":" + currentStock + " in stock");
}
// getters/setters omitted...
}
The Item class throws a custom exception,
InsufficientStockException, which can be added to the
project as an ordinary Java class, not using any Prevayler-specific
options.
public class InsufficientStockException extends Exception {
private int currentStock;
public InsufficientStockException(int currentStock){
this.currentStock = currentStock;
}
public String toString(){
return (super.toString() + ": current stock level is "
+ currentStock);
}
// getters/setters omitted...
}
The subclasses of Item can now be added to the
project as ordinary Java classes--they become persistent BO
classes because they are subclasses of an existing BO class. An
instance of BookItem, for example, will inherit its
id field from the superclass.
public class BookItem extends Item {
private String title;
private String authors;
private String publisher;
public BookItem() {
}
public BookItem(double price, String title, String
authors, String publisher) {
this.setPrice(price);
this.setTitle(title);
this.setAuthors(authors);
this.setPublisher(publisher);
}
public double getPostage() {
return (getPrice() * 0.10);
}
public String toString(){
return(super.toString() + ":" + this.title);
}
// getters/setters omitted...
}
public class SoftwareItem extends Item {
private String title;
private String version;
public SoftwareItem() {
}
public SoftwareItem(double price, String title, String version){
this.setPrice(price);
this.setTitle(title);
this.setVersion(version);
}
public double getPostage(){
return 2.50;
}
public String toString(){
return(super.toString() + ":" + this.title);
}
// getters/setters omitted...
}
public class MultipackItem extends Item {
private Item item;
private int multiple;
private double discount;
public MultipackItem(){}
public MultipackItem(Item item, int multiple, double
discount) {
this.setItem(item);
this.setMultiple(multiple);
this.setDiscount(discount);
}
public double getPrice() {
double packPrice =
(getItem().getPrice() *
getMultiple() * (1 - getDiscount()));
return packPrice;
}
public double getPostage() {
double packPostage = (getItem().getPostage() *
getMultiple() * 0.5);
return packPostage;
}
public String toString(){
return(super.toString() + "(" +
this.item.toString() + ")");
}
// getters/setters omitted...
}
|
You haven't created all of the BO classes yet, but you can now
create a transaction so that you can check whether the system built
so far actually works. The first transaction you need is to add a
new Item to the system. Right-click on the
Item class (again, the class, not the source file) and
select Prevayler -> Create 'Create' transaction for
BO…" as shown in Figure 7.

Figure 7. Creating a transaction
In the Create Transaction dialogue, accept the
name ItemCreateTransaction, but deselect the fields to
be included in the constructor (see Figure 8).

Figure 8. Setting up the transaction
Clicking Finish creates a new class,
ItemCreateTransaction, which contains:
Item to be added to the system.executeAndQuery, which takes a prevalent
system object as a parameter and actually performs the
transaction.Modify the code slightly to the following:
public class ItemCreateTransaction
implements TransactionWithQuery {
private Item item;
public ItemCreateTransaction(Item item) {
this.item = item;
}
public Object executeAndQuery(Object prevalentSystem,
Date executionTime)throws Exception {
Item it = ((PrevalentOrderSystem)prevalentSystem).
addItem(this.item);
return it;
}
}
This transaction takes a BO that is an instance of
Item (or any of its subclasses) as a parameter in its
constructor, and calls the addItem method of the
PrevalentOrderSystem to make it persistent. The
id field is set by the prevalent system.
Note that this transaction works slightly differently from the
way in which Preclipse's auto-generated transactions are intended
to work. The default approach is to take field values for the BO as
parameters in the constructor of the transaction, to let the
prevalent system create a new BO using an appropriate constructor
and to set the fields of the BO in the transaction. This is good if
you know what all of the fields in the BO will be, but not if the
transaction must deal with different types of BOs. You will use
the default approach for the Order BO later on.
The system you have created so far can be used with any kind of
UI to create an application. For simplicity, the "application" here
will be some basic classes that store and retrieve some objects.
You already have a Main class that starts off a
snapshot thread.
Add a new class called Store to the project in the
usual way, using the following code. This will add three different
types of item, including a composite item, to the prevalent
system.
import org.prevayler.Prevayler;
import org.prevayler.PrevaylerFactory;
public class Store {
public final static String DATA_FOLDER = "data";
public static void main(String[] args) {
try {
final Prevayler prevayler =
PrevaylerFactory.createPrevayler(
new PrevalentOrderSystem(),
DATA_FOLDER);
prevayler.execute(new ItemCreateTransaction(
new BookItem(39.95,
"Java Database Best Practices",
"George Reese")));
System.out.println("BookItem added");
prevayler.execute(new ItemCreateTransaction(
new SoftwareItem(74.99,
"SuSE Linux Professional",
"9.2")));
System.out.println("SoftwareItem added");
SoftwareItem switem = new SoftwareItem(169.99,
"Red Hat Enterprise Linux", "4");
prevayler.execute(new ItemCreateTransaction(
new MultipackItem (switem,10, 0.2)));
System.out.println("MultipackItem added");
} catch (Exception e) {
e.printStackTrace();
}
}
}
To test this, run the Main class as a Java
application--this starts a snapshot thread. Now run the
Store class as a Java application, and create the
persistent objects.
How do you know that the objects have been stored? Shortly, you
will add a class that retrieves the stored objects, but Preclipse
provides another, convenient way of examining persistent data.
First, you need to terminate the snapshot thread. Select the
Console for the Main process and terminate the process
using the red terminate icon.
Now right-click on the data folder in the Package Explorer and
select the Refresh menu item. You should see at
least one transaction log and one snapshot listed, similar to
Figure 9.

Figure 9. Snapshot and transaction log
Double-click on the snapshot. The Preclipse Snapshot
Viewer should open and show the persistent data.
Similarly, the transaction log can be viewed in the
Transaction Log Viewer. Figure 10 shows views of a
snapshot and a transaction log for the data created by
Store. This snapshot was actually created after
restarting and then terminating Main again to make
sure the most recent data is included in it. The
ArrayList contains three objects, and
nextItemID is set to 4, as you would expect.


Figure 10. Viewing a snapshot and a transaction log
Querying the prevalent system for objects does not require a
transaction as the system is not changed. Add and run a class named
Retrieve to your project to retrieve and list all of the
Items. The code for Retrieve is shown
below. It simply uses the toString method of each
object to print some information about the object--the
id field, the class name, etc.
import org.prevayler.Prevayler;
import org.prevayler.PrevaylerFactory;
import java.util.List;
import java.util.Iterator;
public class Retrieve {
public final static String DATA_FOLDER = "data";
public static void main(String[] args) {
try {
final Prevayler prevayler =
PrevaylerFactory.createPrevayler(
new PrevalentOrderSystem(), DATA_FOLDER);
List items = ((PrevalentOrderSystem)
prevayler.prevalentSystem())
.getItems();
for (Iterator ii = items.iterator();
ii.hasNext();) {
Item currentItem = (Item) ii.next();
System.out.println(currentItem.toString());
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
The result of running Retrieve should be similar to
the following:
Reading data\0000000000000000001.transactionLog...
1 - class ordersystem.BookItem:100 in stock:
Java Database Best Practices
2 - class ordersystem.SoftwareItem:100 in stock:
SuSE Linux Professional
It is useful when building and testing the system to be able to delete existing snapshots and transaction logs. Preclipse makes this easy. Right-click on the top-level project folder in Package Explorer and select "Prevayler -> Delete all snapshots and transactions," as shown in Figure 11.

Figure 11. Deleting snapshots and transactions
|
So far you have created a system that stores objects based on a
single class hierarchy. Adding the remaining BO classes and
transactions will show how a prevalent system deals with associated
classes. First, add the class OrderLine to the project
as an ordinary Java class:
import java.io.Serializable;
public class OrderLine implements Serializable {
private Item item;
private int quantity;
private double linePrice;
private double linePostage;
public OrderLine(){
}
public OrderLine(Item item, int quantity){
this.setItem(item);
this.setQuantity(quantity);
this.setLinePrice(item.getPrice() * quantity);
this.setLinePostage(item.getPostage() * quantity);
}
// getters/setters omitted...
}
Now add a new BO class called Order, in the same
way that you added the Item BO class. Appropriate fields
and methods are added to PrevalentOrderSystem. Add a
getOrders method similar to the getItems
method you added previously. Modify the generated Order
class as follows:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Order implements Serializable {
private long id;
private List orderLines;
public Order(){
}
public Order(long id) {
this.id = id;
orderLines = new ArrayList();
}
public void newOrderLine(Item item, int quantity) {
try {
item.order(quantity);
OrderLine ol = new OrderLine(item, quantity);
orderLines.add(ol);
} catch (Exception e) {
e.printStackTrace();
}
}
public double getTotalCost() {
double totalCost = 0.0;
for (Iterator itr = orderLines.iterator();
itr.hasNext();) {
OrderLine ol = (OrderLine) itr.next();
totalCost = ol.getLinePrice() +
ol.getLinePostage();
}
return totalCost;
}
// getters/setters omitted...
public String toString(){
return(id + " - " + this.getClass() + ":Cost $" +
this.getTotalCost());
}
}
Now add a new Create transaction for the Order BO class as you
did for the Item class. Do not select the
orderlines field for the transaction constructor in
the dialogue. Modify the executeAndQuery method as
follows:
public Object executeAndQuery(Object prevalentSystem, Date
executionTime) throws Exception {
Order or = ((PrevalentOrderSystem)prevalentSystem).addOrder();
return or;
}
The addOrder method in
PrevalentOrderSystem does not need to be modified--it simply creates a new Order object with the next
available id, and adds it to the system. No other
initialization is required--an Order is initially empty.
public Order addOrder() {
Order newOrder = new Order(nextOrderID++);
this.orders.add(newOrder);
return newOrder;
}
An empty Order is not very interesting--we need a
transaction to allow an Item to be added to an
Order. Add a new transaction to the Order
BO--a Change transaction, this time. The transaction should be
called OrderAddItemTranscation. You need to select at
least one field in the Create Transaction dialogue, shown in Figure
12. Select the orderlines field. This is not really
appropriate (as the transaction that Preclipse generates is aimed
at simply changing the value of a field, and you will be doing
something slightly different here), but you need to select at least
one field for the dialogue to complete.

Figure 12. Creating a Change transaction
Modify the created transaction to the following:
import java.util.Date;
import org.prevayler.TransactionWithQuery;
public class OrderAddItemTransaction
implements TransactionWithQuery {
private long id;
private long itemId;
private int quantity;
public OrderAddItemTransaction(long id, long itemId,
int quantity) {
this.id = id;
this.itemId = itemId;
this.quantity = quantity;
}
public OrderAddItemTransaction() {
}
public Object executeAndQuery(Object prevalentSystem,
Date executionTime throws Exception {
Order order = ((PrevalentOrderSystem)prevalentSystem).
getOrder(id);
Item item = ((PrevalentOrderSystem)prevalentSystem).
getItem(itemId);
order.newOrderLine(item, quantity);
item.order(quantity);
return order;
}
}
Note that this transaction changes the data in two ways--the
Order is updated with the new Orderline,
and the stock for the Item is updated. Finally, add a
new Change transaction for the Item BO class, called
ItemRestockTransaction, selecting the
currentStock field only in the dialogue. Modify the
created transaction to the following:
import java.util.Date;
import org.prevayler.TransactionWithQuery;
public class ItemRestockTransaction
implements TransactionWithQuery {
private long id;
private int newStock;
public ItemRestockTransaction(long id, int newStock) {
this.id = id;
this.newStock = newStock;
}
public ItemRestockTransaction() {
}
public Object executeAndQuery(Object prevalentSystem,
Date executionTime) throws Exception {
Item item = ((PrevalentOrderSystem)prevalentSystem).
getItem(id);
item.reStock(newStock);
return item;
}
}
|
Add the following code to Store so that it performs
transactions of all types:
// restock an item
prevayler.execute(new ItemRestockTransaction(1, 25));
System.out.println("Item restocked");
// add a new Order
prevayler.execute(new OrderCreateTransaction());
System.out.println("Order added");
// add 2 BookItems and 5 MultipackItems to
// the Order - should have ids 1 and 3
prevayler.execute(new OrderAddItemTransaction(1, 1, 2));
prevayler.execute(new OrderAddItemTransaction(1, 3, 5));
System.out.println("Items added to Order");
Delete existing snapshots and transaction logs, and run
Main and Store as before to create the
persistent objects. View the snapshot again, which should look
similar to Figure 13.

Figure 13. Snapshot for the full system
Add the following code to Retrieve and run the
class.
// get orders
List orders = ((PrevalentOrderSystem) prevayler.prevalentSystem())
.getOrders();
for (Iterator io = orders.iterator(); io.hasNext();) {
Order currentOrder = (Order) io.next();
System.out.println(currentOrder.toString());
}
The output should be similar to the following:
Reading data\0000000000000000001.transactionLog...
1 - class ordersystem.BookItem:123 in stock:
Java Database Best Practices
2 - class ordersystem.SoftwareItem:100 in stock:
SuSE Linux Professional
3 - class ordersystem.MultipackItem:100 in stock
(0 - class ordersystem.SoftwareItem:100 in stock:
Red Hat Enterprise Linux)
1 - class ordersystem.Order:Cost $6862.1
This clearly shows that the BookItem with
id = 1 has been restocked and that the
Order has been added.
Let's be clear about this--I am not in any way suggesting that everyone should abandon their databases and convert all their projects to prevalent systems. In most cases, this would be neither possible nor desirable. However, what I do suggest is that, in certain circumstances, it is worth considering prevalence as a simple alternative to using a database. The main characteristics of a suitable application are:
The main advantages of using prevalence are:
The example system described here gives a flavor of how Prevayler and Preclipse can be used to provide persistence for business objects. Persistence is achieved without thinking of the data in terms of anything other than the business objects themselves--no database, no drivers, no schema definition language, no queries, no mappings from classes to tables, and so on. The persistent data is stored in a single folder that can be backed up easily.
It is worth exploring further examples to see how different types of applications can make use of prevalence. The Preclipse site has an example of a GUI application built using the plugin, while the Presto pet store example on the Prevayler site show how a web application can make use of prevalence. In the latter case, the prevalent system class is made an attribute of the application server context so that it can be accessed from JSPs.
Jim Paterson is a Lecturer at Glasgow Caledonian University in the UK , specializing in web development and object-oriented software development.
|
Related Reading Java Data Objects |
Return to ONJava.com.
Copyright © 2009 O'Reilly Media, Inc.