back

Object relational mapping for the
Adobe AIR developer

by David Tucker

The embedded local database included with Adobe AIR gives AIR developers a great deal of power. Now you can develop extensive relational data models that exist solely on the user's hard drive. As you develop powerful applications with Adobe AIR, you may be interested in using Object Relational Mapping (ORM) to lessen the amount of SQL you need to write.

In this two-part series, I introduce you to one of the options for ORM in AIR — FlexORM — as well as highlight some additional tools you may want to use. Through the course of these articles, you will learn how to set up the ORM to work with a database and map both simple and complex relationships in an AIR application. In addition, I examine what an ORM is, and what terminology you will need to know when working with FlexORM.

To follow along, download the sample files. (180K, ZIP).

Introduction to ORM

Server-side developers have been using ORM tools for many years. At its core, an ORM simply enables you to think of your application's data in terms of objects, not database tables. To understand this, consider the class diagram in Figure 1.

Sample contact class diagram.

Figure 1. Sample contact class diagram.

In Figure 1, we are mapping a contact. In this case, we are creating a contact with a first name, last name, group of e-mail addresses, group of phone numbers, and group of addresses. This is not an unrealistic example, and if you were creating a real contact management application, your class model would probably be a great deal more complex than this. However, this should help illustrate the benefit of using an ORM.

While our class diagram includes four classes, to create the same data schema in a relational database, we need at least eight database tables (see Figure 2). In this case, each of our data types has a table, but we also have created a linking table to connect addresses, e-mail addresses, and phone numbers to our contact object. In addition, we have created a type table to store the type for each of those connected objects.

Contacts database tables diagram.

Figure 2. Contacts database tables diagram.

