スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

AndroidにおけるSQLiteを使いやすくする Vol.2

【2011/12/01 追記】
ADT 14.0.0 からライブラリプロジェクトを参照している場合、リソースIDが static final ではなく、ただの static として生成されるようになったので、そのようなプロジェクトの場合はアノテーションでXMLのリソースIDを指定できなくなりました。次回バージョンアップ時に対応致します。バージョンアップ後は、Frontier_android からO/Rマッピングツール部分を FrontierDao という別ライブラリとして分離します。(O/Rマッピング機能だけ利用する場合は、FrontierDao のみをインポートすれば使えるようになります)
-------------------

前回の続きで、実際にO/Rマッピングツールを作成していくことになるのですが、ここで私は「iBATIS (MyBATIS)」とほぼ同じ設計でO/Rマッピングツールを作成しました。このO/Rマッピングツールのいいところは

・SQLを完全に制御できる (速度が遅くなりがちなSQLiteから見ると、ここは非常に重要)
・SQLとソースコードを完全に分離できるため、管理がしやすい
・条件の違いによって動的にSQLを組み立てて実行できる (同じようなSQL文を複数作成する必要がなくなるため、不具合も発生しにくい)

とはいえ、携帯アプリケーションであることを考えると、マッピングしたデータを全てメモリへ保持する(=メモリを大量に消費しがちな)iBATISをそのまま持ってくることは当然できません。完全に一から作成する必要があります。


じゃあ、どう作るんだ!?という話になるのですが、さすがにこればかりはBlogで少しずつ説明するのは規模が大きい内容ですので、すでに私の方で作成したライブラリをご提供させて頂きます。

[Frontier android library]
https://github.com/k-kou/Frontier_android


上記のライブラリ(frontier-android-x.x.x.jar)をプロジェクトにインポートしてご利用ください。
詳しいマニュアルはまだ殆どありませんが、一応上記のgithubにJavadocも同梱しておりますので、ちょっとした参考にはなるかと思います。
(Javadocはお手数ですがJARを解凍して御覧ください)


さて、それでは簡単な使い方をご説明します。


01. SQLiteデータベースを予め作成しておく

Android標準のSQLiteOpenHelperを利用する場合、自前でSQLを記述し、それをアプリ起動時に実行させてデータベースのテーブルを作るという方が多いかと思われます。とはいえ、この方法ではテーブル数が増えれば増えるほど大きな負荷になることは必至です。そこで、データベーステーブルは最初から作成し、assetsフォルダへ格納しておく方法を本ライブラリでは前提としています。

SQLiteデータベースファイルの作成には、私は以下のソフトを利用しています。

[SQLite Expert]
http://www.sqliteexpert.com/


このソフトを利用している理由というのが、使いやすいこともそうですが、なにより「データベースバージョンを予め設定しておける」からです。意外にも、これができるGUIソフトは有償・無償を含めて殆どなく、私が知る限りでは、SQLite Expert以外ではFirefoxプラグインの「SQLite Manager」くらいです。

本ライブラリではDBファイルに設定されたバージョンを見て自動的にバージョンアップ処理を行う仕組みがあります。そのため、このようにバージョンを設定できるGUIソフトを利用しましょう。

ちなみに、SQLite Expertは有料版もありますが、無料版でも機能的には十分です。ただしWindows専用なので、Macユーザの方はFirefoxプラグインの「SQLite Manager」を利用しましょう。


02. エンティティを作成する

エンティティのクラスを作成します。エンティティは、簡単にいえばアクセスするデータベースの構造をJava形式で表現したクラスです。

ここでは例として、以下のようなテーブルへアクセスすることを考えます。

-------------------------
CREATE TABLE [Memos] (
[pk_Memos] INTEGER NOT NULL ON CONFLICT ROLLBACK PRIMARY KEY AUTOINCREMENT,
[Text] VARCHAR(200),
[ImagePath] TEXT,
[ImageThumbnailPath] TEXT)
---------------------------


上記テーブルに対応する、エンティティのソースは以下のようになります。


