feb19.jp

Nobuhiro Takahashi
Designer / Engineer

AIR で SQLite を使う

AIR で SQLite を使う

AIR AS3 には SQLite のデータベースを作成、操作できる API が用意されているので、それでデータベースをクリクリいじることができます。テキストファイルでデータを管理するのはパフォーマンス的に遅い(多分)ので、 DB ファイルを使って高速にデータのやりとりを行います。まずは SQLite について↓。

SQL について

データベースを操作するときの言語、SQL 文。MySQL だろうが PostgreSQL だろうが Oracle だろうが SQLite だろうが、CREATE TABLE とか INSERT とか SQL 文の基本的な文はそのまま使えるみたい。
SQL についてはこの本がオススメだそうです。
これならわかるSQL 入門の入門

ただし、ビット単位の排他的論理和を求めるときに MySQL では ^ 演算子を使うのに対し、PostgreSQL では # 演算子を使うといった、コマゴマした違いがあるので、SQLite ならではの部分もきっちり理解しておきたいところ。

SQLite の書籍は AIR も題材にしている「SQLite入門 第2版」が発売中で、10月末に「SQLite ポケットリファレンス」がでるそうなのでこっちは予約です。

SQLite は PHP5 や Python、Ruby on Rails、iOS SDK、Android SDK でもデフォルトでバンドルされているので、ウェブコーダ―、ウェブデザイナー、組み込み系プログラマーも覚えておくと武器になるかもしれません。いままで自分もだいぶ曖昧だったので再度勉強開始。

--

本題

AIR で SQLite のデータベースを作り、データベースに接続し、テーブルを作り、レコードをいくつか追加し、そのテーブルの中身を拝見するプレイ。

--

まずは DB ファイルを作るクラス。

コメントアウトしている方は AIR app の専用ストレージディレクトリ。テストしやすいと思うのでデスクトップのディレクトリを使用しています。

CreateDB.as
package jp.feb19.data.sqllitetest
{
import flash.filesystem.File;

public class CreateDB
{
private var _db:File;

public function CreateDB()
{
create();
}

private function create():void
{
// ディレクトリの作成
// var dir:File = File.applicationStorageDirectory.resolvePath("db");
var dir:File = File.desktopDirectory.resolvePath("db");
dir.createDirectory();

// db ファイル作成
_db = dir.resolvePath("dbfile.db");
trace(_db.nativePath);
}

public function get dbFile():File
{
return _db;
}
}
}


--

DB への接続を確立するクラス。

open メソッドの引数に繋ぐ DB ファイルへの参照と、同期処理か非同期処理かの判定を入れて使います。

OpenDB.as
package jp.feb19.data.sqllitetest
{
import flash.data.SQLConnection;
import flash.errors.SQLError;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.SQLErrorEvent;
import flash.events.SQLEvent;
import flash.filesystem.File;

public class OpenDB extends EventDispatcher
{
private var _isOpen:Boolean;
private var _dbConnection:SQLConnection;

public function OpenDB()
{
_isOpen = false;
super();
}

public function get isOpen():Boolean
{
return _isOpen;
}

public function get dbConnection():SQLConnection
{
return _dbConnection;
}

public function open(dbFile:File, isAsync:Boolean = false):void
{
_dbConnection = new SQLConnection();

if ( isAsync )
{
// 非同期処理 。Event.CONNECT / IOErrorEvent.IO_ERROR を監視してください。
_dbConnection.addEventListener(SQLEvent.OPEN, openHandler);
_dbConnection.addEventListener(SQLErrorEvent.ERROR, errorHandler);
_dbConnection.openAsync(dbFile);
}
else
{
// 同期処理
try
{
_dbConnection.open(dbFile);
_isOpen = true;

trace("DB 接続完了");
}
catch(error:SQLError)
{
trace("エラー: " + error.message);
trace("詳細: " + error.details);
}
}
}

private function openHandler(event:SQLEvent):void
{
_dbConnection.removeEventListener(SQLEvent.OPEN, openHandler);
_dbConnection.removeEventListener(SQLErrorEvent.ERROR, errorHandler);

trace("DB 接続完了");
_isOpen = true;
dispatchEvent(new Event(Event.CONNECT));
}

private function errorHandler(event:SQLErrorEvent):void
{
_dbConnection.removeEventListener(SQLEvent.OPEN, openHandler);
_dbConnection.removeEventListener(SQLErrorEvent.ERROR, errorHandler);

trace("エラー");
_isOpen = false;
dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, event.error.message + ": " + event.error.details));
}
}
}


