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....
-
For more information on JIRA, see:
http://www.atlassian.com/software/jira