Transcript

Asynchronous SQL in Flex

Jerome Poudevigne

Aureliant

CTO

OrSiSo Adobe AIR Social network aggregator +

multiprotocol chat Facebook, Twitter, Flickr, Friendster,

Yahoo!, MSN, gTalk…

SQLite use by OrSiSo Data comes from the server Stored in SQLite database for fast local

access

UI heavy on SELECT

UI heavy on SELECT

UI heavy on SELECT

UI heavy on SELECT

UI heavy on SELECT

OrSiSo - contention on DB The user is browsing a lot of data

Click on a view icon => UI freeeze on a long SELECT

The data is updated in the background by querying a server 100’s of INSERT staitements =>UI freeze

too

Synchronous cache update

ZZZ….

Dataarrives

Local cacheupdated

Now you can use it again…

Asynchronous : Why ?

“Because synchronous operations execute in the main execution thread, all application functionality (including refreshing the screen and allowing mouse and keyboard interaction) is paused while the database operation or operations are performed.”

For long-running operations this can cause a noticeable pause in the application.

Async data set loading Create a class to send SQL search

query Make this class load chunk by chunk Assign an event every time a chunk is

loaded

public class ListLoader extends ArrayCollection{protected var stmt:SQLStatement = new SQLStatement();protected var result:SQLResult;

public function ListLoader(source:Array=null){ super(source);}

public function start(userCacheConn:SQLConnection):void{(..) stmt.addEventListener(SQLEvent.RESULT, onResult); stmt.text = "SELECT * from c_albums where description <> ''"; stmt.execute(30); dispatchEvent(new CollectionEvent(CollectionEventKind.RESET));}

public function next():void{

if (stmt.executing){

stmt.next(30);}

}

public function onResult(e:SQLEvent):void{ result = stmt.getResult(); dispatchEvent(new CollectionEvent(CollectionEvent.COLLECTION_CHANGE));}

protected var dbReadConn:SQLConnection;protected var theList:ListLoader = new ListLoader();[Bindable] protected var theData:ArrayCollection=new ArrayCollection();

theList.addEventListener(CollectionEvent.COLLECTION_CHANGE,onCollectionChange);theList.addEventListener(CollectionEventKind.RESET, onCollectionReset);

protected function onCollectionChange(e:CollectionEvent):void{

var d:Array = (e.target as ListLoader).data;for (var ii:int=0;ii<d.length;ii++){

theData.addItem(d[ii]);}if (!theList.complete){

theList.next(); // Or use callLater()}

}

trace ("db is opened a-synchronously");theList.start(dbReadConn);

<mx:List height="100%" width="100%" dataProvider="{theData}” itemRenderer="AlbumRenderer" />

Inserting data

Synchronous cache update

ZZZ….

Dataarrives

Local cacheupdated

Now you can use it again…

SQLConnection-synchronous We are used to thattry

{

SQLStatement.exec(“SOME SQL”);

SQLStatement.exec(“MORE SQL”);

}

catch (…)

{

}

Asynchronous

(100’s of XML items)

Dataarrives

Update in the background

User interface never locked

SQLConnection-asynchronous Less familiarStmt.text=“DO SOME SQL”;stmt.addEventListener(COMPLETE, OnNextStep);stmt.execute();…

Function onNextStep(e:SQLEvent){

stmt.text=“DO MORE SQL”stmt.addEventListener(COMPLETE, onDone);stmt.execute();

}Function onDone(e:SQLEvent){

// Whew !}

Issues How can you write 150 insert

statements by using the code above ? What if you need transactions ?

Transactions-asynchronous Naïve 1

Connection.beginTransaction();

Stmt.text=“DO SOME SQL”;stmt.execute();Stmt.text=“DO SOME SQL”;stmt.execute();Stmt.text=“DO SOME SQL”;stmt.execute();

Connection.commit();

Does not work. In Async mode Flex does not guarantee that SQL execution is in the same order as the Flex lines of code…

Transactions-asynchronous Naïve 2Connection.beginTransaction();

Function onStarted(..){ Stmt.text=“DO SOME SQL”; stmt.addEventListener(COMPLETE, OnNextStep); stmt.execute();}

Function onNextStep(e:SQLEvent){ stmt.text=“DO MORE SQL” stmt.addEventListener(COMPLETE, onDone); stmt.execute();}Function onDone(e:SQLEvent){

// Whew !! And what if I had 150 of these… }

A solution : StatementList class

public class StatementList extends EventDispatcher{ public function StatementList(

conn:SQLConnection, statements:Object, withTransaction:Boolean=false )

public function execute ():void

}

Encapsulate a loop in a class

public class StatementList extends EventDispatcher implements ISQLExecutable{public var _execStack:Array = new Array();public function StatementList(

conn:SQLConnection, statements:Object, withTransaction:Boolean=false)

{__execStack.push(statements);}

}

public function execute ():void{

trace ("Beginning transaction",text);_conn.begin(null, new Responder(onBeginTransaction,

onErrorStartingTransaction));}

protected function onBeginTransaction(e:Object=null):void{_ transactionStarted = true; executeNext();}

protected function executeNext():void{

if (_execStack.length > 0){_cStmt = _execStack.shift() as SQLStatement;(..);_cStmt.addEventListener(SQLEvent.RESULT, onResult,false,0,true);_cStmt.execute();}else{

trace ("Done.Committing transaction ",text); _conn.commit(new Responder(onConclude, onCannotCommit));}

}

protected function onResult(e:Object=null):void{

if (e is SQLEvent){ (e.target as EventDispatcher).removeEventListener(SQLEvent.RESULT, onResult);}

executeNext();}

Still having issues…protected function executeNext():void{

if (_execStack.length > 0){_cStmt = _execStack.shift() (..);_cStmt.addEventListener(SQLEvent.RESULT, onResult,false,0,true);_cStmt.execute();}

}

protected function onResult(e:Object=null):void{

executeNext();}

Function onClickListFriends(..){Stmt.text=“SELECT FRIENDS WHERE”;stmt.execute();}

Guaranteed locking…

One level (of abstraction) up… Create an execution queue Everything is IExecutable

package com.aureliant.model.cache{

import flash.events.IEventDispatcher;

public interface IExecutable extends IEventDispatcher{

function execute():void;function get canceled():Boolean;

}}

public class ExecutionQueue{protected var _theQueue:Array = new Array();protected var _current:IExecutable;

public function add(item:IExecutable):void{ _theQueue.push(item);

if (_current == null) //start immediately if empty{ onExecuteNext();}

}protected function onExecuteNext (e:Event=null):void{

if (_current != null){ _current.removeEventListener(StatementListEvent.FINISHED, onExecuteNext); _current = null;}while (_theQueue.length > 0){

var tmp:IExecutable = _theQueue.shift() as IExecutable;if (!tmp.canceled){ _current = tmp; break;}

} if (_current != null) { _current.addEventListener(StatementListEvent.FINISHED, onExecuteNext,false,0,true); _current.execute(); }}}

public function storeAlerts(rawList:Object, responder:IResponder=null):void{

// Create the INSERT statements for the alerts itemsvar saver:StatementList = new StatementList(withTransaction);

for (var ii:int=0;ii<(theList as ArrayCollection).length; ii++){ anItem = (theList as ArrayCollection).getItemAt(ii); saver.addSQL( "INSERT OR IGNORE INTO showedalert(guid,alreadyShowed) values ('"+

anItem.guid+"',0)");}

ExecutionQueue.instance.add(saver); // Put in the queue}

Demo Questions ?