/**
* メモエンティティ。
*
* @author Kou
*
*/
public class MemosEntity implements Serializable {


/**
* シリアルバージョンUID
*/
private static final long serialVersionUID = -8826714454635917736L;

/**
* メモID
*/
private Integer memosId;

/**
* メモ内容
*/
private String text;

/**
* メモ画像パス
*/
private String imagePath;

/**
* メモ画像サムネイルパス
*/
private String imageThumbnailPath;

/**
* メモIDを取得する。
* @return メモID
*/
public Integer getMemosId() {
return memosId;
}

/**
* メモIDを設定する。
* @param memosId メモID
*/
public void setMemosId(final Integer memosId) {
this.memosId = memosId;
}

/**
* メモ内容を取得する。
* @return メモ内容
*/
public String getText() {
return text;
}

/**
* メモ内容を設定する。
* @param text メモ内容
*/
public void setText(final String text) {
this.text = text;
}

/**
* メモ画像パスを取得する。
* @return メモ画像パス
*/
public String getImagePath() {
return imagePath;
}

/**
* メモ画像パスを設定する。
* @param imagePath メモ画像パス
*/
public void setImagePath(final String imagePath) {
this.imagePath = imagePath;
}

/**
* メモ画像サムネイルパスを取得する。
* @return メモ画像サムネイルパス
*/
public String getImageThumbnailPath() {
return imageThumbnailPath;
}

/**
* メモ画像サムネイルパスを設定する。
* @param imageThumbnailPath メモ画像サムネイルパス
*/
public void setImageThumbnailPath(final String imageThumbnailPath) {
this.imageThumbnailPath = imageThumbnailPath;
}

/**
* 各メンバ変数を文字列表現へ変換する
*
* @return 各メンバ変数の文字列表現
*/
@Override
public String toString() {
return "MemosEntity [memosId=" + memosId + ", historiesId=" + historiesId + ", text=" + text + ", imagePath="
+ imagePath + ", imageThumbnailPath=" + imageThumbnailPath + "]";
}

}


Serializableを実装しているのは、メンバ変数として保持したときに onSaveInstanceState で保存できるようにするためと、startActivityのときにIntentにパラメータとして設定できるようにするためです。


03. SQLマップを作成する

SQLマップとは、SQLを記述したXMLファイルのことです。
iBATISではこれをXMLファイルに記述していましたが、
Frontierライブラリにおいても、XMLへ記述します。

具体的な手順としては以下のとおりです。

1. プロジェクトに res/xml ディレクトリを作成する
2. 作成したディレクトリ内に空のXMLファイルを作成する (ここでは sql_memos.xml という名前で作成したこととします)

そして、以下の内容を記述します。


<?xml version="1.0" encoding="UTF-8"?>

<!-- メモ内容テーブル -->
<mapper namespace="Memos">

<!-- エンティティ取得 -->
<select id="getEntity">
SELECT
pk_Memos AS memosId,
Text AS text,
ImagePath AS imagePath,
ImageThumbnailPath AS imageThumbnailPath
FROM
Memos AS M
<dynamic prepend="WHERE">
<isNotNull prepend="AND" property="memosId">
M.memosId = #memosId#
</isNotNull>
</dynamic>
<isNotNull property="limit">
LIMIT #limit#
</isNotNull>
<isNotNull property="offset">
OFFSET #offset#
</isNotNull>
</select>


<!-- エンティティ数の取得 -->
<select id="getCount">
SELECT
IFNULL(COUNT(M.pk_Memos), 0)
FROM
Memos AS M
</select>


<!-- エンティティ登録 -->
<insert id="insert">

<!-- プライマリキー代入先を指定 -->
<selectKey keyProperty="memosId" />

<!-- 追加SQL指定 -->
INSERT INTO
Memos (
Text,
ImagePath,
ImageThumbnailPath
) VALUES (
#text#,
#imagePath#,
#imageThumbnailPath#
)

</insert>

<!-- エンティティ更新 -->
<update id="update">

UPDATE
Memos
SET
Text = #text#,
ImagePath = #imagePath#,
ImageThumbnailPath = #imageThumbnailPath#
WHERE
pk_Memos = #memosId#

</update>

<!-- エンティティ削除 -->
<delete id="delete">

