| 
					
						 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" 
					
  
						
					 |