環境
- androidx.room 2.1.0
基本
-
@Entity
でエンティティのクラスを作る。 -
@Dao
でエンティティを操作するクラスを作る。 -
RoomDatabase
を継承したクラスを作って、そのメンバーとして@Dao
を指定したクラスを登録する -
RoomDatabase
を継承したクラスをアプリから操作する
RoomDatabase
を継承したクラスは、メインスレッドでは操作できない点に注意。
@Entity
クラスの例
@Entity(indices = {@Index(value = {"name"}, unique = true)})
public class User {
@PrimaryKey
public long id;
@ColumnInfo
public String name;
}
@Dao
クラスの例
@Dao
public abstract class UserDao {
@Query("select id from user order by id desc limit 1")
public abstract long getMaxId();
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract void insert(User user);
@Query("delete from user where id = :id")
abstract void delete(long id);
}
RoomDatabase
継承クラスの例
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
DB インスタンスを生成する Singleton クラスの例
public class AppDatabaseSingleton {
private static AppDatabase instance = null;
private AppDatabaseSingleton() {}
public static AppDatabase getInstance(Context context) {
if (instance != null) {
return instance;
}
instance = Room.databaseBuilder(context,
AppDatabase.class, "app_database").build();
return instance;
}
}
データベースの操作例
new Thread(new Runnable() {
@Override
public void run() {
AppDatabase db = AppDatabaseSingleton.getInstance(context);
long maxId = db.UserDao().getMaxId();
callback.onComplete(maxId);
}
}).start();
テーブル名を指定する
@Entity
に (tableName = {"テーブル名")
という感じで書く。指定しない場合はモデル名と同じ名前でテーブルが作られてしまうので、できれば指定したほうが良いと思う。
@Entity(tableName = "users")
public class User {
@PrimaryKey
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
}
モデルに持ちたいがDBに保存したくない列を作る
列名の前に @Ignore
を書く。
@Entity
public class User {
@PrimaryKey
public int id;
public String firstName;
public String lastName;
@Ignore
Bitmap picture;
}
複数カラムにユニークインデックスする
@Entity
に (indeces = {@Index(value = {"1列目", "2列目"}, unique = true)})
という感じで書く。
@Entity(indices = {@Index(value = {"first_name", "last_name"},
unique = true)})
public class User {
@PrimaryKey
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
}
複数まとめて設定したい
カンマ区切りで書く
@Entity(tableName = "users",
indices = {@Index(value = {"first_name", "last_name"},
unique = true)})
INSERT IGNORE 的な事をしたい
まず基本的な事として、クエリの発行は @Entity
ではなく @Dao
で行う。
@Insert
に (onConflict = OnConflictStrategy.IGNORE)
を追記する。
入れ替えたい時は (onConflict = OnConflictStrategy.REPLACE)
。
@Dao
public interface MyDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
public void insertUsers(User... users);
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertBothUsers(User user1, User user2);
@Insert
public void insertUsersAndFriends(User user, List<User> friends);
}
トランザクションしたい
トランザクションする場合、 interface
から abstract class
に変更する必要がある。
その上で、 @Transaction
を対象メソッドに指定する。
@Dao
abstract class UsersDao {
@Transaction
public void setLoggedInUser(User loggedInUser) {
deleteUser(loggedInUser);
insertUser(loggedInUser);
}
@Query("DELETE FROM users")
abstract void deleteUser(User user);
@Insert
abstract void insertUser(User user);
}
後からテーブルや列の追加の追加を行いたい
SQLite の時と同様にバージョンを上げる。上げただけだと何もやってくれないので、何をするかは書く必要がある。
ていうか書かないと怒られるのである。
A migration from 1 to 2 was required but not found. Please provide the necessary Migration path via RoomDatabase.Builder.addMigration(Migration ...) or allow for destructive migrations via one of the RoomDatabase.Builder.fallbackToDestructiveMigration* methods.
何をすれば良いかは親切にエラーメッセージに書いてあるので、端的に言えばそのとおりにするだけだ。
例えばバージョン3まで上げて、バージョン2で Fruit
テーブルを追加し、バージョン3で Book
テーブルに pub_year
という Integer
のカラムを追加した場合、以下のようになる。
@Database(entities = {Fruit.class, Book.class}, version = 3)
public abstract class AppDatabase extends RoomDatabase {
:
public static AppDatabase getInstance(Context context) {
if (instance != null) {
return instance;
}
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
+ "`name` TEXT, PRIMARY KEY(`id`))");
}
};
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE Book "
+ " ADD COLUMN pub_year INTEGER");
}
};
instance = Room.databaseBuilder(context,
AppDatabase.class, "database-name")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build();
return instance;
}
}
この辺もうまくラップして欲しい気がするが、現状はわりと人力感が漂っている。
テーブルの作成SQLなどは、Roomの展開されたほうを参考にそのまま流用すると比較的楽かも知れない。
例えば、 com.example.db.AppDatabase
というクラス名で RoomDatabase
の継承クラスを作っていた場合は、 app/build/generated/source/apt/debug/com/example/db/AppDatabase_Impl.java
に展開された内容が出力されている。
以下は AppDatabase_Impl
の一例。
@SuppressWarnings({"unchecked", "deprecation"})
public final class AppDatabase_Impl extends AppDatabase {
@Override
protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(2) {
@Override
public void createAllTables(SupportSQLiteDatabase _db) {
_db.execSQL("CREATE TABLE IF NOT EXISTS `Fruit` (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`))");
:
}
:
}
}
}
参考
- Defining data using Room entities | Android Developers
- Accessing data using Room DAOs | Android Developers
- Insert | Android Developers
- Migrating Room databases | Android Developers
その他
Googleさんのググラビリティ低い名前の付け方、ちょっと何とかならないか…