Tutorial

We present now a brief tutorial about JSO. After reading the tutorial please take a time to see some examples.

Coding storable classes

To create a storable class you need to respect some rules, but basically you just need to declare it is storable and create a void constructor. To declare the class is storable simply make it implement the Storable interface. This interface is only a marker an no extra work is needed, although more things can be done as we will see.

import net.sf.jso.Storable;

public class MyStorableClass implements Storable {

  public MyStorableClass() {
    // void constructor must exist!
  }

  ...
  
}

Specifying class metadata

Storable classes are inspected by JSO at the first time they are used. Many characteristics can be obtained, but some others can not. For instance, how could you declare indices for you class? Or the fields that compose class' summary? All this can be informed through class metadata.

To inform class metadata to JSO you need to create a static method, named jsoClassMetadata(), that returns an object of the class StorableClassMetadata. This object will be used by JSO to obtain more "storable" characteristcs of the class. Below we show a classe declaring metadata with information about indices and summary fields.

import net.sf.jso.Storable;
import net.sf.jso.schema.StorableClassMetadata;
import net.sf.jso.schema.StorableIndex;

public class User implements Storable {

  // Class metadata.
  private static StorableClassMetadata classMetadata;
  
  // Returns class metadata.
  private static StorableClassMetadata jsoClassMetadata() {
    if(classMetadata == null) {
      // Summary fields.
      String[] summary = new String[] {"name","login","email"};
      // Index for name and unique index for login.
      StorableIndex nameNdx = new StorableIndex("name",new String[] {"name"});
      StorableIndex loginNdx = new StorableIndex("login",new String[] {"login"},true);
      StorableIndex[] ndxs = new StorableIndex[] {nameNdx,loginNdx};
      // Informs summary and indices.
      classMetadata = new StorableClassMetadata(summary,ndxs);
    }
    return classMetadata;
  }

  ...
  
}

Object metadata

Sometimes may be useful to know a little more about the storage process of an object. For that reason JSO provides a way to make such information available to you, through object metadata.

Storable classes may declare a field, named jsoMetadata(), that will hold storate metadata as an object of the class (actually an interface) StorableMetadata. After every persistence operation on the object, JSO will update storage metadata on this field. The class below implements a method to make objects' OIDs available to the application.

import net.sf.jso.OID;
import net.sf.jso.Storable;
import net.sf.jso.StorableMetadata;

public class User implements Storable {

  // Object metadata.
  private StorableMetadata jsoMetadata;

  public OID getOID() {
    OID oid = null;
    if(jsoMetadata != null) {
      oid = jsoMetadata.getOID();
    }
    return oid;
  }

  ...

}

Callback methods

Another feature offered by JSO are the callback methods. A storable class may declare the methods jsoPreStore(), jsoPostRetrieve() and jsoPreDelete() to become aware of persistence events. As showed below the storable class declares two transient fields, that will not be stored. However, it also implements the jsoPostRetrieve() method, so that when an object is retrieved from the database its value (all fields) will be consistent.

import net.sf.jso.Storable;

public class ComplexNumber implements Storable, Cloneable {

  // Using name "real_" instead of "real" to avoid database reserved words.
  private double real_;
  private double imaginary;
  private transient double modulus;
  private transient double argument;
    
  public ComplexNumber() {
  }

  public ComplexNumber(double r, double i) {
    set(r,i);
  }

  public void set(double r, double i) {
    real_ = r;
    imaginary = i;
    calculatePolar();
  }

  private void jsoPostRetrieve() {
    // Only cartesian coordinates are stored.
    // After retrieval polar coordinates are calculated.
    calculatePolar();
  }

  private void calculatePolar() {
    modulus = Math.sqrt(real_*real_ + imaginary*imaginary);
    argument = Math.atan2(imaginary,real_);
  }

  ...

}

Storage environment

