-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DDC-601: UnitOfWork commit order problem #5109
Comments
Comment created by romanb: Hi. The order of operations is mainly designed for a) efficiency and b) preserving foreign key constraints, but not for such kinds of unique constraints. The order is:
Where the topological order is dictated by the foreign key constraints. The unique constraint is not the primary key, is it? In either case, I think this is a strange thing to do, even more if this is the primary key, as a primary key should never be updated or reused by other objects. You can still easily do it, just use an extra flush like you did. If you want both flushes to execute in a single transaction, simply use your own transaction demarcation:
I don't think we can change the order by putting deletions first without breaking other things. At least Hibernate has a similar order (inserts first) and probably for a reason. Here are some related discussions: http://opensource.atlassian.com/projects/hibernate/browse/HHH-2801 |
Comment created by romanb: I tried this with eclipselink (jpa 2 reference implementation, http://www.eclipse.org/eclipselink/ ) as well, same unique constraint violation (inserts first). That makes me not very optimistic that we can do any better. |
Comment created by romanb: Some more references on why deletes are performed last: http://old.nabble.com/EntitEntityManager.remove%3A-Why-does-it-care-about-nullable%3Dfalse--td22942681.html We may consider doing the deletes first since we always have a topological order and most of the mentioned issues might not apply to our architecture or might not be common cases but I am not certain yet. In any case, like I mentioned in my first comment, you can just flush() after remove() to force the deletion first. |
Issue was closed with resolution "Won't Fix" |
Comment created by jean-gui: Calling flush() after remove() is not always possible when using Doctrine with Symfony, because bindRequest does everything under the hood. |
Comment created by magnetik: I'm facing the exact same issue 4 years later. Is ther any thing new to help with this situation? |
Comment created by @Ocramius: [~magnetik] the issue is marked as |
Comment created by deatheriam: When there is a complicated system of flush event handlers that involves sending requests to independent systems, having multiple flushes to solve this particular issue creates a lot of other issues on its own. Also it goes against Doctrine philosophy to issue as few flushes as possible. I checked one of two links (the other one is dead) posted above that supposedly explains why deletes should go last in a transaction, and did not find any meaningful explanation, a quick Google search also yielded a few discussions without a clear answer, for example this one: https://forum.hibernate.org/viewtopic.php?t=934483. @marco, could you re-state why this cannot be achieved without sending any future readers of this ticket to some articles? |
Comment created by deatheriam: At least give us, users an option to use this approach: {quote}Using the Unit of Work setShouldPerformDeletesFirst Method By default, TopLink does insert and update operations first, before delete operations, to ensure that referential integrity is maintained. This is the preferred approach. If you are forced to replace an object with unique constraints by deleting it and inserting a replacement, you may cause a constraint violation if the insert operation occurs before the delete operation. In this case, call setShouldPerformDeletesFirst to perform the delete operation before the insert operation.{quote} |
I'd like to emphasize that this really is a pain when you deal with collections, that you want to The explanation of the current order of operations lies in this link:
One could argue that you could then do So there is no one-size-fits-all solution to this problem. What would be good is, indeed, a configuration option to force |
Even though this whole issue relates to the edge case of replacing entities with unique constraints with one another, "won't fix" won't make it go away. The current workarounds look horrifying, to say the least, and there really should be a better way to solve this problem. Theoretically: |
3 years gone and this is not solved. To "replace" entity in my project i do DBAL delete query before persist (in my case no need to load entity). ORM is simple unable to handle it. This is fine for simple entities, but when it comes from cascade persist combined with orphan remove it is so messed and hard to debug.
Actually there is one, not so simple but real solution: do not split INSERT / UPDATE / DELETE operations, but build operation order based on requirements. First run queries that has no need in chained operations. Then loop next queries that has all requirements solved and continue till all work is done. And for sure circular dependencies can be detected and solved by splitting into two INSERT/UPDATE queries. |
I'm facing an situation where i need to do uptades interleaved with inserts, becuase if i don't, the database give unique exception,, and if i do flush in each case, i can have a Exception in the middle. In that case, i have to do an catch with deletes interleaved with updates to rollback the other flushs. And this can give exception too. |
Reopening for research only for now, 3.0 could be something where we rethink if that might be fixable. |
Another use case where I need UPDATE after DELETE is updating sort order field of remaining entities after DELETING another entity. For example I've got:
Let's say that I want to remove B entity and then run something like that: SET @cnt = 0;
UPDATE table AS w
SET w.sort_order = @cnt := @cnt + 1
ORDER BY w.sort_order ASC
; It won't work because my UPDATE will be performed before DELETE. I can't just flush after DELETE and then run an UPDATE because removing process is a part of deeper process and I can't just break a transaction. UPDATE query is also more complicated and depends on some higher level relations. The most safe solution is to make a sequence of DELETE, UPDATE, DELETE, UPDATE queries wrapped by single transaction. However it's not possible right now. |
Adding a use case for consideration. I have users and users may have 1 or 0 credentials (login, pw). Logins need to be unique and I would like credentials to be replaceable. Ideally, credentials would be a readonly unidirectional object owned by users, if I replace them I would like that to happen in a single transaction, or risk a state where someone loses their credentials between remove and replace. Obviously it can be worked around, like making credentials mutable, or just shoving everything as nullable on the user object. But it feels like a limitation of the orm. |
I was looking for a way to fix this issue because I think that it's really an issue. (I'm new in this thread, and old reasons of why it will not be fixed are not accessible anymore x)) I think the best way to handle this is to change how are staged entities in the uow. They are currently simple arrays but to support order on a commit we need to add a new order notion. I have 2 ideas, with pro and cons:
In any case, it will lead to major changes deep inside the uow in many many functions. I'm interested in trying something. I'd like to know if somebody would be able to follow me until it's merged or if it looks more like a real wontfix (as I said, I cannot really read previous arguments of why it's not a good idea to fix it, I'm open to anything). Thanks! |
Sometimes, complex transactions need to follow a specific order of operations (SELECT, DELETE, UPDATE, INSERT,...) in only one transaction (COMMIT all or ROLLBACK all). I thought ORM just follow the natural order of operations (I mean the order the developer coded it). As an idea, why not try again the transaction with the developer order if the transaction with the INSERT/UPDATE/DELETE reordering did not work and realized a ROLLBACK ? (yes, It's not an efficient idea) |
There are a few requests (doctrine#5742, doctrine#5368, doctrine#5109, doctrine#6776) that ask to change the order of operations in the UnitOfWork to perform "deletes before inserts", or where such a switch appears to solve a reported problem. I don't want to say that this is not doable. But this PR at least adds two tricky examples where INSERTs need to be done before an UPDATE can refer to new database rows; and where the UPDATE needs to happen to release foreign key references to other entities before those can be DELETEd. So, at least as long as all operations of a certain type are to be executed in blocks, this example allows no other order of operations than the current one.
#10809 adds an example that shows why we cannot – at least not, in general – easily change the current order of operations in the UoW. The example requires an INSERT to be done so that an UPDATE can refer to the new entity, and the UPDATE is necessary to release a foreign key reference so that a DELETE can take place. |
There are a few requests (doctrine#5742, doctrine#5368, doctrine#5109, doctrine#6776) that ask to change the order of operations in the UnitOfWork to perform "deletes before inserts", or where such a switch appears to solve a reported problem. I don't want to say that this is not doable. But this PR at least adds two tricky examples where INSERTs need to be done before an UPDATE can refer to new database rows; and where the UPDATE needs to happen to release foreign key references to other entities before those can be DELETEd. So, at least as long as all operations of a certain type are to be executed in blocks, this example allows no other order of operations than the current one.
Regarding @mastir's idea from #5109 (comment): It’s not only that it takes to consider all SQL operations at the individual level and understand when e. g. an UPDATE requires another INSERT to go first. It would need to be smart enough to see where an UPDATE replaces an old value, and that old value is what is blocking another DELETE with a foreign key reference. |
Yes its can be done with correct structure. Here is a way i see it. Example: We have a Dog with 4 Legs in a Cage. Our models are: Cage, Dog, Legs. Cage-Dog is OneToOne relation with orphanRemoval=true. Dog-Leg is OneToMeny relation with orphanRemoval=true. And the scenario: customer comes to store asks to change socks_color for every leg of $liked_dog and takes this dog, so worker go to basment and put another dog into this cage:
So we have this operations in order they were perstited:
So the result operation order is: 1,3 / 2,4,6,7 / 5 The hardest part is requirements detection and checks, but nothing unreal. First simplest idea was to use somthing like simple object with boolean property and change it from UoW, but this will make UoW have all of the possible requiremnts logic. So another way is to have some classes to run checks, for example check for pending operations on same entity before we run delete:
In this example check we wait for all of the queued operations before delete, but we can use same logic to wait only for operations added before our by simple replacing continue with break and some interface like Resolvable added to operations and maybe some new other Resolvable type requirments, like EntityPrimaryKeyRequirment and etc. @mpdude your absolutly right, we can even have RelatiedEntityKeyRequirment and it can be triggered not only by entity removal, but also by its value update. We can apply new strategies like CircularRequrmentsSolvingStrategy to split circular requirments into insert/update operations or OptimizeGroupInsertStrategy to run multiple inserts in one query or even PreparedQueryAnalyticStratagy, to handle analitics and have prepared statments for most of our common operations. The way to detect recursion in requrments is simple as: |
Since there have been attempts to fix it, I'll add my case.
How it should work:
How it try to works:
Perhaps my attempt to explain failed, I will try to answer questions if something is not clear. |
Jira issue originally created by user exxbrain:
The next critical case doesn't work, because of Duplicate entry error:
Instead of that i must use the next construction:
The text was updated successfully, but these errors were encountered: