この記事は、Androidその2 Advent Calendar 2016 の12/18の記事です。
はじめに
コンテンツ・メディア第1事業部の荒木です。ピクトリンクというプリントシール画像を使ったアプリのAndroid版を開発しています。今回は、Androidアプリのデータベース管理で使えるSQLDelightというプラグインの紹介をしたいと思います。前回の記事で紹介したSQLBriteと組み合わせて使うと強力です。
SQLDelightとは?
SQLDelightは、一言でいうとSQL文からJavaのファイルを自動生成するプラグインです。たとえば、以下のようなSQL文が書かれたUser.sqというファイルを作成してビルドをかけます。
CREATE TABLE user ( _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL );
すると、SQLDelightはUser.sqを解析して、UserModel.javaというファイルを生成してくれます。
public interface UserModel { String TABLE_NAME = "user"; String _ID = "_id"; String NAME = "name"; String CREATE_TABLE = "" + "CREATE TABLE user (\n" + " _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n" + " name TEXT NOT NULL\n" + ")"; long _id(); @NonNull String name(); interface Creator<T extends UserModel> { T create(long _id, @NonNull String name); } final class Mapper<T extends UserModel> implements RowMapper<T> { private final Factory<T> userModelFactory; public Mapper(Factory<T> userModelFactory) { this.userModelFactory = userModelFactory; } @Override public T map(@NonNull Cursor cursor) { return userModelFactory.creator.create( cursor.getLong(0), cursor.getString(1) ); } } final class Marshal { protected final ContentValues contentValues = new ContentValues(); Marshal(@Nullable UserModel copy) { if (copy != null) { this._id(copy._id()); this.name(copy.name()); } } public ContentValues asContentValues() { return contentValues; } public Marshal _id(long _id) { contentValues.put(_ID, _id); return this; } public Marshal name(String name) { contentValues.put(NAME, name); return this; } } final class Factory<T extends UserModel> { public final Creator<T> creator; public Factory(Creator<T> creator) { this.creator = creator; } public Marshal marshal() { return new Marshal(null); } public Marshal marshal(UserModel copy) { return new Marshal(copy); } } }
たった4行のファイルから、たくさんのコードが自動生成されましたね。これだけだと何に使うかわからないと思うので、次に具体的な使い方について説明します。
使い方
まず、UserModelを実装したクラスUserを作成します。AutoValueを利用すると以下のように短いコードで書けます。
@AutoValue public abstract class User implements UserModel { public static final Factory<User> FACTORY = new Factory<>(AutoValue_User::new); public static final RowMapper<User> MAPPER = new Mapper<>(FACTORY); }
各種定数
UserModelにはテーブル名やカラム名、テーブル作成用のSQL文が定数として定義されています。たとえば、SQLiteを使う時に作るであろうSQLiteOpenHelperの継承クラスで、テーブル定義をする際にテーブル作成用のSQL文を利用できます。
@Override public void onCreate(SQLiteDatabase sqLiteDatabase) { sqLiteDatabase.execSQL(User.CREATE_TABLE); }
Mapper
UserModelで定義されているMapperを使えば、データベースの取得結果であるCursorをUserクラスに変換することができます。
Cursor cursor = sqLiteDatabase.rawQuery("SELECT * FROM " + User.TABLE_NAME, new String[]{}); User user = User.MAPPER.map(cursor);
Marshal
UserModelにはMarshalと呼ばれるContentValuesのビルダーも定義されています。以下のようにINSERTするときや、UPDATEするときに利用できます。
sqLiteDatabase.insert(User.TABLE_NAME, null, User.FACTORY.marshal() ._id(1) .name("araki") .asContentValues());
Pros / Cons
SQLDelightのメリット(Pros)とデメリット(Cons)に関する私の意見です。
Pros
テーブルの定義をSQLで書くことができる点がSQLDelightのメリットだと思います。ORMライブラリは手軽で便利ですが、ORMの仕様に依存してしまうというデメリットがあります。つまりORM側の問題に依存してしまうというリスクがあります。これに対し、SQL自体を扱うSQLDelightは、SQLで出来ることなら何でもできるという点が強みだと思います。また、シンプルにSQLでテーブルを操作できるので、複雑な問題が発生しにくいのではないかと考えています。
Cons
デメリットとしては、SQLの学習コストがかかることがまず挙げられます。SQL自体はできることが多いですが、使いこなすにはそれ相応の知識が必要だと思います(私にはありません)。また、クラスのGetter, Setterのメソッド名がAutoValueの形式(User#name(), User#_id(1)
など)となっていたので、個人的には見慣れないなと感じています。
まとめ
この記事では、SQLからテーブルを表すクラスを生成してくれるSQLDelightというプラグインを紹介しました。RealmなどのNoSQLのライブラリも盛り上がっていますが、SQLのパワフルさを活かしてみたいという方は、一度試してみてはいかがでしょうか?