[hibernate-issues] [Hibernate-JIRA] Created: (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
Wed Mar 3 03:38:51 EST 2010


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


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