[hibernate-issues] [Hibernate-JIRA] Updated: (HHH-4967) Mapping a class more than once using entity names causes NullPointerException in org.hibernate.ejb.event.EJB3FlushEntityEventListener

Gunnar von der Beck (JIRA) noreply at atlassian.com
Thu Mar 4 10:51:47 EST 2010


     [ http://opensource.atlassian.com/projects/hibernate/browse/HHH-4967?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Gunnar von der Beck updated HHH-4967:
-------------------------------------

    Attachment: hibernate-HHH-4967.zip

Attached Test Case.

Hint: The error occurs only if you have an entity listener attached.
Added corrected EJB3FlushEntityEventListener to test case as well.

> Mapping a class more than once using entity names causes NullPointerException in org.hibernate.ejb.event.EJB3FlushEntityEventListener
> -------------------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HHH-4967
>                 URL: http://opensource.atlassian.com/projects/hibernate/browse/HHH-4967
>             Project: Hibernate Core
>          Issue Type: Bug
>          Components: entity-manager
>    Affects Versions: 3.3.2
>         Environment: Hibernate Entity Manager 3.3.2.GA, JBoss EAP 4.3, Oracle 10
>            Reporter: Gunnar von der Beck
>         Attachments: hibernate-HHH-4967.zip
>
>   Original Estimate: 30 minutes
>  Remaining Estimate: 30 minutes
>
> Map a class more than once and use *entity names*, e.g. as follows:
> {code: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>
>     <!-- Mapping for multilingual texts up to 4000 characters -->
>     <class dynamic-insert="true" dynamic-update="true" optimistic-lock="version"
>            name="mypackage.MultilingualText" table="multilingualtext"
>            entity-name="MultilingualShort">
>         ...
>         <!-- the short text -->
>         <property name="text" type="java.lang.String">
>             <column name="text" sql-type="varchar2(4000)" />
>         </property>
>            
>     </class>
>     <!-- Mapping for multilingual texts holding more than 4000 characters -->
>     <class dynamic-insert="true" dynamic-update="true" optimistic-lock="version" 
>            name="mypackage.MultilingualText" table="multilinguallongtext" 
>            entity-name="MultilingualLong">
>            
>         ...
>         <!-- the long text -->
>         <property name="text" type="java.lang.String">
>             <column name="text" sql-type="clob" />
>         </property>
>     </class>
>     
> </hibernate-mapping>
> {code}
> Use this entity names within a list / set / map /... as follows:
> {code:xml}
> <class name="myClass" ...>
>     ...
>     <!-- multilingual text field for up to 4000 characters -->
>     <set access="field" cascade="all,delete-orphan" fetch="join" lazy="false"
>          name="description" table="myclassdescription">
>         <key>
>             <column name="myclass_id" sql-type="varchar2(36)" />
>         </key>
>         <many-to-many unique="true" class="mypackage.MultilingualText" 
>             entity-name="MultilingualShort">
>             <column name="text_id" sql-type="varchar2(36)"/>
>         </many-to-many>
>     </set>
>     ...
> </class>
> {code}
> When modifying such a list / set / map and calling {{entityManager.merge(<myClass>)}} you get the following exception on {{entityManager.flush()}}:
> {noformat}
> java.lang.NullPointerException
> 	at org.hibernate.ejb.event.EJB3FlushEntityEventListener.copyState(EJB3FlushEntityEventListener.java:53)
> 	at org.hibernate.ejb.event.EJB3FlushEntityEventListener.invokeInterceptor(EJB3FlushEntityEventListener.java:42)
> 	at org.hibernate.event.def.DefaultFlushEntityEventListener.handleInterception(DefaultFlushEntityEventListener.java:308)
> 	at org.hibernate.event.def.DefaultFlushEntityEventListener.scheduleUpdate(DefaultFlushEntityEventListener.java:248)
> 	at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:128)
> 	at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:196)
> 	at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:76)
> 	at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:26)
> 	at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
> 	at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:304)
> {noformat}
> *Solution:* correct the {{org.hibernate.ejb.event.EJB3FlushEntityEventListener}} as follows:
> {code:title=EJB3FlushEntityEventListener.java|borderStyle=solid}
> /**
>  * Overrides the LifeCycle OnSave call to call the PreUpdate operation.
>  * <p>
>  * Error corrected version derived from {@code hibernate-entitymanager-3.3.2GA}.
>  * See method {@code copyState}: considered entity name!
>  */
> public class EJB3FlushEntityEventListener extends DefaultFlushEntityEventListener implements CallbackHandlerConsumer {
> 	/**
> 	 * This class serial version UID.
> 	 */
> 	private static final long serialVersionUID = 1L;
> 	private EntityCallbackHandler callbackHandler;
> 	public void setCallbackHandler(EntityCallbackHandler callbackHandler) {
> 		this.callbackHandler = callbackHandler;
> 	}
> 	public EJB3FlushEntityEventListener() {
> 		super();
> 	}
> 	public EJB3FlushEntityEventListener(EntityCallbackHandler callbackHandler) {
> 		super();
> 		this.callbackHandler = callbackHandler;
> 	}
> 	@Override
> 	protected boolean invokeInterceptor(
> 			SessionImplementor session, Object entity, EntityEntry entry, Object[] values, EntityPersister persister
> 	) {
> 		boolean isDirty = false;
> 		if ( entry.getStatus() != Status.DELETED ) {
> 			if ( callbackHandler.preUpdate( entity ) ) {
> 				isDirty = copyState( entity, entry.getEntityName() ,persister.getPropertyTypes(), values, session.getFactory() );
> 			}
> 		}
> 		return super.invokeInterceptor( session, entity, entry, values, persister ) || isDirty;
> 	}
> 	/**
> 	 * copy the entity state into the state array and return true if the state has changed
> 	 */	
> 	// modified parameter list: ++entityName
> 	private boolean copyState(Object entity, String entityName, Type[] types, Object[] state, SessionFactory sf) {
> 		// modified: consider entity names when resolving metadata!
> 		ClassMetadata metadata = 
> 			(entityName != null ? sf.getClassMetadata(entityName) : sf.getClassMetadata(entity.getClass()));
> 		Object[] newState = metadata.getPropertyValues( entity, EntityMode.POJO );
> 		int size = newState.length;
> 		boolean isDirty = false;
> 		for ( int index = 0; index < size ; index++ ) {
> 			if ( !types[index].isEqual( state[index], newState[index], EntityMode.POJO ) ) {
> 				isDirty = true;
> 				state[index] = newState[index];
> 			}
> 		}
> 		return isDirty;
> 	}
> }
> {code}
> The significant change of the code is to consider the entity name when looking up {{ClassMetadata}} in method {{copyState()}}:
> {code}
>     ClassMetadata metadata = (entityName != null ? sf.getClassMetadata(entityName) : sf.getClassMetadata(entity.getClass()));
> {code}

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://opensource.atlassian.com/projects/hibernate/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

        


More information about the hibernate-issues mailing list