Androidアプリ開発

Room(SQLite) データベースの実装

この記事は約20分で読めます。
記事内に広告が含まれています。
スポンサーリンク

この記事は Android スマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Java での開発経験、XML 構文規則、Android のアプリ開発経験がある方を対象としています。
Android のアプリ開発でお役にたててれば、嬉しいです。
(これから Android のアプリ開発や Java での開発を始めたい方への案内は、
記事の最後で紹介します)

この記事のテーマ


 Room(SQLite)を使用して、ローカルデータベースを実装する

Roomは、オープンソースデータベースのSQLiteを抽象化レイヤとして提供するライブラリです。
Andoridアプリで
SQLiteを使用する場合のベストプラクティスといっても過言ではありません。

Roomを使用することで、データベースの操作や定義、SQLの実行など、SQLiteを最大限に活用することが可能です。
Roomの導入から基本的な使用方法にフォーカスを当てて、開発したアプリのソースを参考に説明したいと思います。

使用するための準備

Roomを使用するには、モジュールのbuild.gradleファイルに定義の追加が必要です。

◎build.gradle(モジュール)
2023年3月現在の最新バージョンは2.6.1です。

dependencies {
    :
    implementation 'androidx.room:room-runtime:2.6.1'
    implementation 'androidx.room:room-rxjava2:2.6.1'
    implementation 'androidx.room:room-guava:2.6.1'
    testImplementation 'androidx.room:room-testing:2.6.1'
    annotationProcessor 'androidx.room:room-compiler:2.6.1'
}

基本構成

Roomの基本構成は、データベースを操作するデータベースクラス、テーブルを定義するエンティティ、テーブルを操作するデータアクセスオブジェクトです。

動画再生アプリ(Duel)フォルダ構成

データベース

データベースを操作するデータベースクラスには、データベース構成を定義するRoomDatabaseを拡張する抽象クラス、データベースのインスタンスを作成するクラス、データベースのテーブルを操作するクラスがあります。

Database

データベース構成を定義するRoomDatabaseを拡張する抽象クラスです。
@Databaseアノテーションを付けて、データベースに関連付けられたエンティティをentities配列に含めます。
データベースに関連付けられた
Daoクラスのインスタンスを返す抽象メソッドを定義します。

@Database(entities = {Movie.class,
                      Chapter.class,
                      History.class }, version = 1, exportSchema = false)
public abstract class DuelDatabase extends RoomDatabase {
    public abstract MovieDao movieDao();
    public abstract ChapterDao chapterDao();
    public abstract HistoryDao historyDao();
}

versionはデータベースのバージョンを定義し、エンティティの追加や変更があった場合にカウントアップします。
スキーマ(エンティティ構造)を出力しない場合は、
exportSchema = falseを指定します。

<strong>重要ポイント</strong>
重要ポイント

exportSchema = false指定した場合、

アプリリリース後のエンティティ変更やautoMigrationが使用できません。

◎スキーマを出力する場合

スキーマを出力する場合、exportSchemaの出力先フォルダ(プロジェクトフォルダ配下にschemasというフォルダ)を作成し、モジュールのbuild.gradleファイルに出力先フォルダの定義を追加します。