--

接続した DB へテーブルを作成するクラス。

create メソッドで作成。引数に同期か非同期かを与えてください。同期処理か非同期処理かは、DB への接続を確立したときの対応と同じになります。
CREATE TABLE 文を使っています。

CreateTable.as
package jp.feb19.data.sqllitetest
{
import flash.data.SQLConnection;
import flash.data.SQLStatement;
import flash.errors.SQLError;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.SQLErrorEvent;
import flash.events.SQLEvent;

public class CreateTable extends EventDispatcher
{
public function CreateTable()
{
super();
}

public function create(sqlConnection:SQLConnection, isAsync:Boolean):void
{
// "Member" テーブルが無いならばテーブルを作ります。
var sqlString:String = "CREATE TABLE IF NOT EXISTS Member(" +
"stuId INTEGER PRIMARY KEY AUTOINCREMENT," +
"firstName TEXT," +
"lastName TEXT" +
")";

var statement:SQLStatement = new SQLStatement();
statement.sqlConnection = sqlConnection;
statement.text = sqlString;

if (isAsync)
{
statement.addEventListener(SQLErrorEvent.ERROR, statementErrorHandler);
statement.addEventListener(SQLEvent.RESULT, statementResultHandler);

statement.execute();
}
else
{
try{
statement.execute();

trace("SQL 成功");
}
catch(error:SQLError)
{
trace("SQL 失敗");
trace("エラー: " + error.message);
trace("詳細: " + error.details);
}
}
}

private function statementErrorHandler(event:SQLErrorEvent):void
{
trace("SQL 失敗");
trace("エラー: " + event.error.message);
trace("詳細: " + event.error.details);
dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, event.error.message + ": " + event.error.details));

SQLStatement(event.target).removeEventListener(SQLErrorEvent.ERROR, statementErrorHandler);
SQLStatement(event.target).removeEventListener(SQLEvent.RESULT, statementResultHandler);
}

private function statementResultHandler(event:SQLEvent):void
{
trace("SQL 成功");
dispatchEvent(new Event(Event.COMPLETE));

SQLStatement(event.target).removeEventListener(SQLErrorEvent.ERROR, statementErrorHandler);
SQLStatement(event.target).removeEventListener(SQLEvent.RESULT, statementResultHandler);
}
}
}


--

作成したテーブルにデータを挿入するクラス。
これも同様です。加えて引数に二つ、挿入する値を与えています。INSERT 文を使っています。

InsertData.as
package jp.feb19.data.sqllitetest
{
import flash.data.SQLConnection;
import flash.data.SQLStatement;
import flash.errors.SQLError;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.SQLErrorEvent;
import flash.events.SQLEvent;

public class InsertData extends EventDispatcher
{
public function InsertData()
{
super();
}

public function insert(sqlConnection:SQLConnection, isAsync:Boolean, firstName:String, lastName:String):void
{
var sqlString:String = "INSERT INTO Member" +
"(firstName, lastName) VALUES (:firstName, :lastName)";

var statement:SQLStatement = new SQLStatement();
statement.sqlConnection = sqlConnection;
statement.text = sqlString;
statement.parameters[":firstName"] = firstName;
statement.parameters[":lastName"] = lastName;

if (isAsync)
{
statement.addEventListener(SQLErrorEvent.ERROR, statementErrorHandler);
statement.addEventListener(SQLEvent.RESULT, statementResultHandler);

statement.execute();
}
else
{
try{
statement.execute();

trace("SQL 成功");
}
catch(error:SQLError)
{
trace("SQL 失敗");
trace("エラー: " + error.message);
trace("詳細: " + error.details);
}
}
}

private function statementErrorHandler(event:SQLErrorEvent):void
{
trace("SQL 失敗");
trace("エラー: " + event.error.message);
trace("詳細: " + event.error.details);
dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, event.error.message + ": " + event.error.details));

SQLStatement(event.target).removeEventListener(SQLErrorEvent.ERROR, statementErrorHandler);
SQLStatement(event.target).removeEventListener(SQLEvent.RESULT, statementResultHandler);
}

private function statementResultHandler(event:SQLEvent):void
{
trace("SQL 成功");
dispatchEvent(new Event(Event.COMPLETE));

SQLStatement(event.target).removeEventListener(SQLErrorEvent.ERROR, statementErrorHandler);
SQLStatement(event.target).removeEventListener(SQLEvent.RESULT, statementResultHandler);
}
}
}

