あいどんノート

日々疑問に思ったことや得られた知見を書きなぐります!

Room,多対多で詰まったので生成されたJavaコード覗いてみた

こんばんは。ふぇりです。

多対多のエンティティを生成中に

FOREIGN KEY constraint failed (code 787)

が出てしまい、そのエラーが中間テーブルを登録する際に発生しており、

いろんなサイト漁ったものの解決策がいまいちだったので、

生成されたJavaコード読むことにしました。

(読めない人間が読むので、勉強向きではないです)_(_><)⊃

 

なので、ついでに他の部分も読んでみて、Roomが実際にどんなコードを書いてくれているのかを感覚的に掴めたらいいなって思います。

Roomの生成コードは以下のディレクトリに潜ると見れると思います。

build/generated/source/kapt/debug/モジュール名/

 

いつも書くようなDataBaseをみてみるとこんな感じかなって思います。

@Database(
entities = [
UserEntity::class
],
version = 5
)
@TypeConverters(DateConverter::class)
abstract class LocalDataBase : RoomDatabase() {
abstract fun userEntityDao(): UserEntityDao
}

生成コードの冒頭をみてみます。

@Override
protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(5) {
@Override
public void createAllTables(SupportSQLiteDatabase _db) {
_db.execSQL("CREATE TABLE IF NOT EXISTS `user_item` (`user_id` TEXT NOT NULL, `user_name` TEXT NOT NULL, `user_thumbnail` TEXT NOT NULL, PRIMARY KEY(`user_id`))");

createOpenHelperなるものをOverrideしていて、_openCallbackにRoomOpenHelperに設定とバージョンを渡してインスタンス生成して代入してますね。

createAllTablesで必要なテーブルを作ってくれてるみたいですね...!

'カラム名' 型 null許容かどうか みたいな形でカラムが追加されて、プライマリキーも登録されていますね。

カラム名を指定しなかった場合どうやって登録されるかみたいですね....(今度みます)

型 null許容かだけで追加されるのでしょうか

 

結構飛ばしてDaoの部分見てみます。

@Override
public UserEntityDao userEntityDao() {
if (_userEntityDao != null) {
return _userEntityDao;
} else {
synchronized(this) {
if(_userEntityDao == null) {
_userEntityDao = new UserEntityDao_Impl(this);
}
return _userEntityDao;
}
}
}

 

_userEntityDao(生成コード内でのUserEntityDaoのインスタンス)がnull出ないならインスタンスを返して、nullならシンクロナイズド(非同期(?))でインスタンス生成して返す形になっていますね。

要求された時に生成するのはメモリなどにも優しそうですね。

 

そして問題の中間テーブルを読んでみて今回は終わりたいと思います。

public final class TodoWithLabelDao_Impl implements TodoWithLabelDao {
private final RoomDatabase __db;

private final EntityInsertionAdapter<TodoWithLabel> __insertionAdapterOfTodoWithLabel;

private final DateConverter __dateConverter = new DateConverter();

public TodoWithLabelDao_Impl(RoomDatabase __db) {
this.__db = __db;
this.__insertionAdapterOfTodoWithLabel = new EntityInsertionAdapter<TodoWithLabel>(__db) {
@Override
public String createQuery() {
return "INSERT OR REPLACE INTO `todo_with_label` (`todo_id_with`,`label_id_with`) VALUES (?,?)";
}

@Override
public void bind(SupportSQLiteStatement stmt, TodoWithLabel value) {
stmt.bindLong(1, value.getTodoId());
stmt.bindLong(2, value.getLabelId());
}
};
}

もっといい名前を考えて命名したいと思います(自戒)

TodoとLabelの中間テーブルなんですが、なかなか読めない部分が多いです...

EntityInsertionAdapter<>でTodoWithLabelが覆われていますね。

なんの役割をしているのでしょうか、みてみましょう。

(長いのでcommand+Bしてね!)

データベースにインサートする役割とかを担っている(?)

insertが配列かそうじゃないかで処理が分かれていて、一つのエンティティごとにInsertQueryとEntityを結びつけていますね。

EntityとDB側のクエリの間を取り持っている(?)

 

エラーが発生した箇所を見ます。

@Override
public void insertLabel(final TodoWithLabel todoWithLabel) {
__db.assertNotSuspendingTransaction();
__db.beginTransaction();
try {
__insertionAdapterOfTodoWithLabel.insert(todoWithLabel);//ココ
__db.setTransactionSuccessful();
} finally {
__db.endTransaction();
}
}
private final EntityInsertionAdapter<TodoWithLabel> __insertionAdapterOfTodoWithLabel;

さっきのEntityInsertionAdapterのinsertで怒られている。

TodoWithLabelはInt型のidを持つカラムが2個でそれぞれがForeignKeyでTodoとLabelと結びついてる形になっています。

今回渡した値はTodoWithLabel(todo_id = 1, label_id = 1)で、それぞれあたいは追加済みで、idだけ投げたのが行けなかったのでしょうか。(多分そう)

TodoWithLabel(Todo, Label)で渡せるようにしてみましょうかね...

 

根本的に何が問題だったのかはまだ理解できていないので、ForeignKeyをまず理解することが先決なのかなって思っています。(内部の仕組み)

今日は頭がもう働かないので寝ようと思います。

解決できなくて悔しいですが.....早起きしたいと思います(8時間睡眠)