android {
    :
    defaultConfig {
        :
        javaCompileOptions {
            annotationProcessorOptions {
                arguments += ["room.schemaLocation": "$projectDir/schemas".toString()]
            }
        }
    }

exportSchema = falseを削除します。

@Database(entities = {Movie.class,
                      Chapter.class,
                      History.class }, version = 1)
public abstract class DuelDatabase extends RoomDatabase {
    public abstract MovieDao movieDao();
    public abstract ChapterDao chapterDao();
    public abstract HistoryDao historyDao();
}
DatabaseSingleton

データベースのインスタンスを作成するクラスです。
Databaseオブジェクトのインスタンス化は、シングルトン(デザインパターン)で実装します。

public class DuelDatabaseSingleton {
    private static DuelDatabase instance = null;
    public static DuelDatabase getInstance(Context context) {
        if (instance != null) { return instance; }
        instance = Room.databaseBuilder(context, DuelDatabase.class, context.getString(R.string.db_name)).build();
        return instance;
    }
}
DatabaseHelper

データベースのテーブルを操作するクラスです。
各テーブルのデータ検索、更新や削除などの処理を実装します。

public class DuelDatabaseHelper {
    private final Handler           handler = new Handler(Looper.getMainLooper());
    private final MovieDao          movieDao;
    private final ChapterDao        chapterDao;
    private final HistoryDao        historyDao;
    private List<Movie>             movieList = new ArrayList<>();
    :
    // コンストラクタ //
    public DuelDatabaseHelper() {
        DuelDatabase duelDatabase = DuelDatabaseSingleton.getInstance(context);
        movieDao = duelDatabase.movieDao();
        chapterDao= duelDatabase.chapterDao();
        historyDao = duelDatabase.historyDao();
    }
    :
  // データ検索 // 
    public void selectMovie(MainActivity mainActivity) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(() -> {
            try {
                movieList = movieDao.selectAll();
            } catch (Exception e) {
                e.printStackTrace();
            }
            handler.post(() -> {
                // SELECT結果の返却
                mainActivity.updateMovieView(movieList);
                :
            });
        });
    }
    :
    // データ更新 //
    public void updateMovie(Movie movie) {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(() -> {
            try {
                if (movie.movie == null) {
                    movieSequence = movieDao.insert(movie);
                    movie.movie = (int)movieSequence;
                } else {
                    movieDao.update(movie);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            handler.post(() -> {
                // 更新後の処理
                :
            });
        });
    }
    :
    // データ削除 //
    public void truncateMovie() {
        if (DEBUG) Log.d(TAG, "truncateMovie");
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(() -> {
            try {
                movieDao.deleteAll();
                movieDao.resetSequence();
            } catch (Exception e) {
                e.printStackTrace();
            }
            handler.post(() -> {
                // 削除後の処理
                :
            });
        });
    }
    :

コンストラクタでデータベースのインスタンス化と、データアクセスオブジェクト(Dao)をインスタンス化します。
テーブルの操作は、
ExecutorServicenewSingleThreadExecutorで使用して、シングルスレッドで実行します。
テーブルを操作するSQLは、後述の
Daoに定義します。

データベースのアクセス処理は、UIスレッドと別のスレッドで実行する必要があります。
このため、呼び元に検索結果を受け取るメソッドを用意して、結果を引数にして呼び元のメソッドを呼び出します。

エンティティ

エンティティには@Entityを付けて、テーブル名、インデックスや制約には@Indexを付けて定義します。
項目毎にプライマリキーには
@PrimaryKey、項目名には @ColumnInfoを付けます。

@Entity(tableName = "Movie", indices = {@Index(value = {"source"}, unique = true)})
public class Movie {
    @PrimaryKey(autoGenerate = true)
    public Integer movie;// シーケンス
    @ColumnInfo(name = "source")
    public String source;// 動画ファイル(xxx.mp4)
    @ColumnInfo(name = "title")
    public String title; // タイトル(表示用)
    @ColumnInfo(name = "point1")
    public Long point1;  // 再生位置1
    @ColumnInfo(name = "point2")
    public Long point2;  // 再生位置2
    @ColumnInfo(name = "point3")
    public Long point3;  // 再生位置3
    @ColumnInfo(name = "point4")
    public Long point4;  // 再生位置4
    @ColumnInfo(name = "point5")
    public Long point5;  // 再生位置5
    @ColumnInfo(name = "archive")
    public String archive;  // ARCファイル(拡張)
    public Movie(Integer movie,
                 String source,
                 String title,
                 Long point1,
                 Long point2,
                 Long point3,
                 Long point4,
                 Long point5,
                 String archive) {
        this.movie = movie;
        this.source = source;
        this.title = title;
        this.point1 = point1 != null ? point1 : 0;
        this.point2 = point2 != null ? point2 : 0;
        this.point3 = point3 != null ? point3 : 0;
        this.point4 = point4 != null ? point4 : 0;
        this.point5 = point5 != null ? point5 : 0;
        this.archive = archive;
    }
 }

@Entityアノテーションの有無でエンティティかテーブルを区別しています。
サンプルでは
publicの項目にすることで、getterやsetterの実装を省略しています。

データアクセスオブジェクト(Dao)

データアクセスオブジェクト(Dao)では、SQLを記述する場合は@Queryを付けてます。
追加する場合は
@Insert、更新する場合は@Update、削除する場合は@Deleteを付けて定義します。
@Query以外の引数はエンティティです。

@Dao
public interface MovieDao {
    // 参照
    @Query("SELECT * FROM Movie WHERE movie = :movie")
    Movie select(Integer movie);
    @Query("SELECT * FROM Movie WHERE source = :source ORDER BY movie DESC LIMIT 1")
    Movie select(String source);
    @Query("SELECT * FROM Movie ORDER BY movie")
    List<Movie> selectAll();
    // 追加
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    long insert(Movie movie);
    // 更新
    @Update(onConflict = OnConflictStrategy.REPLACE)
    void update(Movie movie);
    // 削除
    @Delete
    void delete(Movie movie);
    @Query("DELETE FROM Movie")
    void deleteAll();
    @Query("DELETE FROM sqlite_sequence WHERE name='Movie'")
    void resetSequence();
}

追加では、プライマリキーの採番結果を取得しています。
プライマリキーの採番を初期化するために、
sqlite_sequenceから該当テーブルのレコードを削除しています。

Room(SQLite)のINSERTで自動採番されたシーケンスを取得するはこちらです↓↓↓

データベースにアクセスする

アプリ(Activity)からデータベースにアクセスする場合、DatabaseHelperをインスタンス化します。
インスタンス化した
DatabaseHelperDaoを経由して、テーブルにアクセスします。

private DuelDatabaseHelper   duelDatabaseHelper = null;
private ArrayList<Movie>     movieList = new ArrayList<>();
:
public class MainActivity extends AppCompatActivity {
    :
    
    // データベース初期化
    duelDatabaseHelper = new DuelDatabaseHelper();
    :

    // 検索
    duelDatabaseHelper.selectMovie(this);

    // 更新
    duelDatabaseHelper.updateMovie(movie);
    :

    // 削除
    duelDatabaseHelper.truncateMovie();
    :

    // SELECT結果の受け取り 
    public void updateMovieView(List<Movie> newMovieList) {
        movieList = (ArrayList<Movie>) newMovieList;
        :
    }
  :

データベースのアクセス処理は、UIスレッドと別のスレッドで実行する必要があります。
このため、検索結果を受け取るメソッドを用意して、結果結果を受け取ります。

SQLiteのデータベース管理ツール

SQLiteのデータベース管理ツールとして、DB Browser for SQLiteがあると便利です。
データベースの内容をテーブルごとにリスト表示、指定した条件に一致するレコードを抽出して表示、レコードの追加や削除、SQLの実行などできます。

Room(SQLite) データベースを実装している Androidアプリです。

今回は、ここまでです。

誤字脱字、意味不明でわかりづらい、
もっと詳しく知りたいなどのご意見は、
このページの最後にある
コメントか、
こちら
から、お願いいたします♪

ポチッとして頂けると、
次のコンテンツを作成する励みになります♪

ブログランキング・にほんブログ村へ

これからAndroidのアプリ開発やJavaでの開発を始めたい方へ

初めての Android のアプリ開発では、アプリケーション開発経験がない方や、
アプリケーション開発経験がある方でも、Java や C# などのオブジェクト指向言語が初めての方は、
書籍などによる独学ではアプリ開発できるようになるには、かなりの時間がかかります。
オンラインスクールでの習得をおススメします。

未経験者からシステムエンジニアを目指すのに最適かと、まずは無料相談から♪

未経験者からプログラマーを目指すのに最適かと、まずは無料カウンセリングから♪