Android ORMでオブジェクトの埋め込みはどのように実装するか

Table of Contents:


概要

大抵のORMは特定の型をシリアライズしてカラムに埋め込む機能があります。この機能を type adapterと呼ぶことにします。このtype adapterについて、既存のORMの実装とOrmaでの実装を紹介します。

たとえば、Google Play Services のLatLngなどはtype adapterを使ってカラムに埋め込むとすると、ORMに以下の情報を伝える必要があります。

  • シリアライズ元の型(LatLngなど)
  • シリアライズ先の型(Stringなど)
  • DBのストレージ型(SQLiteの TEXT など)
  • シリアライズとデシリアライズをどのように行うか

実例

いくつか実例をみてみます。評価ポイントは、上記の情報をどうやって伝えるかと、type adapterをどのように使うかです。特に、動的にtype adapterを検索して呼び出すのか、静的に呼び出せるのかは実行速度に影響が出るので重要です。

GreenDAO

  • Custom Types
  • type adapterは PropertyConverter<P, D> を実装したクラスのインスタンス
  • 型とtype adapterの関係は静的に解決される

DBFlow

  • Type Converters
  • @com.raizlabs.android.dbflow.annotation.TypeConverter で注釈して com.raizlabs.android.dbflow.converter.TypeConverter<DataClass, ModelClass> を継承するとtype adapterにできる
  • packageの異なる同名のTypeConverterを使っているので非常に記述が煩雑。どうしてこうなった…。
  • 型とtype adapterの関係は動的に解決される

ActiveAndroid

  • Type serializers
  • type adapterは TypeSerializerを継承したクラスのインスタンス
  • 型とtype adapterの関係は静的に解決される

Ormaでの実装

v1.0では、interface TypeAdapter<SourceType> を実装したクラスを OrmaDatabase.Builder に与えるという方式でした(dynamic type adapter)。これは強制的にStringにシリアライズしていたので、バイナリを埋め込むことができませんでした。

そこでこれを改善すべくv1.2で実装したtype adaptersは、POJOに @StaticTypeAdapter で注釈して静的メソッド serialize()deserialize() を実装する方式にしました。

このtype adapterは、annotation processingで型とtype adapterの関係を静的に解決し、生成するコードにserialize()deserialize()の呼び出しが埋め込まれるというものです。SQLiteの適切な型とメソッド(たとえばBLOBCursor#getBlob())を扱えるようになったので、バイナリを埋め込めるようになりました。

Ormaの実装はさらにオブジェクトインスタンスを介さず静的メソッドを呼び出すので、実行速度の点で有利です。

実装例:

@StaticTypeAdapter(
        targetType = LatLng.class,
        serializedType = String.class
)
public class LatLngAdapter {

    // SerializedType serialize(TargetType source)
    @NonNull
    public static String serialize(@NonNull LatLng source) {
        return source.latitude + "," + source.longitude
    }

    // TargetType deserialize(SerializedType serialized)
    @NonNull
    public static Location deserialize(@NonNull String serialized) {
        String[] values = serialized.split(",");
        return new LatLng(
            Double.parseDouble(values[0]),
            Double.parseDouble(values[1]));
    }
}

v1.0のときの動的なtype adapterもまだ残していますが、2.0で削除予定です。そうなると、コンパイルタイムに適切なtype adapterがなければコンパイルエラーを出すようにできるはずです。