DELETE FROM
Memos
WHERE
pk_Memos = #memosId#

</delete>

</mapper>


いきなり動的SQL記述が出てきてしまいましたので、
iBATISを使ったことがない方は戸惑われたかも知れません。
ひとまずは、iBATISと同じということでご了承ください。


04. DAOクラスを作成

ここで、データベースへアクセスするためのDAOクラスを作成します。
これも以下を参考にしてください。


/**
* メモDAOクラス。
*
* @author Kou
*
*/
@FRMappingXml(R.xml.sql_memos)
public class MemosDao extends FRDatabaseDao {


/**
* 全エンティティを取得する。
*
* @return 全エンティティ
*/
public List<MemosEntity> getAll() {

return getSqlMapper().selectForList(
"getEntity",
MemosEntity.class
);

}


/**
* 指定されたメモIDのエンティティを取得する。
*
* @param memosId 取得するエンティティのメモID
* @return 指定されたメモIDのエンティティ
*/
public MemosEntity getEntity(
final Integer memosId
) {

return getSqlMapper().selectForObject(
"getEntity",
MemosEntity.class,
new FRNameValuePair("memosId", memosId)
);

}


/**
* 指定されたエンティティを登録する。
*
* @param entity 登録するエンティティ
*/
public void insert(
final MemosEntity entity
) {

getSqlMapper().insert("insert", entity);

}


/**
* 指定されたエンティティを更新する。
*
* @param entity 更新するエンティティ
*/
public void update(
final MemosEntity entity
) {

getSqlMapper().update("update", entity);

}


/**
* 指定されたエンティティを削除する。
*
* @param entity 削除するエンティティ
*/
public void delete(
final MemosEntity entity
) {

getSqlMapper().delete("delete", entity);

}

}


DAOクラスは FRDatabaseDao クラスを継承し、FRMappingXmlアノテーションで、さきほど作成したSQLマップファイルのリソースIDを指定します。こうすることで、このDAOクラスは指定したSQLマップファイルと関連付けることが出来ます。

あとはもうiBATISと同じです。
SQLマップに書かれた ID を指定することで、そのSQLを呼び出したり、呼び出し時にパラメータを渡したりできます。

パラメータの指定を行う場合は、FRNameValuePairクラスを利用し、名前付きの値を渡します。ここで指定した名前が、実際にSQLマップへ渡される変数の名前となるわけです。

iBATISと少し違うのは、戻り値の型の指定はSQLマップでは行わず、DAOのコード上で行うということです。ここで指定した型クラスの各メンバ変数名に対応する情報がテーブルにあるかどうかを判定し、自動的に値を設定して返します。ここで、戻り値の型にIntegerなどのプリミティブラッパーが指定された場合は、変換可能な値であれば自動的に変換されて返されます。


05. DAOのインスタンスを取得してデータベースへアクセス

ここまででようやく準備完了です。
Memosテーブルへアクセスするためのコードを記述してみましょう。


//
// (context変数は Context 型の変数。Activity内であれば、thisを渡してもよい)
//
final String dbFileName = "test.db"; // 予め作成して、assetsフォルダへコピーしてあるDBファイル名

// DBファイルをアセッツからコピーする
// (アプリ起動時に一度だけ行えば良い)
FRDatabaseManager.copyDatabaseFromAssets(
context.getApplicationContext(),
dbFileName,
false
);

// デフォルトDBファイル名を設定する
// (アプリ起動時に一度だけ行えば良い)
FRDatabaseManager.setDefaultDatabaseName(context, dbFileName);


// メモDAOを取得する
// (DAOはローカル変数として作成すること。Activityのメンバ変数としては使ってはいけない)
final MemosDao dao = FRDatabaseManager.getInstance(context).getDao(
context,
MemosDao.class
);

// メモ一覧を返す
final List<MemosEntity> entities = dao.getAll();

// 内容を表示する
Log.d("Database Test", String.valueOf(entities));



DAOのインスタンスは上記のように、
FRDatabaseManagerクラスを利用して取得します。

