2014-01-22 6 views
0

Я следующий кодorg.hibernate.StaleObjectStateException: при сохранении домена класса

studentInstance.addToAttempts(studentQuizInstance) 
studentInstance.merge() 
studentInstance.save(flush:true) 

и он бросает следующее исключение в последней строке кода выше

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.easytha.Quiz#1] 

Я видел пару нитей, обсуждающих ту же проблему, и в соответствии с ними я попытался использовать studentInstance.withTransaction также studentInstance.withTransaction, и я также изменил область обслуживания для запроса, но пока ничего не помогло.

Это определенно проблема с потоками, потому что это происходит только тогда, когда 20-30 пользователей одновременно называют этот код.

+0

Я был в подобных ситуациях, что происходит если вы вызываете .merge() на studentQuizInstance, прежде чем вызывать его на studentInstance? – marko

+0

Есть пара аккуратных отладочных трюков для подобных ситуаций (думаю, это помогло мне решить аналогичную проблему в тот же день) - http://stackoverflow.com/questions/536601/what-are-your-favorite-grails-debugging -tricks – marko

ответ

1

Основная проблема здесь в том, что отношения двунаправлены, и обе стороны изменены и версируются. Когда вы вызываете addToAttempts, коллекция attempts, сгенерированная свойством hasMany, инициализируется новой пустой коллекцией, если она равна null, затем экземпляр добавляется к ней, а в поле Student экземпляра устанавливается владеющий учащийся, чтобы убедиться, что в памяти состояние такое же, как и позднее, если вы перезагрузите все из базы данных. Но когда вы включили управление версиями (оптимистичная блокировка), так как обе стороны изменились, оба получат ошибку. Поэтому, если вы перекрываете коллекцию между двумя одновременными пользователями, вы получаете эту ошибку. И это реально - вы рискуете потерять предыдущее обновление, если вы явно не блокируете или не используете оптимистичную блокировку.

Но все это полностью искусственно. Это похоже на много-ко-многим, поэтому все, что вам нужно, - это добавить новую строку в таблицу соединений, которая указывает ученику и попытку. Grails делает это, настраивая коллекции, которые Hibernate обнаруживает изменения, но это действительно использует побочный эффект. Это также очень дорого для больших коллекций. Я оставил одну часть выше о вызове addToAttempts; если бы там уже были экземпляры, каждый из них будет извлекаться из базы данных, хотя вам не нужно ни одного из них. Grails загружает все N предыдущих элементов (где N может быть очень большим числом) и добавляет новый N + 1 st, поэтому Hibernate может обнаружить этот новый элемент. Все, что вам нужно, это вставить одну строку, и вы получите значительное количество трафика базы данных.

Исправление состоит в том, чтобы не разбрасывать merge и withTransaction звонки или другие случайные материалы, которые вы найдете здесь или в другом месте - это удаление одновременного доступа. Здесь вам повезло, потому что это абсолютно искусственно. См. Этот разговор, который я сделал некоторое время назад, что, к сожалению, так же актуально, как и в нынешних Grails, как это было тогда - я описываю подходы к удалению коллекций и замену их более разумными подходами: http://www.infoq.com/presentations/GORM-Performance

+0

Но в вашем случае эта операция должна всегда терпеть неудачу, исправить? – Sap

+0

Нет, проблем с параллелизмом не будет, если вы просто добавляете новую запись в таблицу соединений. Один FK укажет на общую попытку, а другой на текущего ученика. Другие параллельные вставки будут использовать идентификаторы этих учеников. –

+0

Вы можете увидеть пример явного сопоставления таблицы соединений как класса домена в плагине http://grails.org/plugin/spring-security-core - он использует этот подход для сопоставления многих-многих между пользователями и ролями. Если вы добавите 'compile ': spring-security-core: 2.0-RC2" 'в раздел' plugins' 'BuildConfig.groovy' (также включите' mavenRepo "http: //repo.spring.io/milestone/"' в блоке 'repositories') и запустить' grails compile', затем 'grails s2-quickstart test User Role' вы увидите класс домена UserRole.groovy. –