Before start requesting persitence services to JSO, an application program must start the storage controller and the storage manager. Or, in case of a distributed application, the program must start a manager and connect it to the controller. To make things more easy JSO has a class that represents the storage environment of the application. The interface StorageEnvironment, implemented by the class JSOEnvironment, has some methods to facilitate environment start up and shut down.

import net.sf.jso.StorageEnvironment;
import net.sf.jso.StorageManager;
import net.sf.jso.engine.JSOEnvironment;

public class MyProgram {

  public static void main(String[] args) throws Exception {
    // Starts storage environment to a local application.
    StorageEnvironment env = new JSOEnvironment();
    env.startLocal();    
    // Gets reference to the storage manager.
    StorageManager manager = env.getManager();

    ...

    // Stops storage environment.
    env.stop();    
  }

}
When the environment is started, the storage manager may be accessed through the manager reference and persistence services can be requested.

Storing an object

An object may be stored through a call to one of the store() methods. The OID of the stored object is returned.

    ...

    // Creates clients and accounts and makes deposits. 
    Client xexeo = new Client("Geraldo Xexeo","987.654.321-01","Fake St. 123");
    Client joao = new Client("Joao Alexandre","123.456.789-10","Evergreen Tr. 742");
    SpecialAccount account1 = new SpecialAccount(1,xexeo,10000);
    account1.deposit(20000); 
    Account account2 = new Account(2,joao);
    account2.deposit(3000); 
    xexeo.getAccounts().add(account1);
    joao.getAccounts().add(account2);

    // Stores clients.
    // Accounts and operations are also stored. (persistence by reachability)
    manager.store(xexeo);
    manager.store(joao);

    ...

Retrieving an object

An object may be retrieved from the database through a call to one of the retrieve() methods. The OID of the stored object must be informed. If the OID is not known then a query must be done.

    ...

    // Creates a complex number and clone it.
    ComplexNumber c1 = new ComplexNumber(3,4);
    ComplexNumber c2 = (ComplexNumber)c1.clone(); 

    // Stores the complex number.
    OID o1 = manager.store(c1);

    // Destroys the reference and retrieve the object whit a new one.
    c1 = null;
    ComplexNumber rc1 = (ComplexNumber)manager.retrieve(o1);

    ...

Updating an object

Updating an object means retrieving an object, modifying it and storing it with its new value.

    ...

    // Creates an user.
    User ned = new User();
    ned.setName("Ned Flanders");
    ned.setLogin("flanders");
    ned.setEmail("flanders@heaven.com");
    ned.setAddress("Evergreen Tr. 743");
    ned.setPhone("555-8304");
    ned.setComments("Hello, I'm Ned Flanders.");

    // Stores the user.
    OID oidNed = manager.store(ned);

    // Destroys the reference and retrieve the object whit a new one.
    ned = null;
    User retNed = (User)manager.retrieve(oidNed);

    // Modifies the user.
    retNed.setComments("Heydilly-ho! I'm Ned Flanders! Okily-dokily-doo!");

    // Updates the user.
    manager.store(retNed);

    ...

Deleting an object

An object may be delete through a call to one of the delete() methods. The OID of the stored object or the object itself may be used.

    ...
    // Stores the complex number.
    OID o1 = manager.store(c1);

    // Destroys the reference and retrieve the object whit a new one.
    c1 = null;
    ComplexNumber rc1 = (ComplexNumber)manager.retrieve(o1);

    ...

    // Deletes the object.
    // Can use this method.
    // (In this case callback method jsoPreDelete() will not be called)
    manager.delete(o1);
    // Or can use this one.
    // (In this case jsoPreDelete() will be called)
    //manager.delete(rc1);

    ...

Using transactions

Every persistence operation, performed through the methods discussed above (store(), retrieve() and delete()), is executed within a transaction. If none has been specified by the program JSO automatically uses one. JSO starts a transaction, executes the operation and then commits the started transaction. However, if a some task must perform more than one operation a transaction should be explicitly used.