まず最初に、FRDatabaseManagerクラスのcopyDatabaseFromAssetsメソッドを使用し、assetsフォルダから予め作成しておいたDBファイルをコピーします。次に、FRDatabaseManagerクラスのsetDefaultDatabaseNameメソッドでアクセスするDBファイル名を設定します。

この2つの設定をアプリ起動時に一回だけ行っておくことで、以降指定したDBファイルへのアクセスがデフォルトで行えるようになります。

その後、FRDatabaseManagerクラスのgetInstanceメソッドで、FRDatabaseManagerのインスタンスを取得し、そのインスタンスからgetDaoメソッドをコールすることで、DAOのインスタンスを取得します。もし自前で new をしてDAOを作成して利用したとしても、内部的にSQLマッピングが行われないDAOが作成されて例外が発生するようになっていますので、上記のように FRDatabaseManager を使ってインスタンスを生成してください。




以上で、簡単ですがFrontierライブラリによるデータベースアクセスを紹介しました。
このように、ライブラリを作成することでSQLiteは格段に扱いやすくなり、導入へのしきいも下がるかと思われます。

FrontierライブラリはApache License 2.0で公開しておりますので
ライセンスの範囲内で自由にご利用頂いても結構です。

機能面やマニュアルはこれから充実化していこうと思います。
スポンサーサイト

コメントの投稿

非公開コメント

in句

android ibatis で検索したらヒットしました。
少し使ってみています。
SELECTでin区を使いたい場合、
どのようにすれば実現できますか?

Re: in句

はじめまして。コメントありがとうございます。

IN句についてですが、
動的SQLとして条件によって生成しないのであれば利用することが可能です。
動的SQLとして条件によってIN句を付けたい場合、
本家iBATISでいう iterate タグを現バージョンではサポートしていないため、
EXISTS句を利用した方法への置き換えが必要になります。

iterateタグなど、現状サポートしていないタグについては
近いうちに対応する予定ですので、少々お待ち頂けると幸いです。

DBのアップデート

初めまして。kuriと申します。

androidでもO/Rマッピングのライブラリがないかと思って検索したところ見つけました。

1点疑問があります。
それは、仕様変更でリリース後にテーブルを追加する場合にどのように行うのかということです。

androidフレームワークのようにDBのバージョンを持っているわけではないようなので、どのようにすれば実現できますのでしょうか?

Re: DBのアップデート

はじめまして。コメントありがとうございます。

本ライブラリですと、予めassetsにSQLite形式のDBファイルを配置して利用します。
そのassetsのDBファイルのバージョンを、アプリ起動時などにFRDatabaseManagerのneedsUpdateメソッド
を使用することで、バージョンアップの必要があるかどうかをチェックします。

バージョンアップの必要があるとわかれば、
カラム数の増減程度でれば、FRDatabaseManager#update メソッドを使用することで
データの移行までをすべて自動的に行います。
(時間がかかる処理なので、AsyncTaskなどUIスレッド以外で実行してください)

このメソッドは、Android端末内にあるバージョンアップ前のアプリのDBファイルのバージョンと、
バージョンアップ後のassets内のDBファイルのバージョンをチェックし、
assets内のDBファイルのバージョン数値が大きければバージョンアップ処理を行います。
SQLite形式のファイルにバージョンを指定できるソフトは
WindowsアプリのSQLite ExpertやFirefoxプラグインのSQLiteManagerで可能です。

ただし、「あるレコードは削除して、あるレコードだけは引き継ぐ」といった細かい指定は自動的には行えないため、
その場合はFRDatabaseSavableクラスを継承したクラスを作成し、
canSaveTable, canSaveRecord, canSaveColumnの各メソッドをオーバーライドして
細かい条件を指定して FRDatabaseManager#update メソッドを実行することが可能です。


ですので、リリース後であってもテーブルの追加や既存テーブルのカラムの増減くらいであれば
現バージョンでも十分に対応することができます。

ただ、Android標準のSQLiteOpenHelperクラスのように
バージョンごとに条件分岐し、スキーマ変更処理を書くような処理にも対応させたいとは考えておりますので
このあたりは近いうちにバージョンアップで対応させる予定です。
プロフィール

Kou

Author:Kou
モバイル関連の開発ばかりやってる人のブログです。たまにWebもやります。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。