This particular collection of tables creates many complexities when dealing with the application's data. When creating a new contact, we need to perform the following steps:

  1. Create the Contact table (if it hasn't already been created).
  2. Insert the first name, last name, and ID into the contact table. Retrieve the primary key (ID) from the Insert action.
  3. Create the Address table (if hasn't already been created).
  4. Insert the address data into the Address table. Retrieve the primary keys (ID) from the Insert actions.
  5. Create the Contact_Address_Bridge table (if it hasn't already been created).
  6. Insert the primary key of each address and the contact into the Contact_Address_Bridge table.
  7. Perform steps 3–6 for phone numbers and e-mail addresses.

In this case, creating a contact becomes a very complex task. While performing this with a synchronous connection for the database would be tedious, it would be unwieldy if you were performing all of these queries with an asynchronous connection.

In addition to this complexity, you would have to tackle some additional questions as you develop the application, such as:

Why does this complexity exist? If we look at this from a theoretical viewpoint, simply creating a single piece of data for your application should not be this complex. This problem is linked to the object-relational paradigm mismatch . While relational databases are the standard for storing data, object-oriented programming (OOP) is the standard for defining data. This leads to a mismatch. Normalized data in a database will not match up with proper OOP class architecture. This is where an ORM comes in. Just as its name indicates, ORM provides a mapping between the object-oriented classes and the relational database. While object-oriented databases do exist, they represent only a minimal percentage of the market. In addition, Adobe AIR only supports the relational SQLite database locally.

According to Christian Bauer and Gavin King1, an ORM provides four main features:

1. Bauer, Christian and Gavin King. Java Persistence with Hibernate. New York: Manning, 2006.

Some ORMs provide all four of these functions, and these are referred to as full-featured ORMs. In reality, none of the ORMs for AIR are full featured yet, but they still provide a great deal of functionality that developers can utilize.

A quick search across Google Code, Github, RIAForge, and other open source sites reveals many different AIR ORM solutions. Some of the solutions meet one or two of the above criteria, but I found one open source project that meets the first, third, and fourth. This project, FlexORM, is an open source project hosted at RIAForge and is released under the BSD license.

Defining object mapping with FlexORM

Few things are as central to an ORM as the way the tool defines the object mapping. In this case, FlexORM utilizes metadata to define the object mapping. This is my personal preference for any ORM tool because:

To begin, we will create a mapping for a simplified version of our Contact class.

package vo
{
	[Bindable]
	[Table( name="CONTACT" )]
	public class ContactVO
	{
		
		[Id]
		public var id:int;
		[Column( name="first_name" )]
		public var firstName:String;
		[Column( name="last_name" )]
		public var lastName:String;
		
	}
}

In this example, you can see that we have a basic ActionScript class. This will save our basic contact data (which initially will have only a first name and last name). There are a few metadata tags that Flex developers should recognize as being out of the ordinary. These are the metadata tags used by FlexORM. For example, the [Table] metadata tag is what FlexORM uses to define this class as an entity that needs to be persisted. This also has an argument that allows you to define the name of the table that will store the entity. In addition, there is an [Id] tag that specifies which property will serve as the primary key for the database. This needs to be type int. Finally, [Column] tags are used to define the database columns. These are not required if you want the name of the property to be the name of the database column.

For creating a basic model object without any complex relationships, these are the only metadata tags you need to work with in FlexORM. One important step you need to follow, however, is to be sure that the Flex compiler keeps all of these metadata tags when it compiles your application. First, right-click your project in Adobe Flex Builder and choose Properties. Select the Flex Compiler option on the left and add the following line to whatever is currently in the Additional Compiler Arguments field:

-keep-as3-metadata+=Table,Id,Column,ManyToOne,OneToMany,ManyToMany,Transient

Building a sample application with FlexORM

Once these settings have been added and the basic model class is defined, we can create a basic application to test the ORM. In this case, I already created an AIR project and saved the model class from the first code sample above into a package named vo.

Next, we need to add the FlexORM library to our project. You can download the file from RIAForge or from the sample files included with this article. Then, we need to add the flexorm.swc file to the project:

  1. Right-click the project name and choose Flex Build Path from the left Library Path. Then click the Add SWC button.
  2. Select the file on the hard drive and click OK. The FlexORM library has been added to the project.

Now that we have added FlexORM to the application, we can construct the actual test application. In the main application file, I have the following code that is executed on the creationComplete event of the main WindowedApplication:

protected var entityManager:EntityManager = EntityManager.instance;

protected function application_creationCompleteHandler( event:FlexEvent ):void
{
	var dbFile:File = File.applicationStorageDirectory.resolvePath( "contacts.db" );
	var sqlConnection:SQLConnection = new SQLConnection();
	sqlConnection.open( dbFile );
	entityManager.sqlConnection = sqlConnection;
}

In this example, the first thing we do is create an instance of the EntityManager. Because the EntityManager is a singleton, we get the instance by referencing the static property instance on the EntityManager class. Next, we define the database file. In this case, it's a file named contacts.db in the application storage directory. Then, the SQLConnection is created. It is pointed to the database file and then it opens a connection using the synchronous mode. Finally, the instance of the SQLConnection is set on the EntityManager's sqlConnection property.

Next, a simple form is created to collect the first and last name of the contact. This form also has a Save Contact button. When this button is clicked, the saveContact() method is called:

protected function saveContact():void
{
	var contact:ContactVO = new ContactVO();
	contact.firstName = firstNameInput.text;
	contact.lastName = lastNameInput.text;
	entityManager.save( contact );
}

In this method, a new instance of the ContactVO is created. The first and last names are assigned, and the save method of the EntityManager is called with the instance of the ContactVO passed into it. This takes care of the entire save process. When that is called, the following SQL is executed on the contacts.db database:

create table if not exists main.CONTACTS(id integer primary key autoincrement,first_name text,
updated_at date,last_name text,created_at date,marked_for_deletion boolean)

create index if not exists main.c_o_n_t_a_c_t_id_idx_id_idx on CONTACTS(id asc)

insert into main.CONTACTS( first_name, updated_at, last_name, created_at, marked_for_deletion) 
values (:firstName, :updatedAt, :lastName, :createdAt, :markedForDeletion)

The first thing FlexORM does is create the Contacts table based on the mapping you have defined in the model class. It also adds some additional properties that are used internally by FlexORM. Next, it creates an index for the table based on the primary key. Finally, it uses an insert statement with named parameters to insert your values into the database. If you use an SQLite administration tool (such as the SQLite Admin tool for AIR developed by Christophe Coenraets), you can view the data in the table to verify that is was inserted correctly. In Figure 3, you can see that I have verified the data was inserted correctly.

SQLite administration tool.

Figure 3. SQLite administration tool.

Moving forward

In this example, we have accomplished many things with only a little ActionScript. We connected FlexORM to a file on the local file system. We used metadata to define simple mapping within the application's data model. And we utilized the EntityManager in FlexORM to save new instances of a data model class.

I hope you see some of the benefits of using an ORM already. However, the true benefits of using an ORM will become extremely obvious in the second part of this article when I cover:

As I mentioned previously, ORM is a tool that should be in any AIR developer's toolbox. With the techniques from this two-part article, you will be able to develop AIR applications that logically persist data without writing a single line of SQL code.

Additional ORM tools

After I started this article, there was an interesting development in the AIR ORM field. The Adobe ColdFusion 9 public beta shipped with an AIR ORM that is used with the ColdFusion 9 AIR online/offline data synchronization feature. Interestingly enough, this ORM does not require a server-side component to work. It can be used entirely on the client side. While this is still in beta, it is a promising solution that AIR developers should keep an eye on because it is similar to FlexORM in its implementation.

‹ Back


David Tucker is a software engineer for Universal Mind, focusing on the next generation of RIAs with Adobe Flex and AIR. He is based in Savannah, Georgia, but you can find him online at DavidTucker.net.