SQL 文に引数を直接つなげずにわざわざ parameter を使っているのは、SQL インジェクション攻撃対策です。また、パフォーマンスもよいそうです。
Adobe Flash Platform * ステートメント内でのパラメーターの使用

--

DB の中身を選択してデータを表示するクラス。
SELECT 文を使っています。

SelectData.as
package jp.feb19.data.sqllitetest
{
import flash.data.SQLConnection;
import flash.data.SQLResult;
import flash.data.SQLStatement;
import flash.errors.SQLError;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.SQLErrorEvent;
import flash.events.SQLEvent;

public class SelectData extends EventDispatcher
{
private var _result:Array;

public function SelectData()
{
super();
}

public function get result():Array
{
return _result;
}

public function select(sqlConnection:SQLConnection, isAsync:Boolean):void
{
var sqlString:String = "SELECT * FROM Member";

var statement:SQLStatement = new SQLStatement();
statement.sqlConnection = sqlConnection;
statement.text = sqlString;

if (isAsync)
{
statement.addEventListener(SQLErrorEvent.ERROR, statementErrorHandler);
statement.addEventListener(SQLEvent.RESULT, statementResultHandler);

statement.execute();
}
else
{
try{
statement.execute();
var result:SQLResult = statement.getResult();
_result = result.data is Array ? result.data : [{rows:result.rowsAffected}];

trace("SQL 成功");
}
catch(error:SQLError)
{
trace("SQL 失敗");
trace("エラー: " + error.message);
trace("詳細: " + error.details);
}
}
}

private function statementErrorHandler(event:SQLErrorEvent):void
{
trace("SQL 失敗");
trace("エラー: " + event.error.message);
trace("詳細: " + event.error.details);
dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, false, false, event.error.message + ": " + event.error.details));

SQLStatement(event.target).removeEventListener(SQLErrorEvent.ERROR, statementErrorHandler);
SQLStatement(event.target).removeEventListener(SQLEvent.RESULT, statementResultHandler);
}

private function statementResultHandler(event:SQLEvent):void
{
trace("SQL 成功");
var result:SQLResult = SQLStatement(event.target).getResult();
_result = result.data is Array ? result.data : [{rows:result.rowsAffected}];

dispatchEvent(new Event(Event.COMPLETE));

SQLStatement(event.target).removeEventListener(SQLErrorEvent.ERROR, statementErrorHandler);
SQLStatement(event.target).removeEventListener(SQLEvent.RESULT, statementResultHandler);
}
}
}

--

最後に、これらを使用するメインクラスです。
今回は同期処理にしています。
非同期処理にする場合、一つ一つの区切りでイベントを待つ必要がありますが、 SQLStatement を使っているので、キュー処理になるので割と同期っぽく作れます。
非同期実行モデルについて
同期処理は楽だけど、実務では同期している間アプリがフリーズする可能性があるので、なるべく非同期がオススメ。

Main_SQLLiteTest.as
package jp.feb19.data.sqllitetest
{
        import flash.display.Sprite;
        
        public class Main_SQLLiteTest extends Sprite
        {
                public function Main_SQLLiteTest()
                {
                        super();
                        
                        // 同期で今回はやります
                        const isAsync:Boolean = false;
                        
                        var createDB:CreateDB = new CreateDB();
                        
                        var openDB:OpenDB = new OpenDB();
                        openDB.open(createDB.dbFile, isAsync);
                        
                        var createTable:CreateTable = new CreateTable();
                        createTable.create(openDB.dbConnection, isAsync);
                        
                        var insertData:InsertData = new InsertData();
                        insertData.insert(openDB.dbConnection, isAsync, "unko", "unko");
                        insertData.insert(openDB.dbConnection, isAsync, "unkounko", "unkounko");
                        insertData.insert(openDB.dbConnection, isAsync, "unkounkounko", "unkounkounko");
                        
                        var selectData:SelectData = new SelectData();
                        selectData.select(openDB.dbConnection, isAsync);
                        
                        for (var s:String in selectData.result)
                        {
                                trace(s + ": " +selectData.result[s].firstName + " " + selectData.result[s].lastName);
                        }
                        // 0: unko unko
                        // 1: unkounko unkounko
                        // 2: unkounkounko unkounkounko
                }
        }
}

相変わらず頭悪いサンプルですみません。

Tweet Share Bookmark

Navigation

prev: O'Reilly Ebook Store でオライリー電子書籍を買ってみた
next: AS3 で GoF デザインパターン [01] - Iterator

Recently Entries