强制 Hibernate 在 INSERT 之前发出 DELETE 以避免违反唯一约束?
背景: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违反唯一约束。
我们只知道两个选项:
- 使约束可延迟
- 删除唯一约束
选项 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:
- Make the constraint deferrable
- 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
从您描述的问题来看,尚不清楚您是否有实体
BondPayment
或者是否有直接链接到Payment
的Bond
。现在,我假设您通过BondPayment
建立了Payment
和Bond
之间的链接。在这种情况下,Hibernate 正在做正确的事情,您需要在应用程序中添加一些逻辑来检索链接并将其删除(或更改它)。像这样的事情:您可能正在做这样的事情:
在第一种情况下,保留了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 aBond
linked directly to aPayment
. For now, I suppose you have the link betweenPayment
andBond
throughBondPayment
. 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:You are probably doing something like this:
In the first case, the
BondPayment.id
is kept, and you are just changing thepayment
for it. In the second case, it's a brand newBondPayment
, 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 aUser
with a unique constraint onlogin
, and you are trying to insert a second record with a duplicatelogin
. Hibernate will accept (it doesn't knows if thelogin
exists in the database) and your database will refuse.