Почему в двух потоках создается по два одинаковых элемента, хотя у базы настроен синглтон с synchronized? (FOREIGN KEY constraint failed (code 787))

Запускается два потока, которые в цикле создают объект Picture. Появилась проблема с тем, что создается 2 одинаковых элемента в таблице. Я узнал о synchronized и добавил его в код. Проблема, на удивление, не исчезла.

@Database(entities = {Picture.class, ViewPicture.class}, version = 27, exportSchema = false)
public abstract class InternalDB extends RoomDatabase {

    public abstract PictureDao pictureDao();
    public abstract ViewPictureDao viewPictureDao();

    private static volatile InternalDB INSTANCE;

    static InternalDB getDatabase(final Context context) {
        if (INSTANCE == null) {
            synchronized (InternalDB.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context, InternalDB.class, "database-name").fallbackToDestructiveMigration().build();
                }
            }
        }
        return INSTANCE;
    }
}

В приложении я "слушаю" через LiveData базу для определенного вью через запрос к таблице M2M view_picture. Когда я запускаю приложение, я дважды (для каждой вьюхи - "новинки" и "популярное") запускаю функцию LoadPictures на добавление объектов из нетворка (Firebase) в таблицу picture и от туда в view_picture. Элементы на "вьюхах" могут повторятся. В данном случае повторяется первый элемент и он создается дважды и возникает ошибка FOREIGN KEY constraint failed (code 787) при добавлении в таблицу view_picture.

private long PictureInsertOrUpdate(Picture picture) {
    long id = this.pictureDao.getIdByPublicId(picture.public_id);
    if (id == 0)
        return this.pictureDao.insert(picture);
    this.pictureDao.update(picture);
    return id;
}

private void LoadPictures(String viewName, Map<String, Object> parameters) {
    FirebaseDB.LoadPictures(pictures -> {
        Executor myExecutor = Executors.newSingleThreadExecutor();
        myExecutor.execute(() -> {
            for (Picture picture : pictures) {
                long id = this.PictureInsertOrUpdate(picture);
                this.viewPictureDao.insert(new ViewPicture(viewName, id)); //тут ошибка
            }
        });
    }, parameters);
}

Если я запускаю через дебагер, то ошибки нет. Видимо дебагер притормаживает некоторые внутренние процессы приложения, и потоки успевают попасть, скажем так, в такт друг-другу.


Ответы (1 шт):

Автор решения: Aleksandr Andrukhov

Проблема была решена правильным обновлением элемента. Идею взял тут: https://code.luasoftware.com/tutorials/android/android-room-insert-or-update/

Мой код теперь insertOrUpdate:

@Insert(onConflict = OnConflictStrategy.IGNORE)
long insertIgnore(Picture picture);

@Update
void update(Picture picture);

@Transaction
default long insertOrUpdate(Picture picture) {
    long id = insertIgnore(picture);
    if (id == -1) {
        update(picture);
        id = findByPublicId(picture.public_id).id;
    }
    return id;
}
→ Ссылка