Using the Repository

by Andrew Taylor <andy@benow.ca>
This tutorial covers creating, storing, retrieving and deleting objects using the BeNOW repository API. Standard JDBC persistance requires a manual mapping from object land to row/column land. This mapping can be tedious to write and maintain. The Repository API uses schema managment to create and modify tables and reflection to transparently persist objects. The result is natural and easy persistence of objects. After this tutorial, you'll be easily storing objects specific to your use case.
Contents
  1. Overview
  2. Creating Classes
  3. Storing Objects
  4. Fetching Objects
  5. Removing Objects
Overview
To begin this tutorial, you'll need to create a repository project and verify that a connection can be made. Please follow the Connecting to a Repository Tutorial. Once the connection has been validated, continue to create a class to be stored by the repository.
Creating Classes
Classes need little in order to be persisted by the repository. Classes to be persisted should extend JSQLObject. JSQLObject provides helper methods and logic for field management. By doing this in the super class, the descendant classes can be nice and simple. If the project was created as outlined in the connection tutorial, there will be some sample classes which this tutorial uses. First, we'll examine src/java/test/org/benow/repository/Person.java.

The class is declared as normal, and extends JSQLObject:

public class Person extends JSQLObject {

The fields are declared as normal, with annotations where appropriate:

  @StringLength(64)
  private final String firstName;
  @StringLength(64)
  @Searchable
  private final String lastName;
  @StringLength(128)
  private final String address;
  @StringLength(64)
  private final String city;
  @StringLength(16)
  private final String postal;
  private int heightInCMs;

The @StringLength annotation is used to indicate that a string field is to be stored with a specified length. If no StringLength is given for a string field, then the field is stored as a blob (or equivalent), which might have an impact on searching. Fixed length strings are easier to see when browsing the database.

The @Searchable annotation indicates that an upcase copy of the string is also to be stored. Having an upper case representation can make searching easier.

List fields are created as JSQLList objects. JSQLList (or rather the JSQLArrayList implementation) provides facilities for easing list population and storage. The declaration of a list is:

  private final JSQLList<Person> friends=new JSQLArrayList<Person>(this,"friends");

The JSQLArrayList must be created with a reference to the containing JSQLObject (this) and the name of the assigned field. This information is required for list navigation and population. It's not as easy as List/ArrayList, but close. If you want to manage object associations, use a JSQLList/JSQLArrayList pairing.

The rest is standard getters and setters, with the exception of getFriends():

