The Persistence library eases the communication between an Adobe AIR application and an SQLite database. Both use different type systems (relational SQL versus object-oriented ActionScript). This Object Relational Mapping (ORM) library follows an iBATIS approach.
There are a large number of Object Relational Mapping (ORM) libraries available, some are opensource and others are commercial. There is generally two schools of thought though, which are typified in the Java-world by Hibernate and iBATIS .
Hibernate is very much driven from the OOPs perspective, where you start with a domain model and decorate it is with metadata. The role of the library is to read and write objects to the database, it generates the SQL for you and often generates schema from the object model. The advantages are you have a single source, your object model, and don't necessarily require any specialist database knowledge.
Alternatively, there is iBATIS. It drives from the database. You have a relational data model (often pre-existing), an object model and you want to map between the two. You still write SQL and the library supports the maintainability of the SQL and mapping too and from the SQL and your object model. The benefits of this approach are they allow you to use the "database as a database" and leverage the skills of a database specialist.
There is no right or wrong. Your choice may depend on personal preference, skills available within your team or scale of application.
The Cairngorm Persistence Library adopts the iBATIS approach. If you prefer Hibernate then there are Hibernate-like-ORM libraries for AIR. There is one from David Tucker and also one from Christophe Coenraets.
To use the Cairngorm Persistence Library the SQL is placed in a SQL map and the developer writes a Data Access Object (DAO). You can parametrize your SQL using indexed ('?') and named ('@' and ':') parameters. Although, indexed parameters are only supports for \<Select/> statements. For all other statement you must provide an Object and use named parameters.
The PersistenceClient Class is main entry point to the Persistence framework. Your DAO will use PersistenceClient to create an instance of ISqlSession , from which operations can be performed against the database. For each operation on your DAO you are recommended to create a new instance of ISqlSession as it maintains state. For example, if you are manually demarking the transactional boundary then the SqlSession maintains the batch. If you are only executing a single statement within a transaction then you may choose to create a single instance of ISqlSession.
The SQL is externalized to a SQL map. You are encouraged to use multiple SQL maps for maintainability. The recommendation is to scope SQL maps by functional area.
The sample application shows how to initialize the Persistence framework using Parsley. If you look at PersistenceContext.mxml it shows how the PersistenceClient is instantiated.
The SQL maps support the following grammar:
The first time a statement is executed it is cached in the ** StatementCache ** for use in future operations. We do this because a statement must be prepared (compiled) before it can be executed. Once a statement has been prepared it doesn't need to be prepared again.
All the metadata associated with the SQL is defined on the object. For example:
Person.as
package com.adobe.cairngorm.persistence.sample.domain
{
[Bindable]
public class Person
{
[Id]
[Mapped]
public var personId:int;
[Mapped]
public var firstName:String;
[Mapped]
public var lastName:String;
[Mapped(typeHandler="dateTypeHandler")]
public var dateOfBirth:Date;
[Mapped(typeHandler="booleanTypeHandler")]
public var friend:Boolean;
[Association]
public var address:Address;
}
}
As can be seen from the above example there are three metadata tags, which are described in the following table.
| Metadata | Description | Attribute |
|---|---|---|
| [Id] | Used to specify the unique-id of an item. When you insert a new item the returned rowId is set on the id-property. Please refer to the following pages of the SQLite documentation: ROWIDs and Autoincrement | |
| [Mapped] | Specifies a mapped property | By default is assumes the SQL parameter is called the same as the property. It also assumes the COLUMN name (for a \<Select/> statement is the same as the property, but in uppercase. |
| [Mapped(typeHandler="booleanTypeHandler")] - specifies a type handler, this is bidirectional. | ||
| [Mapped(parameter="name")] - allows you to override the SQL parameter name. | ||
| [Mapped(column="FIRSTNAME")] - allows you to override the COLUMN name. | ||
| [Association] | Defines a 1:1 association. The Persistence Library will follow associations. For example, if you are selecting from a PERSON table and an ADDRESS table it will construct a Person Object with an association to an Address Object. If you are inserting a new Address you can simply pass the Person Object, it will then get what it needs to the Address and use the id of the Person for the foreign-key constraint. |
If you need to translate the value coming back from the database you can use a type handler. The metadata show how to specify the type handler, but it must be declared on the SQL map as in the following example.
<type:DateTypeHandler id="dateTypeHandler" />
<type:BooleanTypeHandler id="booleanTypeHandler" />
The following type handlers are included in the Persistence distribution:
If you want to provide your own type handler you can implement the ITypeHandler interface.
When you execute an operation against the database an implicit transaction is created if one is not already in progress, this is done by the AIR SDK. If you explicitly call SqlSession.beginTransaction() a new transaction is started and all operations are batched. When you call SqlSession.commitTransaction() the batch of operations is executed sequentially. If there is an error executing one of the operations then rollback is called and no further operations in the batch are executed.
The database can be accessed synchronously or asynchronously. As a general rule all writes are executed synchronously and all reads and batched operations are executed asynchronously.
You can override this behavior by calling SqlSession.accessDatabaseAsynchronously() or SqlSession.accessDatabaseSynchronously(). For example, a call to SqlSession.accessDatabaseAsynchronously() will execute all subsequent operations asynchronously. If you want to revert to the default mode then call SqlSession.useDefaultDatabaseAccess().
Take a look at the PersistenceTest for a reference implementation.