LoginSignup
23
24

More than 3 years have passed since last update.

[Android/Java]Roomでローカルデータベースを操作する

Last updated at Posted at 2019-06-30

環境

  • 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`))");
        :
      }
      :
    }
  }
}

参考

その他

Googleさんのググラビリティ低い名前の付け方、ちょっと何とかならないか…

23
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
24