Получение исключения "Неудачное ограничение UNIQUE" с использованием метода Ormlite createOrUpdate()

У меня есть несколько вызовов по всему моему приложению, где таблицы в моей базе данных обновляются с помощью вызовов .createOrUpdate()

Документация предлагает из этих звонков:

Это удобный метод для создания элемента в базе данных, если он не существует. Идентификатор извлекается из параметра данных, и в базе данных выполняется запрос по идентификатору. Если в базе данных есть строка с таким же идентификатором, то все столбцы в базе данных будут обновлены из полей в параметре данных. Если идентификатор равен нулю (или 0 или другому значению по умолчанию) или не существует в базе данных, тогда объект будет создан в базе данных. Это также означает, что для вашего элемента данных должно быть определено поле id.

Тогда я понимаю, что при вызове createOrUpdate() базовый код должен вызывать "insert", если в базе данных нет строки, и "update", если строка существует.

Кажется, что происходит то, что основной код вызывает "вставку", когда строка уже существует, вызывая исключение.

Кто-нибудь знает, почему это происходит или как этого избежать?

Исключение ниже:

java.lang.RuntimeException: java.sql.SQLException: Unable to run insert stmt on object
    Feed(id=126275579_organisations-email_58284484, type=organisations-email, category=1, msg=<p>Body</p>, regarding=Bobby Castle, subject=Subject, createdTs=2016-01-04T09:59:27+00:00, completed=0, read=0, starred=0, archived=0, remoteAttachments=[], localAttachments=null, authorId=250008275, authorName=Live Regression Test, authorImg=null): INSERT INTO `feed` (`id` ,`type` ,`category` ,`msg` ,`regarding` ,`subject` ,`createdTs` ,`completed` ,`read` ,`starred` ,`archived` ,`authorId` ,`authorName` ,`authorImg` ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
     at com.j256.ormlite.dao.RuntimeExceptionDao.createOrUpdate(RuntimeExceptionDao.java:252)
     at uk.co.test.test.data.orm.models.Feed.createOrUpdate(Feed.java:81)
     at uk.co.test.test.interactors.server.CategoryInteractor$6.onSuccessfulResponse(CategoryInteractor.java:308)
     at uk.co.test.test.interactors.server.CategoryInteractor$6.onSuccessfulResponse(CategoryInteractor.java:290)
     at uk.co.test.test.interactors.common.StandardCallbackRunnable.checkStandardResponse(StandardCallbackRunnable.java:27)
     at uk.co.test.test.interactors.common.StandardCallbackRunnable.checkStandardResponse(StandardCallbackRunnable.java:12)
     at uk.co.test.test.interactors.common.CallbackRunnable.success(CallbackRunnable.java:127)
     at retrofit.CallbackRunnable$1.run(CallbackRunnable.java:45)
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
     at java.lang.Thread.run(Thread.java:818)
Caused by: java.sql.SQLException: Unable to run insert stmt on object Feed(id=126275579_organisations-email_58284484, type=organisations-email, category=1, msg=<p>Body</p>, regarding=Bobby Castle, subject=Subject, createdTs=2016-01-04T09:59:27+00:00, completed=0, read=0, starred=0, archived=0, remoteAttachments=[], localAttachments=null, authorId=250008275, authorName=Live Regression Test, authorImg=null): INSERT INTO `feed` (`id` ,`type` ,`category` ,`msg` ,`regarding` ,`subject` ,`createdTs` ,`completed` ,`read` ,`starred` ,`archived` ,`authorId` ,`authorName` ,`authorImg` ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
     at com.j256.ormlite.misc.SqlExceptionUtil.create(SqlExceptionUtil.java:22)
     at com.j256.ormlite.stmt.mapped.MappedCreate.insert(MappedCreate.java:135)
     at com.j256.ormlite.stmt.StatementExecutor.create(StatementExecutor.java:450)
     at com.j256.ormlite.dao.BaseDaoImpl.create(BaseDaoImpl.java:310)
     at com.j256.ormlite.dao.BaseDaoImpl.createOrUpdate(BaseDaoImpl.java:336)
     at com.j256.ormlite.dao.RuntimeExceptionDao.createOrUpdate(RuntimeExceptionDao.java:249)
     at uk.co.test.test.data.orm.models.Feed.createOrUpdate(Feed.java:81) 
     at uk.co.test.test.interactors.server.CategoryInteractor$6.onSuccessfulResponse(CategoryInteractor.java:308) 
     at uk.co.test.test.interactors.server.CategoryInteractor$6.onSuccessfulResponse(CategoryInteractor.java:290) 
     at uk.co.test.test.interactors.common.StandardCallbackRunnable.checkStandardResponse(StandardCallbackRunnable.java:27) 
     at uk.co.test.test.interactors.common.StandardCallbackRunnable.checkStandardResponse(StandardCallbackRunnable.java:12) 
     at uk.co.test.test.interactors.common.CallbackRunnable.success(CallbackRunnable.java:127) 
     at retrofit.CallbackRunnable$1.run(CallbackRunnable.java:45) 
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) 
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) 
     at java.lang.Thread.run(Thread.java:818) 
