FURYU Tech Blog - フリュー株式会社

フリュー株式会社の開発者が技術情報を発信するブログです。

SQLDelightの紹介

この記事は、Androidその2 Advent Calendar 2016 の12/18の記事です。

はじめに

コンテンツ・メディア第1事業部の荒木です。ピクトリンクというプリントシール画像を使ったアプリのAndroid版を開発しています。今回は、Androidアプリのデータベース管理で使えるSQLDelightというプラグインの紹介をしたいと思います。前回の記事で紹介したSQLBriteと組み合わせて使うと強力です。

SQLDelightとは?

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