I am using Hibernate 3.2.CR2 and EntityManager/Annotations 3.2.CR1 though this also occurs with the latest svn version as of today.
I have a parent/child relationship defined as follows.
Code:
@Entity
public class Library {
protected Integer id;
protected Set<Book> books = new HashSet<Book>();
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
void setId(Integer id) {
this.id = id;
}
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "libraryId", nullable = false)
public Set<Book> getBooks() {
return books;
}
public void setBooks(Set<Book> items) {
this.books = items;
}
}
@Entity
public class Book {
protected Integer id;
protected String title;
protected Library library;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@ManyToOne
@JoinColumn(name = "libraryId", insertable = false, updatable = false, nullable = false)
public Library getLibrary() {
return library;
}
void setLibrary(Library library) {
this.library = library;
}
}
When I do the following inside of a transaction everything works fine, however this code generates a ClassCastException.
Code:
EntityManager em = Persistence.createEntityManagerFactory("manager").createEntityManager();
Library library = new Library();
Book book = new Book();
book.setTitle("New Book");
book.setLibrary(library);
library.getBooks().add(book);
em.persist();
em.getTransaction().begin();
em.getTransaction().commit();
Exception:
Caused by: java.lang.ClassCastException: org.hibernate.action.DelayedPostInsertIdentifier
at org.hibernate.type.IntegerType.set(IntegerType.java:41)
at org.hibernate.type.NullableType.nullSafeSet(NullableType.java:83)
at org.hibernate.type.NullableType.nullSafeSet(NullableType.java:60)
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:1910)
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:1887)
at org.hibernate.persister.entity.AbstractEntityPersister$1.bindValues(AbstractEntityPersister.java:2038)
at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:32)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2044)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2481)
at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:47)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:248)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:232)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:139)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:297)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:993)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:340)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:53)
In looking at the source the DelayedPostInsertIdentifier is used as a placeholder for the entity identifier when a transaction isn't present.
When I do a persist up above three EntityIdentityInsertActions are queued up to be executed when a transaction comes online. The status of the object is also recorded in the state array. The Library id is not available at this point and a DelayedPostInsertIdentifier object is used for the key in the Book state arrays.
When the library object is persisted it replaces it's delayed EntityKey in the persistence context with the real one, however the state arrays of the Book objects are never updated with the real key. This causes a ClassCastException when the IntegerType class tries to cast the DelayedPostInsertIdentifier to an Integer to put it into the prepared statement.
Somehow the state array in the insert actions need to be updated when the parent actually gets a real key. My first thought was to change DelayedPostInsertIdentifier to keep a reference to the entity it is being used for and then when the value is needed it can be looked up from the persisence context. Right now I am doing this lookup in NullableType.nullSafeSet which works for us but probably doesn't cover all cases.
Should I enter this issues into JIRA and is there a better place to do the lookup?