Заставить Hibernate выдавать DELETE перед INSERT, чтобы избежать уникальных нарушений ограничений? - PullRequest
6 голосов
/ 11 марта 2011

Фон: http://jeffkemponoracle.com/2011/03/11/handling-unique-constraint-violations-by-hibernate

Наш стол:

BOND_PAYMENTS (BOND_PAYMENT_ID, BOND_NUMBER, PAYMENT_ID)

Существует ограничение первичного ключа для BOND_PAYMENT_ID и ограничение уникальности для (BOND_NUMBER, PAYMENT_ID).

Приложение использует Hibernate и позволяет пользователю просматривать все платежи, связанные с конкретной Облигацией; и это позволяет им создавать новые ссылки и удалять существующие ссылки. После того, как они внесли все необходимые изменения на странице, они нажимают «Сохранить», и Hibernate делает все возможное, чтобы запустить необходимый SQL в базе данных. По-видимому, Hibernate определяет, какие записи необходимо удалить, какие нужно вставить, и оставляет остальные нетронутыми. К сожалению, сначала он делает INSERT, затем выполняет DELETE.

Если пользователь удаляет ссылку на платеж, затем передумает и повторно вставляет ссылку на тот же платеж, Hibernate с радостью пытается вставить его, а затем удалить. Поскольку эти вставки / удаления выполняются как отдельные операторы SQL, Oracle немедленно проверяет ограничение при первой вставке и выдает ORA-00001, нарушенное уникальное ограничение .

Нам известны только два варианта:

  1. Сделать ограничение отложенным
  2. Снять уникальное ограничение

Вариант 2 не очень приятен на вкус, поскольку ограничение обеспечивает превосходную защиту от неприятных ошибок приложения, которые могут позволить сохранить противоречивые данные. Мы пошли с вариантом 1.

ALTER TABLE bond_payments ADD
  CONSTRAINT bond_payment_uk UNIQUE (bond_number, payment_id)
  DEFERRABLE INITIALLY DEFERRED;

Недостатком является то, что индекс, созданный для контроля этого ограничения, теперь является неуникальным индексом, поэтому может быть несколько менее эффективным для запросов. Мы решили, что это не такой большой ущерб для этого конкретного случая. Другим недостатком (по совету Гэри) является то, что он может страдать от конкретной ошибки Oracle - хотя я считаю, что мы будем защищены (по крайней мере, в основном) от работы приложения.

Есть ли другие варианты, которые мы должны рассмотреть?

1 Ответ

2 голосов
/ 11 марта 2011

Из описанной вами проблемы неясно, есть ли у вас сущность BondPayment или если у вас есть Bond, связанная напрямую с Payment. На данный момент, я полагаю, у вас есть связь между Payment и Bond до BondPayment. В этом случае Hibernate делает правильные вещи, и вам нужно добавить некоторую логику в ваше приложение, чтобы получить ссылку и удалить ее (или изменить ее). Примерно так:

bond.getBondPayment().setPayment(newPayment);

Вы, вероятно, делаете что-то вроде этого:

BondPayment bondPayment = new BondPayment();
bondPayment.setPayment(newPayment);
bondPayment.setBond(bond);
bond.setBondPayment(bondPayment);

В первом случае BondPayment.id сохраняется, и вы просто меняете payment для него. Во втором случае это совершенно новый BondPayment, и он будет конфликтовать с существующей записью в базе данных.

Я сказал, что Hibernate делает правильные вещи, потому что он угрожает BondPayment как «обычной» сущности, жизненный цикл которой определяется вашим приложением. Это то же самое, что User с уникальным ограничением на login, и вы пытаетесь вставить вторую запись с дубликатом login. Hibernate примет (он не знает, существует ли login в базе данных), и ваша база данных откажется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...