History | Log In     View a printable version of the current page.  
Issue Details (XML | Word | Printable)

Key: HHH-368
Type: Bug Bug
Status: Closed Closed
Resolution: Rejected
Priority: Major Major
Assignee: Unassigned
Reporter: Robert Watkins
Votes: 1
Watchers: 3
Operations

If you were logged in you would be able to see more operations.
Hibernate Core

Deleting many-to-many or one-to-many entities isn't cascading.

Created: 18/Apr/05 11:19 PM   Updated: 20/Apr/05 04:55 PM
Component/s: core
Affects Version/s: 3.0 final
Fix Version/s: None

Time Tracking:
Not Specified

Environment: Hibernate 3.0, Oracle 10g


 Description  « Hide
I have both many-to-many and one-to-many mappings in my project. When I delete the entities, the deletion isn't cascaded accordingly. Thus, dependent objects aren't deleted from the database (either the 'many' side of the one-to-many, or the entries in the join table for the many-to-many).

Example mapping (one-to-many):

Booking -> BookingNight.

HQL: delete from Booking

Error:
19/04/2005 13:57:03 org.hibernate.hql.ast.QueryTranslatorImpl parse FINE: parse() - HQL: delete from com.wotif.jaguar.domain.booking.Booking
19/04/2005 13:57:03 org.hibernate.hql.ast.QueryTranslatorImpl showHqlAst FINE: --- HQL AST ---
 \-[DELETE] 'delete'
    \-[FROM] 'implied-from-so-i-can-use-the-fromClause-rule-during-analysis-phase'
       \-[DOT] '.'
          +-[DOT] '.'
          | +-[DOT] '.'
          | | +-[DOT] '.'
          | | | +-[DOT] '.'
          | | | | +-[IDENT] 'com'
          | | | | \-[IDENT] 'wotif'
          | | | \-[IDENT] 'jaguar'
          | | \-[IDENT] 'domain'
          | \-[IDENT] 'booking'
          \-[IDENT] 'Booking'

19/04/2005 13:57:03 org.hibernate.hql.ast.ErrorCounter throwQueryException FINE: throwQueryException() : no errors
19/04/2005 13:57:03 org.hibernate.hql.ast.FromElement doInitialize FINE: FromClause{level=1} : com.wotif.jaguar.domain.booking.Booking (no alias) -> booking0_
19/04/2005 13:57:03 org.hibernate.hql.ast.QueryTranslatorImpl analyze FINE: --- SQL AST ---
 \-[DELETE] QueryNode: 'delete' querySpaces (ORDER_INVOICE)
    \-[FROM] FromClause: 'implied-from-so-i-can-use-the-fromClause-rule-during-analysis-phase' FromClause{level=1, fromElementCounter=1, fromElements=1, fromElementByClassAlias=[], fromElementByTableAlias=[booking0_], fromElementsByPath=[], collectionJoinFromElementsByPath=[], impliedElements=[]}
       \-[FROM_FRAGMENT] FromElement: 'ORDER_INVOICE' FromElement{explicit,not a collection join,classAlias=null,role=null,tableName=ORDER_INVOICE,tableAlias=booking0_,colums={,className=com.wotif.jaguar.domain.booking.Booking}}

19/04/2005 13:57:03 org.hibernate.hql.ast.ErrorCounter throwQueryException FINE: throwQueryException() : no errors
19/04/2005 13:57:03 org.hibernate.hql.ast.QueryTranslatorImpl generate FINE: HQL: delete from com.wotif.jaguar.domain.booking.Booking
19/04/2005 13:57:03 org.hibernate.hql.ast.QueryTranslatorImpl generate FINE: SQL: delete from ORDER_INVOICE
19/04/2005 13:57:03 org.hibernate.hql.ast.ErrorCounter throwQueryException FINE: throwQueryException() : no errors
19/04/2005 13:57:03 org.hibernate.jdbc.AbstractBatcher logOpenPreparedStatement FINE: about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
19/04/2005 13:57:03 org.hibernate.jdbc.AbstractBatcher log FINE: delete from ORDER_INVOICE
19/04/2005 13:57:03 org.hibernate.jdbc.AbstractBatcher logClosePreparedStatement FINE: about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
19/04/2005 13:57:03 org.hibernate.util.JDBCExceptionReporter logExceptions FINE: could not execute update query [delete from ORDER_INVOICE]
java.sql.SQLException: ORA-02292: integrity constraint (RWATKINS.OIAN_ORDER_INVOICE_ID_FK) violated - child record found

