I have extracted the following example from a problem we're having on a software in production. I didnt find another post or an entry in JIRA for it ; if there is one, or if it is an error from our side (we're quite new to Hibernate), my apologize.
To sum it up :
A class (Collec in the example) has 2 lists (subClass1 and subClass2), each of instances of a subclass (resp. SubClass1 and SubClass2) of the same superclass (SuperClass).
The SuperClass/SubClass1/SubClass2 is mapped as table-per-hierarchy.
Note we use the where= attribute for the mapping of the lists.
A data corruption occurs when we remove an element (subClass1[0] in the example) from one of the lists : it also removes the element of the other list having the same index (if there is one ; subClass2[0]).
It's because the sql generated does a :
Code:
update SuperClass set Collec_id=null, index_=null where Collec_id=? and index_=?
which applies to both SubClass1 and SubClass2 entries.
It doesnt use the where= of the list. Or the id of the SuperClass table as in the delete (if we use cascase=all-delete-orphan) which follows that update and is correct :
Code:
delete from SuperClass where SuperClass_id=?
The data corruption is even worse in the following example, since it removes subClass2[0] and subClass2[1].
Is it a bug, and is there a way to bypass it ? Best would be without having :
- to change the code (like using only 1 list in Collec and doing a filter in the code),
- to change the schema (like using table-per-subclass),
- to use a set : order is important, so we cant use set (and composite-index, if it would help),
since we have that problem in an project already in production (well atm it's rather stuck than in production :p).
The first 2 being the 2 working answers other than the "where=" we use to the situation described in "My class has two one-to-many associations to different subclasses of the same root class, but Hibernate ignores the actual concrete class when loading the associations." of the FAQ
http://www.hibernate.org/117.html#A15.
The 2nd answer in the FAQ ("map each association to a different column of the Transaction table") looks like an error to me (cant specify column with one-to-many, right ?).
Follows the example showing the problem.
TIA !
Hibernate version: 2.1.6 (also happens in 2.1.4)
Mapping documents:Code:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="test.Collec" table="Collec">
<id
column="Collec_id"
name="id"
type="int"
>
<meta attribute="scope-set">protected</meta>
<generator class="native" />
</id>
<list name="SubClass1" cascade="all-delete-orphan" where="subType = '1'">
<key column="Collec_id"/>
<index column="index_"/>
<one-to-many class="test.SubClass1"/>
</list>
<list name="SubClass2" cascade="all-delete-orphan" where="subType = '2'">
<key column="Collec_id"/>
<index column="index_"/>
<one-to-many class="test.SubClass2"/>
</list>
</class>
<class name="test.SuperClass" table="SuperClass">
<id
column="SuperClass_id"
name="id"
type="int"
>
<meta attribute="scope-set">protected</meta>
<generator class="native" />
</id>
<discriminator
column="subType"
type="string"
length="1"
/>
<subclass name="test.SubClass1" discriminator-value="1">
</subclass>
<subclass name="test.SubClass2" discriminator-value="2">
</subclass>
</class>
</hibernate-mapping>
Code between sessionFactory.openSession() and session.close():Code:
s = sf.openSession();
t = s.beginTransaction();
Collec collec = new Collec();
collec.setSubClass1(new ArrayList());
collec.getSubClass1().add(new SubClass1());
collec.getSubClass1().add(new SubClass1());
collec.setSubClass2(new ArrayList());
collec.getSubClass2().add(new SubClass2());
collec.getSubClass2().add(new SubClass2());
s.saveOrUpdate(collec);
t.commit();
// database fine :
// Collec :
// +-----------+
// | Collec_id |
// +-----------+
// | 1 |
// +-----------+
// SuperClass :
//
// +---------------+---------+-----------+--------+
// | SuperClass_id | subType | Collec_id | index_ |
// +---------------+---------+-----------+--------+
// | 1 | 1 | 1 | 0 |
// | 2 | 1 | 1 | 1 |
// | 3 | 2 | 1 | 0 |
// | 4 | 2 | 1 | 1 |
// +---------------+---------+-----------+--------+
t = s.beginTransaction();
collec.getSubClass1().remove(0);
s.saveOrUpdate(collec);
t.commit();
// table Superclass corrupted :
//
// +---------------+---------+-----------+--------+
// | SuperClass_id | subType | Collec_id | index_ |
// +---------------+---------+-----------+--------+
// | 2 | 1 | 1 | 0 |
// | 3 | 2 | [NULL] | [NULL] |
// | 4 | 2 | [NULL] | [NULL] |
// +---------------+---------+-----------+--------+
Full stack trace of any exception that occurs:No exception, only data corruption in database.
Name and version of the database you are using:MySql (4.0.20-standard ?), also happens on Oracle 9i
The generated SQL (show_sql=true):see debug below
Debug level Hibernate log excerpt:Code:
18:52:53,687 DEBUG SQL:226 - insert into Collec values ( )
18:52:53,718 DEBUG SQL:226 - insert into SuperClass (subType) values ('1')
18:52:53,734 DEBUG SQL:226 - insert into SuperClass (subType) values ('1')
18:52:53,734 DEBUG SQL:226 - insert into SuperClass (subType) values ('2')
18:52:53,734 DEBUG SQL:226 - insert into SuperClass (subType) values ('2')
18:52:53,750 DEBUG SQL:226 - update SuperClass set Collec_id=?, index_=? where SuperClass_id=?
18:52:53,750 DEBUG IntegerType:46 - binding '1' to parameter: 1
18:52:53,750 DEBUG IntegerType:46 - binding '1' to parameter: 3
18:52:53,765 DEBUG IntegerType:46 - binding '0' to parameter: 2
18:52:53,765 DEBUG SQL:226 - update SuperClass set Collec_id=?, index_=? where SuperClass_id=?
18:52:53,765 DEBUG IntegerType:46 - binding '1' to parameter: 1
18:52:53,765 DEBUG IntegerType:46 - binding '2' to parameter: 3
18:52:53,765 DEBUG IntegerType:46 - binding '1' to parameter: 2
18:52:53,765 DEBUG SQL:226 - update SuperClass set Collec_id=?, index_=? where SuperClass_id=?
18:52:53,765 DEBUG IntegerType:46 - binding '1' to parameter: 1
18:52:53,765 DEBUG IntegerType:46 - binding '3' to parameter: 3
18:52:53,765 DEBUG IntegerType:46 - binding '0' to parameter: 2
18:52:53,765 DEBUG SQL:226 - update SuperClass set Collec_id=?, index_=? where SuperClass_id=?
18:52:53,765 DEBUG IntegerType:46 - binding '1' to parameter: 1
18:52:53,765 DEBUG IntegerType:46 - binding '4' to parameter: 3
18:52:53,781 DEBUG IntegerType:46 - binding '1' to parameter: 2
Begin of sql corrupting
Code:
18:54:20,140 DEBUG SQL:226 - update SuperClass set Collec_id=null, index_=null where Collec_id=? and index_=?
18:54:20,140 DEBUG IntegerType:46 - binding '1' to parameter: 1
18:54:20,140 DEBUG IntegerType:46 - binding '1' to parameter: 2
18:54:20,140 DEBUG SQL:226 - update SuperClass set Collec_id=null, index_=null where Collec_id=? and index_=?
18:54:20,156 DEBUG IntegerType:46 - binding '1' to parameter: 1
18:54:20,156 DEBUG IntegerType:46 - binding '0' to parameter: 2
End of sql corrupting
Code:
18:54:20,156 DEBUG SQL:226 - update SuperClass set Collec_id=?, index_=? where SuperClass_id=?
18:54:20,156 DEBUG IntegerType:46 - binding '1' to parameter: 1
18:54:20,171 DEBUG IntegerType:46 - binding '2' to parameter: 3
18:54:20,171 DEBUG IntegerType:46 - binding '0' to parameter: 2
18:54:20,171 DEBUG SQL:226 - delete from SuperClass where SuperClass_id=?
18:54:20,187 DEBUG IntegerType:46 - binding '1' to parameter: 1