  public List<Person> getFriends() {
    fetchFieldQuiet("friends");
    return friends;
  }

The fetchFieldQuiet("friends") causes the list values to be fetched. If the getFriends() is called repeatedly, the values will only be fetched the first time.

So, that's it for object creation. This is a very basic example, and more examples can be found in src/java/test/org/benow/repository directory. More advanced concepts coved in the sample classes include Abstract classes, Interfaces, etc.

Storing Objects
We'll now store several instances of Person. The doStore() method at the end of src/java/test/org/benow/repository/Tutorial.java can be run under un*x with bin/tutorial_repository.sh create or bin/tutorial_repository.bat create in windows. The following is performed within the main method:
Person andy=new Person("Andrew", "Taylor", "36 End of Internet Cl.", "Noosphere", "T4C 4C4");
andy.setHeightInCMs(185);
Person ben=new Person("Ben", "Yeardly", "420 Green Loop", "Sloquet Hotsprings", "T4C 0C0");
ben.setHeightInCMs(175);
Person john=new Person("John","Smith","123 Here Cres.","Heresville","910222");
john.setHeightInCMs(180);
Person mary=new Person("Mary","Smith","123 Here Cres.","Heresville","910222");
mary.setHeightInCMs(155);

andy.update();
ben.update();
john.update();
mary.update();

Instances are created for andy, ben, john and mary. All the people are then updated. The update causes the database schema to be created (if not existing) and the person values are stored to that schema.

Note that the above code is terse. Unlike in the connection tutorial, there is no repository getting, no connection taking and no transaction used. This is the simple way of working with the repository. On update, the default repository is taken and connections and transactions taken and used automatically. There is more overhead with this simple way, but it's fine for simple usage. More advanced usage, including the reuse of connections and transactions can be found in the src/java/test/org/benow/repository/ code.

The database at this point contains the following tables:

SQL> show tables;
       CLASS_SCHEMA_INFO                      PERMISSION                     
       PERMISSION_ROLE                        PERMISSION_USERS               
       PERSON                                 ROLE                           
       ROLE_USERS                             USERS                          
       USERS_USER_MODULE               

CLASS_SCHEMA_INFO is used internally to track schema change, PERMISSION, ROLE and USERS tables are used for security, and the PERSON table is the table created for the Person object. The PERSON table is:

SQL> show table person;
ID                              INTEGER Not Null 
FIRST_NAME                      VARCHAR(64) Nullable default null
LAST_NAME                       VARCHAR(64) Nullable default null
LAST_NAME_S                     VARCHAR(64) Nullable default null
ADDRESS                         VARCHAR(128) Nullable default null
CITY                            VARCHAR(64) Nullable default null
POSTAL                          VARCHAR(16) Nullable default null
HEIGHT_INC_MS                   INTEGER Nullable default null

Triggers on Table PERSON:
SET_PERSON_ID, Sequence: 0, Type: BEFORE INSERT, Active

ID is an autogenerated integer for each person (as provided by JSQLObject), and the columns correspond to the fields in the Person Object. The PERSON table itself contains:

ID FIRST_NAME LAST_NAME LAST_NAME_S ADDRESS                CITY         POSTAL      HEIGHT_INC_MS 
== ========== ========= =========== ====================== ============ =========== =============
1  Andrew     Taylor    TAYLOR      36 End of Internet Cl. Noosphere    T4C 4C4     185
2  Ben        Yeardly   YEARDLY     420 Green Loop         Sloquet      T4C 0C0     175
3  John       Smith     SMITH       123 Here Cres.         Heresville   910222      180
4  Mary       Smith     SMITH       123 Here Cres.         Heresville   910222      155 

... which correspond to the stored objects. Note that a sequential id has been generated for each object.

Fetching Objects
Objects are fetched by specifying a class, and object retrieval can be refined via field name or an sql statement. All the following fetches can be seen in the doFetch() method in src/java/test/org/benow/repository/Tutorial.java, which can be run under un*x with bin/tutorial_repository.sh fetch or bin/tutorial_repository.bat fetch in windows.
Fetching By Class
Fetching by class involves using a query object which gets objects of a certain class. The simplest of examples is:
List<Person> people = JSQLQuery.getAllObjectsQuery(Person.class).getObjects();
which fetches all instances of the Person class. Again, this is the terse syntax, which is made to be easy to use. The JSQLQuery helper class is used to create QueryContexts, which are the actual fetchers. The getObjects() method fetches all objects of the specified class, with SQL creation, object construction and object population done automatically. The getObjects() call shown above fetches all instances of Person and keeps them in memory, so it is inefficient with a large number of objects. If you want to fetch and deal with objects one-at-a-time, use the getObjectIterator() method:
ResultIterator peopleI = JSQLQuery.getAllObjectsQuery(Person.class).getObjectIterator();
while (peopleI.hasNext()) {
  Person p=(Person) peopleI.next();
  System.out.println(p);
}
The getObjectIterator() method has the advantage of using less memory.

If you want to page through the results, there is an alternative SubList getObjects(int starting, int spanning) which may be used:

SubList<Person> somePeople=JSQLQuery.getAllObjectsQuery(Person.class).getObjects(2, 2);
System.out.println("\nFetched "+somePeople.size()+" people of: "+somePeople.getSupersetSize()+
    " from index: "+somePeople.getStartPos());
That snippet fetches the 3rd and 4th Person, in id order. The SubList result can be used to guide further page based naigation. The equivalent for fetching all objects using paging is:
JSQLQuery.getAllObjectsQuery(Person.class).getObjects(SubList.FROM_START, SubList.SPANNING_ALL);
Again, all these examples are using the simple syntax, which is easy to use but has more overhead. Connection and transaction sharing are prefered when many operations over many objects are being done.
Restricting By Field
Results may be refined by specifying a field. The following examples fetches all Persons with a last name of Smith:
List<Person> smiths=JSQLQuery.getObjectByFieldQuery(Person.class, "lastName", "Smith").getObjects();
the restriction field name (lastName) is specified exactly how it is declared in java. The repository automatically does the translation to the table column name. To fetch with multiple fields use getObjectByFields(Class,String[],Object[]):
try {
  Person johnSmith=(Person) JSQLQuery.getObjectByFieldsQuery(Person.class,
      new String[] {"firstName","lastName"}, 
      new Object[] {"John","Smith"}).getObject();
  System.out.println(johnSmith);
} catch (NoSuchObjectException e) {
  System.err.println("No John Smith");
}
Note that this is using the getObject() method, which causes a NoSuchObjectException if the expected object is not found. By trapping the exception, the non existence can be handled.
Ordering Results by Fields
Result ordering can be done by specifying an argument to the QueryContext:
QueryContext ctx = JSQLQuery.getAllObjectsQuery(Person.class);
ctx.setOrderFields(new OrderField[]{
    new OrderField("lastName",Ordering.ASCENDING), 
    new OrderField("firstName",Ordering.ASCENDING), 
});
this fetches Person objects ordered by lastName then firstName. Again, the field names correspond to the exact field name used in the java class declaration, and the repository does the translation to column name.
Restricting By SQL (advanced)
More advanced fetch restrictions can be done via SQL. This is a more advanced usage, and should only be used when the previous field restrictions are insufficient. By creating a JSQLClassQuery with an SQL suffix, results can be further filtered:
JSQLClassQuery q = new JSQLClassQuery(Person.class);
String sql=q.getSQLColumnForField("heightInCMs")+">=180";
q.setQuerySuffix(sql);
List<Person> tallPeople=q.createContext().getObjects();
This is a bit messy, but allows fine control. First, the query for a Person is created. The SQL column name for the heightInCMs field (as declared in java) is included in the SQL query suffix, which is then assigned. When the SQL query is created, the field retrieval (select) will be combined with the suffix and invoked to return people over 180 CMs. The generated SQL query can be seen if debugging is turned on for org.benow.repository within etc/logging.xml:
select p.ID,p.FIRST_NAME,p.LAST_NAME,p.LAST_NAME_S,p.ADDRESS,p.CITY,p.POSTAL,p.HEIGHT_INC_MS from PERSON p 
where p.HEIGHT_INC_MS>=180
By using getSQLColumnForField(), there is no need to know the underlying SQL schema.
Removing Objects
Removing objects is very simple, just call the remove() method:
Person mary=(Person) JSQLQuery.getObjectByFieldQuery(Person.class, "firstName", "Mary").getObject();
mary.remove();
Mary is fetched, then removed. To remove all instances of a class, call deleteObjects(Class):
JSQLQuery.deleteObjects(Person.class);
Object removal code can be found in the doDelete() method in src/java/test/org/benow/repository/Tutorial.java, which can be run under un*x with bin/tutorial_repository.sh delete or bin/tutorial_repository.bat delete in windows.
The BeNOW tutorials are a work in progress. If you have any comments or suggestions, please email <andy@benow.ca>.