at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:125)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:305)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:272)
at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:623)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:181)
at oracle.jdbc.driver.T4CPreparedStatement.execute_for_rows(T4CPreparedStatement.java:543)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1028)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:2888)
at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:2960)
at org.hibernate.hql.ast.UpdateStatementExecutor.execute(UpdateStatementExecutor.java:67)
at org.hibernate.hql.ast.QueryTranslatorImpl.executeUpdate(QueryTranslatorImpl.java:292)
at org.hibernate.impl.SessionImpl.executeUpdate(SessionImpl.java:815)
at org.hibernate.impl.QueryImpl.executeUpdate(QueryImpl.java:89)
at com.wotif.jaguar.util.HibernateTestUtil.deleteAll(HibernateTestUtil.java:307)
at com.wotif.jaguar.util.HibernateTestUtil.deleteAll(HibernateTestUtil.java:251)
at com.wotif.jaguar.hibernate.HibernateConfigTest.testDeleteAll(HibernateConfigTest.java:160)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at junit.framework.TestCase.runTest(TestCase.java:154)
at junit.framework.TestCase.runBare(TestCase.java:127)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:118)
at junit.framework.TestSuite.runTest(TestSuite.java:208)
at junit.framework.TestSuite.run(TestSuite.java:203)
at junit.textui.TestRunner.doRun(TestRunner.java:116)
at com.intellij.rt.execution.junit2.IdeaJUnitAgent.doRun(IdeaJUnitAgent.java:57)
at junit.textui.TestRunner.start(TestRunner.java:172)
at com.intellij.rt.execution.junit.TextTestRunner2.startRunnerWithArgs(TextTestRunner2.java:23)
at com.intellij.rt.execution.junit2.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:97)
at com.intellij.rt.execution.junit2.JUnitStarter.main(JUnitStarter.java:31)
19/04/2005 13:57:03 org.hibernate.util.JDBCExceptionReporter logExceptions WARNING: SQL Error: 2292, SQLState: 23000
19/04/2005 13:57:03 org.hibernate.util.JDBCExceptionReporter logExceptions SEVERE: ORA-02292: integrity constraint (RWATKINS.OIAN_ORDER_INVOICE_ID_FK) violated - child record found

----

Example mapping (many-to-many):

RoomType <--> RoomFacility

HQL: delete from RoomType

Error:

19/04/2005 14:16:40 org.hibernate.hql.ast.QueryTranslatorImpl parse FINE: parse() - HQL: delete from com.wotif.jaguar.domain.property.RoomType
19/04/2005 14:16:40 org.hibernate.hql.ast.QueryTranslatorImpl showHqlAst FINE: --- HQL AST ---
 \-[DELETE] 'delete'
    \-[FROM] 'implied-from-so-i-can-use-the-fromClause-rule-during-analysis-phase'
       \-[DOT] '.'
          +-[DOT] '.'
          | +-[DOT] '.'
          | | +-[DOT] '.'
          | | | +-[DOT] '.'
          | | | | +-[IDENT] 'com'
          | | | | \-[IDENT] 'wotif'
          | | | \-[IDENT] 'jaguar'
          | | \-[IDENT] 'domain'
          | \-[IDENT] 'property'
          \-[IDENT] 'RoomType'

19/04/2005 14:16:40 org.hibernate.hql.ast.ErrorCounter throwQueryException FINE: throwQueryException() : no errors
19/04/2005 14:16:40 org.hibernate.hql.ast.FromElement doInitialize FINE: FromClause{level=1} : com.wotif.jaguar.domain.property.RoomType (no alias) -> roomtype0_
19/04/2005 14:16:40 org.hibernate.hql.ast.QueryTranslatorImpl analyze FINE: --- SQL AST ---
 \-[DELETE] QueryNode: 'delete' querySpaces (PROP_MASTER_ROOM_TYPE)
    \-[FROM] FromClause: 'implied-from-so-i-can-use-the-fromClause-rule-during-analysis-phase' FromClause{level=1, fromElementCounter=1, fromElements=1, fromElementByClassAlias=[], fromElementByTableAlias=[roomtype0_], fromElementsByPath=[], collectionJoinFromElementsByPath=[], impliedElements=[]}
       \-[FROM_FRAGMENT] FromElement: 'PROP_MASTER_ROOM_TYPE' FromElement{explicit,not a collection join,classAlias=null,role=null,tableName=PROP_MASTER_ROOM_TYPE,tableAlias=roomtype0_,colums={,className=com.wotif.jaguar.domain.property.RoomType}}