Caused by: java.sql.SQLException: inserting to database failed: INSERT INTO `feed` (`id` ,`type` ,`category` ,`msg` ,`regarding` ,`subject` ,`createdTs` ,`completed` ,`read` ,`starred` ,`archived` ,`authorId` ,`authorName` ,`authorImg` ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
     at com.j256.ormlite.misc.SqlExceptionUtil.create(SqlExceptionUtil.java:22)
     at com.j256.ormlite.android.AndroidDatabaseConnection.insert(AndroidDatabaseConnection.java:169)
     at com.j256.ormlite.stmt.mapped.MappedCreate.insert(MappedCreate.java:91)
     at com.j256.ormlite.stmt.StatementExecutor.create(StatementExecutor.java:450) 
     at com.j256.ormlite.dao.BaseDaoImpl.create(BaseDaoImpl.java:310) 
     at com.j256.ormlite.dao.BaseDaoImpl.createOrUpdate(BaseDaoImpl.java:336) 
     at com.j256.ormlite.dao.RuntimeExceptionDao.createOrUpdate(RuntimeExceptionDao.java:249) 
     at uk.co.test.test.data.orm.models.Feed.createOrUpdate(Feed.java:81) 
     at uk.co.test.test.interactors.server.CategoryInteractor$6.onSuccessfulResponse(CategoryInteractor.java:308) 
     at uk.co.test.test.interactors.server.CategoryInteractor$6.onSuccessfulResponse(CategoryInteractor.java:290) 
     at uk.co.test.test.interactors.common.StandardCallbackRunnable.checkStandardResponse(StandardCallbackRunnable.java:27) 
     at uk.co.test.test.interactors.common.StandardCallbackRunnable.checkStandardResponse(StandardCallbackRunnable.java:12) 
     at uk.co.test.test.interactors.common.CallbackRunnable.success(CallbackRunnable.java:127) 
     at retrofit.CallbackRunnable$1.run(CallbackRunnable.java:45) 
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) 
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) 
     at java.lang.Thread.run(Thread.java:818) 
Caused by: android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: feed.id (code 1555)
     at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
     at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:782)
     at android.

1 ответ

Решение

Тогда я понимаю, что при вызове createOrUpdate() базовый код должен вызывать "insert", если в базе данных нет строки, и "update", если строка существует.

Это верно, однако операция не является (и не может быть) атомарной, поскольку для ее выполнения выполняется несколько вызовов базы данных. В методе DAO нет блокировки, хотя, возможно, и должно быть.

Если мы посмотрим на BaseDaoImpl.createOrUpdate(...) код вы можете увидеть, что это делает:

  1. Извлеките идентификатор из рассматриваемого объекта.
  2. Ищет его в базе данных, чтобы увидеть, существует ли он.
  3. Вызовы update(...) если существует иначе create(),

Поскольку происходит несколько обращений к базе данных, существуют условия состязания, если несколько потоков одновременно выполняют этот метод.

at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)

Правильно, если вы используете этот метод DAO с потоками (и createIfNotExists(...) который также делает 2 вызова базы данных), то я бы сделал это в течение synchronized блок:

synchronized (dao) {
    status = dao.createOrUpdate(...);
}
Другие вопросы по тегам