Hey there, everyone. I'm having an interesting problem with a child collection, and I was hoping someone here would have some input on what I'm doing wrong. I've simplified down to two classes that can reproduce the behavior.
In short, I have a Thing. The Thing has a collection of past Status, each with a statusName, startDate and endDate. The
current Status of the Thing is represented by the Status without an endDate.
The problem occurs when I load an existing Thing and try to change its current status. I take the current Status object, set an end date on it, and add a new Status object without an end date. When I persist, Hibernate appears to attempt to insert the new Status before updating the end date on the old Status, which violates the unique constraint that says you can only have one Status with a null end date.
Any suggestions on what I can do to make Hibernate understand that it needs to update the existing records before inserting new ones would be greatly appreciated. :)
In the database, I have the following:
THING table:
Code:
CREATE TABLE THING
(
THING_ID NUMBER(15)
);
ALTER TABLE THING ADD
(
PRIMARY KEY (THING_ID)
);
STATUS table:
Code:
CREATE TABLE STATUS
(
STATUS_ID NUMBER(15),
THING_ID NUMBER(15) NOT NULL,
STATUSNAME VARCHAR2(15 BYTE) NOT NULL,
STARTDATE DATE NOT NULL,
ENDDATE DATE
);
ALTER TABLE STATUS ADD
(
PRIMARY KEY (STATUS_ID)
);
ALTER TABLE STATUS ADD
(
CONSTRAINT STATUS_UK1
UNIQUE (THING_ID, STARTDATE, STATUSNAME)
);
ALTER TABLE STATUS ADD
(
CONSTRAINT STATUS_END_UK
UNIQUE (THING_ID, ENDDATE)
);
ALTER TABLE STATUS ADD
(
FOREIGN KEY (THING_ID) REFERENCES THING (THING_ID)
);
Mapping documents:
Thing.hbm.xml:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="test">
<class name="Thing" table="Thing">
<id name="id" column="thing_id" type="java.lang.Long">
<generator class="org.hibernate.id.SequenceGenerator">
<param name="sequence">thing_id_seq</param>
</generator>
</id>
<set name="statusHistory" cascade="all, delete-orphan">
<key column="thing_id" not-null="true"/>
<one-to-many class="Status"/>
</set>
</class>
</hibernate-mapping>
Status.hbm.xml:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="test">
<class name="Status" table="Status">
<id name="id" column="status_id" type="java.lang.Long">
<generator class="org.hibernate.id.SequenceGenerator">
<param name="sequence">status_id_seq</param>
</generator>
</id>
<many-to-one name="thing" class="Thing" column="thing_id" insert="false" update="false" not-null="true"/>
<property name="statusName" type="string"/>
<property name="startDate" type="java.util.Date"/>
<property name="endDate" type="java.util.Date"/>
</class>
</hibernate-mapping>
Here's the test code I'm running:
Code:
Session sess = PersistenceUtil.getSession("test");
Query q = sess.createQuery("FROM Thing as t where t.id = 1");
Thing thing = (Thing)q.uniqueResult();
thing.setStatus("Old");
Transaction tx = sess.beginTransaction();
sess.saveOrUpdate(thing);
tx.commit();
And the relevant method in Thing.java:
Code:
public void setStatus(String statusName)
{
Status oldStat = null;
boolean found = false;
Iterator i = statusHistory.iterator();
while (i.hasNext() && !found)
{
oldStat = (Status)i.next();
if (oldStat.getEndDate() == null)
found = true;
}
oldStat.setEndDate(new Date());
Status newStat = new Status(statusName);
newStat.setThing(this);
statusHistory.add(newStat);
}
And, last but not least, here is the generated SQL and error that results:
Code:
Hibernate: select thing0_.thing_id as thing1_ from apps.Thing
thing0_ where thing0_.thing_id=1
Hibernate: select statushist0_.thing_id as thing2_1_, statushist0_.status_id
as status1_1_, statushist0_.status_id as status1_0_,
statushist0_.thing_id as thing2_0_0_, statushist0_.statusName as
statusName0_0_, statushist0_.startDate as startDate0_0_,
statushist0_.endDate as endDate0_0_ from
apps.Status statushist0_ where statushist0_.thing_id=?
Hibernate: select apps.status_id_seq.nextval from dual
Hibernate: insert into apps.Status (statusName, startDate, endDate,
thing_id, status_id) values (?, ?, ?, ?, ?)
WARN [org.hibernate.util.JDBCExceptionReporter] SQL Error: 1,
SQLState: 23000
ERROR [org.hibernate.util.JDBCExceptionReporter] ORA-00001: unique
constraint (APPS.STATUS_END_UK) violated
WARN [org.hibernate.util.JDBCExceptionReporter] SQL Error: 1,
SQLState: 23000
ERROR [org.hibernate.util.JDBCExceptionReporter] ORA-00001: unique
constraint (APPS.STATUS_END_UK) violated