SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

一歩進んだAndroidアプリ開発ができる「Android Jetpack」入門

【Android Jetpack入門 】データベース処理を自動化してくれるライブラリ「Room」を徹底解説!

一歩進んだAndroidアプリ開発ができる「Android Jetpack」入門 第2回

  • X ポスト
  • このエントリーをはてなブックマークに追加

Roomの三種の神器の作り方

 Room利用の準備が整ったところで、前節で紹介したRoomに必要な3個の部品の作り方を紹介していきます。

Java版エンティティ

 先述のように、エンティティはテーブル構造を表すJava/Kotlinクラスです。ここで、例えば、表1のようなカクテルデータを管理するcocktailmemosテーブルを想定すると、エンティティクラスのJavaコードは、リスト3のようになります。

表1:cocktailmemosテーブルの構造
カラム名 データ型 内容
id 整数値 主キー
name 文字列 カクテル名
note 文字列 メモ
リスト3:エンティティクラス(Java版)
@Entity(tableName = "cocktailmemos")  // (1)
public class Cocktailmemo {  // (2)
	@PrimaryKey  // (3)
	public long id;  // (4)
	@NonNull  // (5)
	public String name;  // (6)
	public String note;  // (7)
}

 テーブル名がcocktailmemosですので、(2)のように、これを単数系のキャメル記法に変換したものがクラス名となります。ただし、(1)のように@Entityアノテーションを記述する必要があります。これがないとエンティティと認識されません。その際、tableNameプロパティを指定すると、指定されたテーブル名で作成されます。この指定がない場合は、クラス名がテーブル名になります。

 このエンティティクラス内に、カラムに対応するpublicフィールドを定義します。その際、フィールドのデータ型とフィールド名が、そのままテーブルのカラムに適用されます。例えば、(4)はフィールド名がidでデータ型がlongなので、整数値型のカラムidとなります。(6)と(7)も同様に、文字列型のnameとnoteが生成され、結果的に表1のテーブルが作成されることになります。

 さらに、主キーカラムに対しては、(3)のように@PrimaryKeyアノテーションを記述します。もし、オートインクリメントの主キーにしたい場合は、以下のようにします。

@PrimaryKey(autoGenerate = true)

 また、NOT NULLカラムに対しては、(5)のように@NonNullアノテーションを記述します。デフォルト値を設定したい場合は、以下のようにします。

@ColumnInfo(defaultValue = "…")

 この@ColumnInfoでは、さらに、以下のようにnameプロパティを利用することで、フィールド名とは別のカラム名を指定することもできます。

@ColumnInfo(name = "…")

Kotlin版エンティティ

 このエンティティをKotlinで定義する場合は、リスト4のようなコードとなります。

リスト4:エンティティクラス(Kotlin版)
@Entity(tableName = "cocktailmemos")  // (1)
class Cocktailmemo (  // (2)
	@PrimaryKey val id: Int,  // (3)
	val name: String,  // (4)
	val note: String?  // (5)
)

 リスト4の(1)のように、クラスアノテーションとして@Entityを記述するところは、Javaコードと同じです。違うのは、フィールドとしてカラムを定義する代わりに、(2)のようにプライマリーコンストラクタの引数として、カラムに該当するプロパティを定義する点です。(3)が主キーidを定義しているコードであり、valの前にアノテーションを記述しています。また、Kotlinは変数宣言そのものにnull許容かどうかを定義できるので、(4)のように通常の引数宣言とすると、@NonNullアノテーションの代わりになります。逆に、(5)のように「?」を記述することで、NULL許容のカラムとなります。

Java版DAOインターフェース

 エンティティの作成が終了したところで、三種の神器の2個目であるDAOの作成を行なっていきます。このDAOは、先述の通り、インターフェースとして定義します。例えば、前項で定義したcocktailmemosテーブルのデータ操作として、表2の処理を考えたとします。

表2:cocktailmemosテーブルのデータ操作
操作内容 メソッド名 戻り値の内容
主キーでの検索 findByPK Cocktailmemoエンティティ
登録 insert 登録された主キーを表すlong値
削除 delete 削除件数を表すint値

 これらの操作内容を定義したDAOインターフェースは、Javaコードではリスト5のようになります。