JSO allows each thread to have one (and only one) transaction. A reference to current thread's transaction may be obtained through storage manager's getTransaction(). The Transaction interface has methods to begin, commit and roll back the transaction.

    ...

    // Begins a transaction.
    Transaction tx = manager.getTransaction();
    tx.begin();

    ...

    Account account = (Account)accounts.get(0);
    System.out.println(account.getNumber() + ": " + account.getClient().getName());
    System.out.println("Initial balance: " + account.getBalance());
    // Withdraws.
    account.withdraw(100);
    account.withdraw(70);
    // Stores account after operations.
    manager.store(account);

    // Ends transaction.
    tx.commit();

    ...
If any exceptions are thrown during the execution of the transaction JSO automatically rolls it back. The reference to the transaction becomes unusable and a new reference should be acquired by the thread.

Querying the database

JSO allows querying the database through methods count(), countPages() and retrieve() of the interface QueryProcessor, which is extended by the StorageManager interface. These methods receive parameters that inform JSO which objects should be returned (or counted) from the database. The class Parameters can hold information about selection and order criteria, pagination, summarization and the usage of polymorphism. It also holds an array of Variables.

The variables represent the objects processed during query execution. A variable has an unique name within the parameters and has a class. At least one variable must be used, and if more than one is used the first one is considered as the "main" variable. The objects corresponding to the "main" variable are the ones retrieved by the query.

Selection criteria is done by the Criteria class. This class is actually used to build criteria expressions, while the Predicate class can in fact define selection criteria. A predicate is formed of a left operand, an operator (CriteriaOperator) and a right operand. The left operand always refers to the field of a variable. If no variable is specified to the left operand JSO uses the "main" variable. The right operand may be a value or field of a variable. In the last case a variable must be specified. Whenever more than one predicate must be used a Criteria object is used to create a predicate expression.

To order the results the Order and FieldOrder classes may be used. In an analogous way to the selection criteria the Order class is used to create expressions, while the FieldOrder class effectively defines order criteria. Such definitions consists of the field of a variable and an operator (OrderOperator). Again, if no variable is specified JSO uses the "main" variable.

Parameters also allow the list of objects selected by a query to be devided in pages, of pageSize objects, and make the result equal to the objects listed in a defined page.

Last, but not least, parameters hold indications whether summary objects should be returned and if polimorphism should be used (default).

Below, two examples of queries are shown.

    ...

    // Lists clients.
    Parameters paramsClients = new Parameters(Client.class);
    // Order by name.
    paramsClients.order = new FieldOrder("name",OrderOperator.ASC);
    // Use summaries.
    paramsClients.summaries = true;
    List clients = manager.retrieve(paramsClients);
    System.out.println(clients.size() + " client(s) found:");
    for(int i = 0; i < clients.size(); ++i) {
      Client client = (Client)clients.get(i);
      System.out.println(client.getName() + " - " + client.getIdentity());
    }

    ...
    ...

    // Gets account 1.
    Parameters params = new Parameters(Account.class);
    // Account number.
    Integer number = new Integer(1);
    // Search predicate.
    params.criteria = new Predicate("number",CriteriaOperator.EQUAL,number);
    List accounts = manager.retrieve(params);
    // We know account 1 exists, but query result could be empty...
    Account account = (Account)accounts.get(0);
    System.out.println(account.getNumber() + ": " + account.getClient().getName());

    ...
The lists returned as the results of the queries are unmodifiable.

Using text fields

Text fields should be use whenever you would like to store long strings (longer than 255 characters). The Text class can be used almost seamlessly as the String class. The class below uses a text field and implements helper methods that receive Strings to set the field's value.

import net.sf.jso.Storable;
import net.sf.jso.text.Text;

public class User implements Storable {

  private String name;
  private String login;
  private String email;
  private String address;
  private String phone;
  private Text comments;
    
  public User() {
  }

  public void setComments(String someComments) {
    comments = new Text(someComments);
  }

  public void setComments(Text someComments) {
    comments = someComments;
  }

  public Text getComments() {
    return comments;
  }

  ...
JSO will use a LONGVARCHAR column to store this field.