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 Variable
s.
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 String
s 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.