Grails "Строка была обновлена ​​или удалена другой транзакцией (или отображение несохраненного значения было неправильным)"

Кажется, что нет определенного решения проблем параллелизма в Grails (2.3.7). Я испробовал все рекомендации, но когда я нажимаю количество одновременных потоков, следующий фрагмент кода неизменно завершается ошибкой:

package simpledb

import grails.transaction.Transactional
import groovy.transform.Synchronized
import org.apache.commons.logging.LogFactory

@Transactional
class OwnerService {
    private static final myLock1 = new Object()
    private static final myLock2 = new Object()

    @Synchronized('myLock1')
    static public saveOwner(def ownerName) {
        def ownerInstance = null
        Owner.withNewTransaction {
            ownerInstance = Owner.findOrCreateByName(ownerName)
            ownerInstance.save(failOnError: true, flush: true)
        }
        ownerInstance
    }

    @Synchronized('myLock2')
    static public associateDog(def ownerId, def dogId) {
        def lockedOwnerInstance
        Owner.withNewTransaction {
            lockedOwnerInstance = Owner.lock(ownerId)
            def lockedDogInstance = Dog.lock(dogId)
            lockedOwnerInstance.addToDogs(lockedDogInstance)
            lockedOwnerInstance.save(failOnError: true, flush: true)
        }
        lockedOwnerInstance
    }
}

Сбой в строке "def lockedDogInstance = Dog.lock(dogId)":

Error 500: Internal Server Error    

URI
      /simpledb/JsonSlurper/api
Class
      org.hibernate.StaleObjectStateException
Message
      Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [simpledb.Dog#111]

Приведенный выше дизайн очень прост, когда между владельцем и собакой существует отношение "многие ко многим":

Класс собаки:

package simpledb

class Dog {
    String name
    Breed breed = null
    Integer age = null
    static hasMany = [owners: Owner]
    static belongsTo = Owner
    static mapping = { owners lazy: false }
    static constraints = {
        name blank: false, nullable: false, unique: true
        breed nullable: true
        age nullable: true
    }
}

Владелец класса:

package simpledb

class Owner {
    String name;
    static hasMany = [dogs: Dog]
    static mapping = { dogs lazy: false }
    static constraints = {
    }
}

К вашему сведению - БД - MySQL.

Любые рекомендации?

1 ответ

Хорошо, у вас здесь много чего происходит, большую часть которого, я уверен, вы можете утилизировать. Так что вместо того, чтобы пытаться это исправить, давайте разберем его до минимума и продолжим:

  1. Ваши методы обслуживания не должны быть статичными.
  2. Ваш сервис уже транзакционный, поэтому withNewTransaction() можешь идти. Вам также не нужно промывать.
  3. Нет необходимости синхронизировать методы обслуживания.
  4. Вам не нужно блокировать на Dog потому что вы не меняете его (добавляя его в Owner.dogs только создает запись в объединяемой таблице).

С этими изменениями ваша служба выглядит так:

package simpledb

import grails.transaction.Transactional
import org.apache.commons.logging.LogFactory

@Transactional
class OwnerService {

    def saveOwner(def ownerName) {
        def ownerInstance = Owner.findOrCreateByName(ownerName)

        ownerInstance.save(failOnError: true)
        ownerInstance
    }

    def associateDog(def ownerId, def dogId) {
        def ownerInstance = Owner.lock(ownerId)
        def dogInstance = Dog.read(dogId)

        ownerInstance.addToDogs(dogInstance)
        ownerInstance.save(failOnError: true)
        ownerInstance
    }
}

Посмотрите, как далеко это уходит. Возможно, вы даже сможете снять блокировку владельца.

Помимо того, что сказал @Emmanuel-Rosa, если происходит слишком много одновременных обновлений, можете ли вы также убедиться, что перед сохранением вызываете "refresh" (для владельца)? (подход повторяющихся операций чтения).

Дополнения таблицы соединений не должны страдать от них, хотя. Только если некоторые собаки пытаются быть "добавлены" к тем же владельцам, это может вызвать проблемы.

Другой подход (не в этом случае, но), если нужно обновить только один или два столбца, вы можете использовать обычный SQL.

Другие вопросы по тегам