リスト5:DAOインターフェース(Java版)
@Dao  // (1)
public interface CocktailmemoDAO {  // (2)
	@Query("SELECT * FROM cocktailmemos WHERE id = :id")  // (3)
	public ListenableFuture<Cocktailmemo> findByPK(int id);  // (4)
	@Insert  // (5)
	public ListenableFuture<Long> insert(Cocktailmemo cocktailmemo);  // (6)
	@Delete  //  (7)
	public ListenableFuture<Integer> delete(Cocktailmemo cocktailmemo);  // (8)
}

 まず、インターフェース名は、リスト5の(2)のように、「エンティティクラス名+DAO」とするとわかりやすいです。そして、そのインターフェース宣言に対して、(1)の@Daoアノテーションを記述する必要があります。

 このインターフェース内に、各データ処理をメソッドとして定義します。(4)のfindByPK()、(6)のinsert()、(8)のdelete()として定義されたメソッドは、まさに表2の通りです。ただし、そのメソッドに対して、そのメソッドにおいて実行するSQL文を(3)のように@Queryアノテーションの()内に記述します。その際、バインド変数を利用する場合は、メソッドの引数を定義し、その引数名に:(コロン)を接頭辞としたものをプレースホルダとします。例えば、(4)のfindByPK()にはid引数が定義されているので、これにコロンをつけた:idをプレースホルダとしています。そして、引数の値が、このプレースホルダに埋め込まれた(バインドされた)上でSQLが実行されます。

 なお、この@Queryで指定するSQL文はコンパイル段階でチェックされ、構文エラーはコンパイルエラーの形で指摘してくれます。存在しないテーブルやカラムの指定も、同じくコンパイルエラーとなるので、便利です。

 この@Queryアノテーションには、SELECT文だけでなく、INSERT/UPDATE/DELETEの各SQL文を記述することができますが、単純なINSERT/UPDATE/DELETEの場合は、(5)や(7)のように、専用のアノテーションが用意されていますので、これらを利用できます。ただし、その場合は、メソッドの引数は、(6)や(8)のようにエンティティオブジェクトとなります。

 このようにしてDAOメソッドを定義する際、1点注意点があります。それは、戻り値はそのまま指定できないことです。Roomでは、これらのSQL処理を非同期で実行することとなっています。それに対応するために、SQL実行の結果を表す戻り値は、ListenableFutureオブジェクトでラップする必要が出てきます。(4)や(6)や(8)の戻り値が、表2の戻り値の記載通りではなく、全て、ListenableFuture<Cocktailmemo>のように、本来の戻り値をジェネリクスとして型指定している記述になっているのは、そのためです。リスト5には含まれていませんが、もし全件検索のように検索結果が複数行になる場合は、各要素をエンティティとするListオブジェクトとなり、戻り値の型はListenableFuture<List<Cocktailmemo>>のようになります。

 なお、このListenableFutureは、Javaの非同期処理結果を格納するインターフェースであるFutureの子インターフェースであり、GoogleがリリースしているGuavaライブラリに含まれています。そのため、RoomのDAOをコーディングするためには、あらかじめこのGuavaライブラリの利用を設定しておく必要があります、リスト1の(4)はこのための記述であり、さらに、RoomとGuavaを連携させるために、同じくリスト1の(3)の設定が必要となります。

Kotlin版DAOインターフェース

 さて、このDAOインターフェースをKotlinで定義すると、リスト6のようになります。

リスト6:DAOインターフェース(Kotlin版)
@Dao
interface CocktailmemoDAO {
	@Query("SELECT * FROM cocktailmemos WHERE id = :id")
	suspend fun findByPK(id: Int): Cocktailmemo?
	@Insert
	suspend fun insert(cocktailmemo: Cocktailmemo): Long
	@Delete
	suspend fun delete(cocktailmemo: Cocktailmemo): Int
}

 アノテーションの記述方法はJavaと全く同じです。Javaコーディングと全く違うのは、非同期処理の扱いです。Kotlinには、Javaにはないコルーチンという仕組みがあります。これを利用しますので、各メソッドにはsuspendを記述します。このsuspendのおかげで、戻り値については、ListenableFutureオブジェクトでラップする必要はなくなります。

Java版Room Database

 三種の神器の最後は、Room Databaseです。この部品をなぜRoom Databaseと呼ぶかは、ズバリ、RoomDatabaseクラスを継承した抽象クラスとして作成するからです。これは、Javaコードでは、リスト7のようになります。

リスト7:Room Database(Java版)
@Database(entities = {Cocktailmemo.class}, version = 1)  // (1)
public abstract class AppDatabase extends RoomDatabase {  // (2)
	private static AppDatabase _instance;
	public static AppDatabase getDatabase(Context context) {
		if (_instance == null) {
			_instance = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "cocktailmemo_db").build();  // (3)
		}
		return _instance;
	}
	public abstract CocktailmemoDAO createCocktailmemoDAO();  // (4)
}

 リスト7の(2)を見ると、RoomDatabaseを継承した抽象クラスになっているのがわかります。クラス名は、なんでもかまいませんが、慣習的にAppDatabaseとすることが多いです。

 エンティティやDAO同様、ここでもアノテーションを記述します。それが、(1)の@Databaseです。このアノテーションのentitiesパラメータとして、このデータベースで利用するエンティティクラスを配列として指定します。ここで指定し忘れると、テーブルが作成されませんので、注意してください。versionパラメータは、データベースのバージョン番号を表します。このバージョン番号は、SQLiteDatabaseを利用した旧来のデータベース処理において、ヘルパークラス内に記述していたデータベースのバージョン番号と同様です。

 このクラス内に記述する必要があるのは、本来は、DAOインターフェースを戻り値とする抽象メソッドだけです。それが、(4)です。メソッド名はなんでもかまいませんが、DAOを戻り値とする生成メソッドであることから、「create+DAOインターフェース名」とするとわかりやすいです。この抽象メソッドは、定義されているDAOインターフェース全てについて記述しておきます。

Kotlin版Room Database

 本来は、この抽象メソッドを定義すればよいRoom Databaseクラスのはずなのに、リスト7では、他にさまざまなコードが記述されています。この理由については、次節で紹介するとして、先に、Kotlin版のRoom Databaseのコードを紹介します。これは、リスト8の通りです。

リスト8:Room Database(Kotlin版)
@Database(entities = [Cocktailmemo::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
	companion object {
		private var _instance: AppDatabase? = null
		fun getDatabase(context: Context): AppDatabase {
			if (_instance == null) {
				_instance = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "cocktailmemo_db").build()  // (1)
			}
			return _instance!!
		}
	}
	abstract fun createCocktailmemoDAO(): CocktailmemoDAO
}

 アノテーションの記述方法、抽象メソッドの定義ともに、リスト7のJavaコードと考え方は同じです。

次のページ
Roomの利用方法

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
一歩進んだAndroidアプリ開発ができる「Android Jetpack」入門連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 齊藤 新三(サイトウ シンゾウ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook <個人紹介>WINGSプロジェクト所属のテクニカルライター。Web系製作会社のシステム部門、SI会社を経てフリーランスとして独立。屋号はSarva(サルヴァ)。HAL大阪の非常勤講師を兼務。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/17124 2023/01/12 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング