强制 Hibernate 在 INSERT 之前发出 DELETE 以避免违反唯一约束?

发布于 2024-10-21 05:58:51 字数 1182 浏览 3 评论 0原文

背景: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 会计算出哪些记录需要删除,哪些记录需要插入,而其余的则保持不变。不幸的是,它首先执行插入操作,然后执行删除操作。

如果用户删除了付款链接,然后改变了主意并重新插入了同一付款的链接,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 错误 - 尽管我相信由于应用程序的工作方式,我们将(至少,大部分)免疫。

我们还应该考虑其他选择吗?

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

Our table is:

BOND_PAYMENTS (BOND_PAYMENT_ID, BOND_NUMBER, PAYMENT_ID)

There is a Primary key constraint on BOND_PAYMENT_ID, and a Unique constraint on (BOND_NUMBER, PAYMENT_ID).

The application uses Hibernate, and allows a user to view all the Payments linked to a particular Bond; and it allows them to create new links, and delete existing links. Once they’ve made all their desired changes on the page, they hit “Save”, and Hibernate does its magic to run the required SQL on the database. Apparently, Hibernate works out which records need to be deleted, which need to be inserted, and leaves the rest untouched. Unfortunately, it does the INSERTs first, then it does the DELETEs.

If the user deletes a link to a payment, then changes their mind and re-inserts a link to the same payment, Hibernate quite happily tries to insert it then delete it. Since these inserts/deletes are running as separate SQL statements, Oracle validates the constraint immediately on the first insert and issues ORA-00001 unique constraint violated.

We know of only two options:

  1. Make the constraint deferrable
  2. Remove the unique constraint

Option 2 is not very palatable, because the constraint provides excellent protection from nasty application bugs that might allow inconsistent data to be saved. We went with option 1.

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

The downside is that the index created to police this constraint is now a non-unique index, so may be somewhat less efficient for queries. We have decided this is not as great a detriment for this particular case. Another downside (advised by Gary) is that it may suffer from a particular Oracle bug - although I believe we will be immune (at least, mostly) due to the way the application works.

Are there any other options we should consider?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

是伱的 2024-10-28 05:58:52

从您描述的问题来看,尚不清楚您是否有实体 BondPayment 或者是否有直接链接到 PaymentBond。现在,我假设您通过 BondPayment 建立了 PaymentBond 之间的链接。在这种情况下,Hibernate 正在做正确的事情,您需要在应用程序中添加一些逻辑来检索链接并将其删除(或更改它)。像这样的事情:

bond.getBondPayment().setPayment(newPayment);

您可能正在做这样的事情:

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

在第一种情况下,保留了BondPayment.id,而您只是更改了它的付款方式。在第二种情况下,它是一个全新的 BondPayment,它将与数据库中的现有记录冲突。

我说 Hibernate 正在做正确的事情,因为它威胁到作为“常规”实体的 BondPayment,其生命周期由您的应用程序定义。这与拥有一个对 login 具有唯一约束的 User 相同,并且您尝试插入具有重复 login 的第二条记录。 Hibernate 将接受(它不知道数据库中是否存在登录),而您的数据库将拒绝。

From the problem you described, it's not clear if you have an entity BondPayment or if you have a Bond linked directly to a Payment. For now, I suppose you have the link between Payment and Bond through BondPayment. In this case, Hibernate is doing the right thing, and you'll need to add some logic in your app to retrieve the link and remove it (or change it). Something like this:

bond.getBondPayment().setPayment(newPayment);

You are probably doing something like this:

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

In the first case, the BondPayment.id is kept, and you are just changing the payment for it. In the second case, it's a brand new BondPayment, and it will conflict with an existing record in the database.

I said that Hibernate is doing the right thing because it threats BondPayment as a "regular" entity, whose lifecycle is defined by your app. It's the same as having a User with a unique constraint on login, and you are trying to insert a second record with a duplicate login. Hibernate will accept (it doesn't knows if the login exists in the database) and your database will refuse.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文