この記事は、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のパワフルさを活かしてみたいという方は、一度試してみてはいかがでしょうか?