| orvind ( @ 2009-06-24 16:35:00 |
История 2 Как редактор испортил базу
Однажды наш дизайнер Миша (
mrfrostman ) пожаловался, что данные, получающиеся при экспорте из редактора не соответствуют действительности. Например, магазинов в редакторе 12, а для установки на карту доступно 16.
В нашей базе есть отдельная табличка с "описателями" ресурсов. Через нее ресурс локается на редактирование и табличка эта отдельная, чтобы взятие/снятие локов не портило список коммитов в envers'е (это такая надстройка над hibernate, которая позволяет отслеживать историю изменений). Единственное, из-за чего (чисто теоретически) действительно могло бы не совпадать количество объектов в редакторе и в экспорченных файлах - это если бы вдруг описатель ресурса удалился, а сам ресурс не пометился бы удаленным. Но это невозможно, так как удаление описателя и пометка ресурса как удаленного производятся внутри одной транзакции, так что если даже по пути выскочил exception, транзакция не даст испортить базу. Мой мозг долго отказывался верить в то, что в этой теории где-то есть ошибка, так что я довольно долго копал в неправильном направлении, проверяя экспортер.
В конце концов я полез в базу.. И обнаружил там, что дескрипторы-таки уничтожены. Я написал скрипт, восстанавливающий дескрипторы и решил еще раз удалить магазин... и опа! Обнаружилось нечто интересное. При попытке удалить магазин вылетает
Мать-перемать! С чего вдруг autocommit=true ?! Как это может быть, если я специально ставил InnoDB, специально добавлял в hibernate опцию autocommit=false и какого-то еще одного способа отключить автокоммит просто нет и быть не может. Вводим текст исключения в гугл и видим, что энтерпрайз-программисты из hibernate'а замутили еще один мегаалгоритм. Учтите, hibernate используется в очень серьезных сайтах. Не удивлюсь, если его используют банки, интернет-магазины и т.д.
Итак, мой код:
Как этот алгоритм блестяще кастрируется hibernate'ом? А вот смотрите.
1. Сначала открывается транзакция - это первая строчка.
2. Мы хотим удалить объект. Hibernate смотрит, что какой-то ресурс удаляется или изменяется, а данные в кэше могут слегка отличаться от данных в базе. Он знает, что транзакция началась и нужно следить, чтобы ничего не сломалось. Для этого нужно привести участвующие в транзакции объекты в то состояние, каким оно является в базе на момент начала транзакции. Дескрипторы наших ресурсов лежат в отдельной таблице и ссылок на них нигде нет, они используются только для отметок, что редактирование началось/закончилось. Поэтому вторая строчка тупо генерит "DELETE FROM resourcedescriptor WHERE id = ?".
3. А вот изменяющийся магазин уже используется во многих местах. Каждый моб может иметь на него ссылку, да и он сам уже мог измениться в базе. Поэтому hibernate должен послать набор селектов, чтобы обновить магазин до нового состояния. А теперь следите за руками! В истории номер 1 мы запретили для селектов делать OUTER JOIN'ы. Поэтому селектов нужно сделать много. Внутри одной транзакции. Не так чтобы очень много, но больше, чем разрешает установленный в hibernate'е по умолчанию лимит селектов на одну транзакцию. Что делать? И тут наши энтерпрайз-гении придумывают блестящее решение. Они.. внимание.. барабанная дробь.. Я говорил, что они настоящие гении? Приготовились?.. Так вот, они включают автокоммит! И типо волки сыты - пользователю никакой эксепшен не вылетел, и сцуко овцы целы - ограничение на количество селектов не превысили, так как каждый селект при включенном автокоммите идет отдельной транзакцией. А теперь возвращаем автокоммит обратно в false, типа так и было. И посылаем апдейты в базу.
4. Ну а теперь пора коммитить транзакцию. Но что за нах? Дескриптор-то уже убит в базе и убит он автокоммитом. Какая-то непонятная ситуация. Выбрасываем эксепшен и роллбэкаем транзакцию. Вуаля!
Самое досадное в этой истории то, что и тут мегасистему можно "настроить". Есть магический флажок relaxAutoCommit=true, о смысле названия которого мне лучше не думать. Я попытался найти в гугле описание того, что же именно флажок делает, но первые 3 страницы мне упорно говорят: "Он фиксит баг с вылетающим эксепшеном Can't call commit when autocommit=true". Типа магия такая.
Я понимаю, конечно, что вопрос риторический, но разве можно писать серьезный коммерческий код ТАК?
Однажды наш дизайнер Миша (
В нашей базе есть отдельная табличка с "описателями" ресурсов. Через нее ресурс локается на редактирование и табличка эта отдельная, чтобы взятие/снятие локов не портило список коммитов в envers'е (это такая надстройка над hibernate, которая позволяет отслеживать историю изменений). Единственное, из-за чего (чисто теоретически) действительно могло бы не совпадать количество объектов в редакторе и в экспорченных файлах - это если бы вдруг описатель ресурса удалился, а сам ресурс не пометился бы удаленным. Но это невозможно, так как удаление описателя и пометка ресурса как удаленного производятся внутри одной транзакции, так что если даже по пути выскочил exception, транзакция не даст испортить базу. Мой мозг долго отказывался верить в то, что в этой теории где-то есть ошибка, так что я довольно долго копал в неправильном направлении, проверяя экспортер.
В конце концов я полез в базу.. И обнаружил там, что дескрипторы-таки уничтожены. Я написал скрипт, восстанавливающий дескрипторы и решил еще раз удалить магазин... и опа! Обнаружилось нечто интересное. При попытке удалить магазин вылетает
java.sql.SQLException: Can't call commit when autocommit=true Мать-перемать! С чего вдруг autocommit=true ?! Как это может быть, если я специально ставил InnoDB, специально добавлял в hibernate опцию autocommit=false и какого-то еще одного способа отключить автокоммит просто нет и быть не может. Вводим текст исключения в гугл и видим, что энтерпрайз-программисты из hibernate'а замутили еще один мегаалгоритм. Учтите, hibernate используется в очень серьезных сайтах. Не удивлюсь, если его используют банки, интернет-магазины и т.д.
Итак, мой код:
entityManager.getTransaction().begin();
entityManager.remove(descriptor);
entityManager.merge(resource);
entityManager.getTransaction().commit();
Как этот алгоритм блестяще кастрируется hibernate'ом? А вот смотрите.
1. Сначала открывается транзакция - это первая строчка.
2. Мы хотим удалить объект. Hibernate смотрит, что какой-то ресурс удаляется или изменяется, а данные в кэше могут слегка отличаться от данных в базе. Он знает, что транзакция началась и нужно следить, чтобы ничего не сломалось. Для этого нужно привести участвующие в транзакции объекты в то состояние, каким оно является в базе на момент начала транзакции. Дескрипторы наших ресурсов лежат в отдельной таблице и ссылок на них нигде нет, они используются только для отметок, что редактирование началось/закончилось. Поэтому вторая строчка тупо генерит "DELETE FROM resourcedescriptor WHERE id = ?".
3. А вот изменяющийся магазин уже используется во многих местах. Каждый моб может иметь на него ссылку, да и он сам уже мог измениться в базе. Поэтому hibernate должен послать набор селектов, чтобы обновить магазин до нового состояния. А теперь следите за руками! В истории номер 1 мы запретили для селектов делать OUTER JOIN'ы. Поэтому селектов нужно сделать много. Внутри одной транзакции. Не так чтобы очень много, но больше, чем разрешает установленный в hibernate'е по умолчанию лимит селектов на одну транзакцию. Что делать? И тут наши энтерпрайз-гении придумывают блестящее решение. Они.. внимание.. барабанная дробь.. Я говорил, что они настоящие гении? Приготовились?.. Так вот, они включают автокоммит! И типо волки сыты - пользователю никакой эксепшен не вылетел, и сцуко овцы целы - ограничение на количество селектов не превысили, так как каждый селект при включенном автокоммите идет отдельной транзакцией. А теперь возвращаем автокоммит обратно в false, типа так и было. И посылаем апдейты в базу.
4. Ну а теперь пора коммитить транзакцию. Но что за нах? Дескриптор-то уже убит в базе и убит он автокоммитом. Какая-то непонятная ситуация. Выбрасываем эксепшен и роллбэкаем транзакцию. Вуаля!
Самое досадное в этой истории то, что и тут мегасистему можно "настроить". Есть магический флажок relaxAutoCommit=true, о смысле названия которого мне лучше не думать. Я попытался найти в гугле описание того, что же именно флажок делает, но первые 3 страницы мне упорно говорят: "Он фиксит баг с вылетающим эксепшеном Can't call commit when autocommit=true". Типа магия такая.
Я понимаю, конечно, что вопрос риторический, но разве можно писать серьезный коммерческий код ТАК?