19/04/2005 14:16:40 org.hibernate.hql.ast.ErrorCounter throwQueryException FINE: throwQueryException() : no errors
19/04/2005 14:16:40 org.hibernate.hql.ast.QueryTranslatorImpl generate FINE: HQL: delete from com.wotif.jaguar.domain.property.RoomType
19/04/2005 14:16:40 org.hibernate.hql.ast.QueryTranslatorImpl generate FINE: SQL: delete from PROP_MASTER_ROOM_TYPE
19/04/2005 14:16:40 org.hibernate.hql.ast.ErrorCounter throwQueryException FINE: throwQueryException() : no errors
19/04/2005 14:16:40 org.hibernate.jdbc.AbstractBatcher logOpenPreparedStatement FINE: about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
19/04/2005 14:16:40 org.hibernate.jdbc.AbstractBatcher log FINE: delete from PROP_MASTER_ROOM_TYPE
19/04/2005 14:16:40 org.hibernate.jdbc.AbstractBatcher logClosePreparedStatement FINE: about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
19/04/2005 14:16:40 org.hibernate.util.JDBCExceptionReporter logExceptions FINE: could not execute update query [delete from PROP_MASTER_ROOM_TYPE]
java.sql.SQLException: ORA-02292: integrity constraint (RWATKINS.PMRTF_PROP_ROOM_TYPE_ID_FK) violated - child record found

at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:125)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:305)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:272)
at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:623)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:181)
at oracle.jdbc.driver.T4CPreparedStatement.execute_for_rows(T4CPreparedStatement.java:543)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1028)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:2888)
at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:2960)
at org.hibernate.hql.ast.UpdateStatementExecutor.execute(UpdateStatementExecutor.java:67)
at org.hibernate.hql.ast.QueryTranslatorImpl.executeUpdate(QueryTranslatorImpl.java:292)
at org.hibernate.impl.SessionImpl.executeUpdate(SessionImpl.java:815)
at org.hibernate.impl.QueryImpl.executeUpdate(QueryImpl.java:89)
...



Obvious workaround would be to use deletion cascade in the database, but this worked in Hibernate 2.1.6 without those triggers.

 All   Comments   Work Log   Change History   FishEye      Sort Order: Ascending order - Click to sort in descending order
Gavin King - 18/Apr/05 11:33 PM
Correct, you should not expect this; check the EJB3 spec.

Robert Watkins - 19/Apr/05 12:54 AM
What, I shouldn't expect Hibernate to delete entries from join tables automatically when it makes them automatically?

The one-to-many one I can understand (somewhat), but the many-to-many isn't understandable by any means.

The only way I have of deleting the entries at the moment would be to write raw SQL to delete the entries from the join table - OR to have a database trigger do it for me.

At the very least, this needs a mention in the migration guide, because it's a major change in behaviour between Hibernate 2 and 3.

(Side note: I'm getting mildly annoyed at being told I need to read the EJB 3 spec for Hibernate; these should be covered in the Hibernate manual. I'm not using Hibernate as part of a container managed persistence mechanism, after all; the limitations of EJB 3 aren't on top of my mind when I'm trying to write Hibernate code)

Gavin King - 19/Apr/05 09:32 AM
You are using free software. You have no right to get annoyed about *anything*. End Of Conversation.

Max Rydahl Andersen - 19/Apr/05 09:44 AM
you are using a new feature in H3, not something that was available in H2.

Read the hibernate docs which refer to the exact semantics.

Max Rydahl Andersen - 19/Apr/05 09:45 AM
namely:

/**
* Execute the update or delete statement.
* </p>
* The semantics are compliant with the ejb3 Query.executeUpdate()
* method.
*
public int executeUpdate() throws HibernateException;

Robert Watkins - 19/Apr/05 04:22 PM
Max:

H2 version: session.delete("Foo")

H3 version: session.createQuery("delete from Foo").executeUpdate();

The migration guide flat out states that the replacement for session.delete() is the DELETE query. Finding serious differences in behaviour is an issue. The issue is either with the code or the guide. As the difference is intentional, the issue is with the guide.

Guys, seriously: you can take this feedback or ignore it. It's your choice. And Gavin: the comment that I'm getting annoyed about all the comments to EJB3 is feedback - it's _not_ very clear in the migration guide or the manual that understanding how Hibernate 3 works requires an intimate knowledge of the relevant section of the EJB 3 spec. It's not even clear what the section actually is. Is it all of the persistence spec? Just Section 3.11 of the second public _draft_? I'm not criticising the decision to bind Hibernate to EJB3; I'm saying that the extent of the bind is not very clear.

Steve Ebersole - 19/Apr/05 04:32 PM
I think you are a little confused. In Hibernate2 you had either a choice of:
1) session.delete(anInstanceofSomeEntity)
2) session.delete("select e from MyEntity as e")
(btw, session.delete("Foo") is not valid in H2...)

option #2 actually does a select query, pulls all that stuff into memory, and then iterates the result calling #1 for each entity.

The delete query stuff in Hibernate3 was *unconditionally* added in order to support ejb3. In difference to Hibernate2, it *does not* pull this shit in memory. So how do you propose that we cascade these deletes?

Instead of bitching (incorrectly) "oh this changed", why not constructively suggest a solution?

Robert Watkins - 19/Apr/05 04:37 PM
Okay, I've been busy reading the EJB 3 persistence spec. It doesn't say that anywhere that deleting objects in a many-to-many relationship fails to clean up join tables or relationships.

The exact statement (in 3.11) is "Bulk update and delete operations apply to entities of a single entity bean class (together with its subclasses,
if any)."

Nowhere does it say that you have to go through the system and detangle all of the relationships the objects particpate in first.

In 2.3.2, It says: If [entity] X is a managed entity, the remove operation causes it to transition to the removed state. The remove operation is cascaded to entities referenced by X, if the relationships from X to these other entities is annotated with the cascade=REMOVE or cascade=ALL annotation member value.

Okay, it doesn't say that this behaviour also applies to delete queries, but it doesn't say it shouldn't. This still smells like a bug to me.

Robert Watkins - 19/Apr/05 04:48 PM
Steve, the example I gave was an example of 2, from memory. I meant session.delete("from Foo"), which is the _correct_ version of your example (the "select e from MyEntity as e" is actually the Hibernate _1_ syntax, I believe).

I know what the behaviour of 2 is. I know Hibernate 3, with the bulk queries, does it differently.

I'm not proposing that you pull the shit into memory, and I'm not proposing a solution (though seeing as how you have the metadata in place, I can't see why appropriate delete queries, using the criteria as a subselect, can't work).

What I'm proposing is that the migration guide make it a bit clearer that migrating from session.delete("from Foo") is not a simple case of changing the call and that there are behavioural differences above and beyond the performance improvement. In particular, the fact that cascades don't work (and that you should use database cascades instead) should be made more explict, _especially_ as the EJB 3 documentation doesn't imply anything of the sort.

Steve Ebersole - 19/Apr/05 04:59 PM
<quote>
Okay, it doesn't say that this behaviour also applies to delete queries, but it doesn't say it shouldn't.
</quote>

Axctually it does (implicitly). When you quote 2.3.2, you mention "transition to the removed state"; that by definition indicates that the entity is available in memory.

Gavin King - 19/Apr/05 05:04 PM
Given that I was deeply involved in writing that section of the spec, I am able to confirm that our understanding of the spec is the intended one.

Steve Ebersole - 19/Apr/05 05:05 PM
<quote>
Steve, the example I gave was an example of 2, from memory. I meant session.delete("from Foo"), which is the _correct_ version of your example (the "select e from MyEntity as e" is actually the Hibernate _1_ syntax, I believe).
</quote>
Oh now you want to argue with me about the syntax of HQL? You realize that "from MyEntity" is just the short-hand of "select e from MyEntity as e", right? The old session.delete(String queryString) took a *select query*.

Robert Watkins - 19/Apr/05 06:05 PM
Suffice to say: this could be a bit clearer in the migration doco, guys. I'm sure your understanding is correct, Gavin, and I'm not disputing it. I'm saying that _my_ understanding, taken _from_ the doco, doesn't cover this. So can the doco be made clearer?

Emmanuel Bernard - 19/Apr/05 06:25 PM
I've added a 1 line warning in the migration guide.

Robert Watkins - 19/Apr/05 07:14 PM
Danke. I'll shut up now.

Christian Bauer - 20/Apr/05 05:16 AM
Thanks!

Andrew Hurst - 20/Apr/05 04:52 PM
I realize this is a closed issue, but hopefully I can get a clarification that will probably help other people with the same question as mine.

It appears to me, after reading this bug report (I have the same problem as the original submittor, by the way), that the following is the case:

Suppose that we have a many-to-many mapping between Employees and Departments. An Employee can belong to many Departments, and a Department can contain many Employees. Both are managed with Sets. Assume Department->Employee is the main association, i.e. inverse="true" is defined in the Employee set in the Department mapping file.

From what I read above, it appears that this code used to work:
// Assume d is a Department, e is an Employee, initialized elsewhere.
d.getEmployees().remove( e );
e.getDepartments().remove( d );
session.save( d );
// The association in the join table between employee and department
// has been removed by this point, because of cascading

But that same code, in hibernate 3, will not work, as it will not remove the association between the two objects that is in the join table. Instead, we need to craft a query to delete it by hand. Something more along the lines of:

// Assume d is a Department, e is an Employee, initialized elsewhere.
d.getEmployees().remove( e );
e.getDepartments().remove( d );
session.save( d );
session.save( e );
// run an HQL query to delete the association here, as well

------
Is this really the case? Do I understand what was written above correctly?

If this is the case, why would we have to drop down to HQL to manage something that other than this case, is fully managed by Hibernate?

Christian Bauer - 20/Apr/05 04:55 PM
Use the forum!