There appears to be a bug in DefaultFlushEntityEventListener
when checking for changes in immutable natural ids.
Hibernate version:
We are using Hibernate 3.1.3 and Spring 1.2.6
Mapping documents:
Site.hbm.xml:
------------
<?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>
<class name="com.learjet.aces.domain.site.Site" table="A4_SITE">
<id name="entityId" column="ID">
<generator class="native"/>
</id>
<natural-id mutable="false">
<property name="name" type="string"
column="SITE_NAME" >
</property>
</natural-id>
<version name="persistedVersion" column="PERSISTED_VERSION" />
<component name="auditInfo" class="com.learjet.aces.domain.AuditInfo">
<property name="active" column="IS_ACTIVE" type="boolean"
insert="true" update="true" not-null="true" />
<property name="createdBy" column="CREATED_BY" type="java.lang.String"
insert="true" update="true" not-null="true" />
<property name="creationDate" column="CREATION_DATE" type="java.util.Date"
insert="true" update="true" not-null="true" />
<property name="lastUpdatedBy" column="LAST_UPDATED_BY" type="java.lang.String"
insert="true" update="true" not-null="true" />
<property name="lastUpdatedDate" column="LAST_UPDATED" type="java.util.Date"
insert="true" update="true" not-null="true" />
</component>
<set name="holidays"
lazy="true"
table="A4_HOLIDAY"
order-by="HOLIDAY_DATE asc"
cascade="all"
>
<key column="SITE_ID"/>
<composite-element class="com.learjet.aces.domain.site.Holiday">
<property name="holidayDate" column="HOLIDAY_DATE" not-null="true"/>
</composite-element>
</set>
</class>
</hibernate-mapping>
Code between sessionFactory.openSession() and session.close():
TestCase
--------
public void testClearHoliday() throws Exception {
Site site = Site.getDAO().findSiteByName("Wichita"); // this site is in the db
if (site.getHolidays().size()==0) { // there are no holidays yet
Holiday h = new Holiday();
h.setHolidayDate(Holiday.convertYYYYMMDD("20060101"));
site.addHoliday(h);
Exception here ---> site.saveOrUpdate(); // calls SiteDAO
Site dbSite = Site.getDAO().findSiteByName("Wichita");
assertEquals(1,dbSite.getHolidays().size());
}
BaseDAO (invoked from SiteDAO)
-------
public void update(Persistable entity) throws DataAccessException {
this.hibernateTemplate.update(entity); // disappears into Spring 1.2.6, Hibernate 3.1.3
}
Full stack trace of any exception that occurs:
DefaultFlushEntityEventListener.checkNaturalId(EntityPersister, Serializable, Object[], Object[], EntityMode, SessionImplementor) line: 76
DefaultFlushEntityEventListener.getValues(Object, EntityEntry, EntityMode, boolean, SessionImplementor) line: 155
DefaultFlushEntityEventListener.onFlushEntity(FlushEntityEvent) line: 106
DefaultFlushEventListener(AbstractFlushingEventListener).flushEntities(FlushEvent) line: 195
DefaultFlushEventListener(AbstractFlushingEventListener).flushEverythingToExecutions(FlushEvent) line: 76
DefaultFlushEventListener.onFlush(FlushEvent) line: 26
SessionImpl.flush() line: 985
HibernateTemplate(HibernateAccessor).flushIfNecessary(Session, boolean) line: 394
HibernateTemplate.execute(HibernateCallback, boolean) line: 366
HibernateTemplate.update(Object, LockMode) line: 655
HibernateTemplate.update(Object) line: 651
SiteDAO(BaseDAO).update(Persistable) line: 186
SiteDAO(BaseDAO).saveOrUpdate(Persistable) line: 156
Site.saveOrUpdate() line: 115
SyncHolidayTest.testClearHoliday() line: 44
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: not available
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: not available
Method.invoke(Object, Object...) line: not available
SyncHolidayTest(TestCase).runTest() line: 154
SyncHolidayTest(TestCase).runBare() line: 127
TestResult$1.protect() line: 106
TestResult.runProtected(Test, Protectable) line: 124
TestResult.run(TestCase) line: 109
SyncHolidayTest(TestCase).run(TestResult) line: 118
RemoteTestRunner.runTests(String[], String) line: 478
RemoteTestRunner.run() line: 344
RemoteTestRunner.main(String[]) line: 196
Name and version of the database you are using:
Oracle 8.1.7 (not a problem here)
The generated SQL (show_sql=true):
Last SQL call came from
org.hibernate.event.def.DefaultFlushEntityEventListener line 71:
loaded = session.getPersistenceContext().getNaturalIdSnapshot( identifier, persister );
Hibernate: select site_.SITE_NAME as SITE3_0_ from A4_SITE site_ where site_.ID=?
It appears that the NaturalIdSnapshot only looks at the properties contained within the
<natural-id> tag, and in this case it will always be size 1.
Debug level Hibernate log excerpt:
n/a
Location of Hibernate bug
DefaultFlushEntityEventListener
-------------------------------
//$Id: DefaultFlushEntityEventListener.java 8751 2005-12-05 18:45:52Z steveebersole $
package org.hibernate.event.def;
...
line 62:
private void checkNaturalId(
EntityPersister persister,
Serializable identifier,
Object[] current,
Object[] loaded,
EntityMode entityMode,
SessionImplementor session) {
if ( persister.hasNaturalIdentifier() ) {
if ( loaded == null ) {
loaded = session.getPersistenceContext().getNaturalIdSnapshot( identifier, persister );
}
Type[] types = persister.getPropertyTypes();
int[] props = persister.getNaturalIdentifierProperties();
boolean[] updateable = persister.getPropertyUpdateability();
for ( int i=0; i<props.length; i++ ) {
int prop = props[i];
if ( !updateable[prop] ) {
bug here ---> if ( !types[prop].isEqual( current[prop], loaded[prop], entityMode ) ) {
throw new HibernateException(
"immutable natural identifier of an instance of " +
persister.getEntityName() +
" was altered"
);
}
}
}
}
}
When i==0, prop=1, updateable[1]=false, types[1]=String, current[1]="Wichita"
but loaded[1] is out of bounds
Should be loaded[i] instead, which gives loaded[0]="Wichita"
Values from Eclipse debugger:
----------------------------
updateable.size()=4, [0]=true, [1]=false, [2]=true, [3]=false
props.size()=1, props[0]=1
types.size()=4, [0]=Integer,[1]=String,[2]=Component,[3]=OrderedSet
current.size()=4, [0]=0, [1]="Wichita", [2]=AuditInfo, [3]=PersistentSet
loaded.size()=1, [0]="Wichita"
entityMode="pojo"
|