I'm trying to create a simple Parent/Child relationship with the JPA 2 API and Hibernate 3.6.7 or 4.0.0.CR4 as a provider, and I discovered some strange behaviors which can be reproduced on a simple setup.
In a nutshell I have two entities: Parent and Child both using embeded keys ParentKey and ChildKey. The definitions follow:
Code:
@Embeddable
public class ChildKey
implements Serializable {
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Integer number;
}
Code:
@Embeddable
public class ParentKey
implements Serializable {
@Column(nullable = false, updatable = false)
private String name;
}
Code:
@Entity
public class Child {
@EmbeddedId
private ChildKey key;
@Column
private String value;
}
Code:
@Entity
public class Parent {
@EmbeddedId
private ParentKey key;
@Column
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Child> children;
public void setChildren(List<Child> children) {
this.children = children;
}
}
The test consist in a first transaction creating a parent, add a couple of children, persist it in the database and a second retrieve it, remove one child while adding another one and merge it to the database:
Code:
@Transactional
public void populate() {
for (int i = 0; i < 2; i++) {
final Parent parent = new Parent("Parent-" + i);
List<Child> children = new ArrayList<Child>();
for (int j = 0; j < 2; j++) {
children.add(new Child(parent.getName(), j));
}
parent.setChildren(children);
em.persist(parent);
}
}
@Transactional
public void removeChild() {
final Parent parent = em.find(Parent.class, new ParentKey("Parent-0"));
List<Child> children = new ArrayList<Child>(parent.getChildren());
children.remove(0);
children.add(new Child(parent.getName(), 42));
parent.setChildren(children);
em.merge(parent);
}
As is, this does not work as I expect since it creates a relationship table. So I added a @JoinColumn to the children attribute in the Parent class.
Code:
@Column
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name="name")
private List<Child> children;
When running my test I notice that Hibernate seems to find out what should be done: creation of a new child and removal of another one. However it fails at runtime with the following exception:
Code:
[main] DEBUG org.hibernate.SQL - update Child set name=null where name=?
Hibernate: update Child set name=null where name=?
[main] DEBUG o.h.p.c.AbstractCollectionPersister - done deleting collection
[main] DEBUG org.hibernate.jdbc.AbstractBatcher - Executing batch size: 1
[main] DEBUG org.hibernate.jdbc.AbstractBatcher - about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
[main] DEBUG o.h.util.JDBCExceptionReporter - Could not execute JDBC batch update [update Child set name=null where name=?]
org.h2.jdbc.JdbcBatchUpdateException: NULL not allowed for column "NAME"; SQL statement:
update Child set name=null where name=? [23502-160]
at org.h2.jdbc.JdbcPreparedStatement.executeBatch(JdbcPreparedStatement.java:1107) ~[h2-1.3.160.jar:1.3.160]
Which is expected as the name column is part of the Child's key and can, therefore not be null.
I did explore another path which consist in replacing the collection contents rather than the collection itself:
Code:
@Column
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name="name")
private List<Child> children = new ArrayList<Child>();
public void setChildren(List<Child> children) {
this.children.clear();
this.children.addAll(children);
}
It also fails with the following exception:
Code:
[main] DEBUG org.hibernate.SQL - update Child set name=null where name=? and number=?
Hibernate: update Child set name=null where name=? and number=?
[main] DEBUG org.hibernate.jdbc.AbstractBatcher - about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
[main] DEBUG o.h.util.JDBCExceptionReporter - could not delete collection rows: [entity.Parent.children#component[name]{name=Parent-0}] [update Child set name=null where name=? and number=?]
org.h2.jdbc.JdbcSQLException: Invalid value "3" for parameter "parameterIndex" [90008-160]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:329) ~[h2-1.3.160.jar:1.3.160]
at org.h2.message.DbException.get(DbException.java:169) ~[h2-1.3.160.jar:1.3.160]
at org.h2.message.DbException.getInvalidValueException(DbException.java:215) ~[h2-1.3.160.jar:1.3.160]
at org.h2.jdbc.JdbcPreparedStatement.setParameter(JdbcPreparedStatement.java:1270) ~[h2-1.3.160.jar:1.3.160]
at org.h2.jdbc.JdbcPreparedStatement.setInt(JdbcPreparedStatement.java:308) ~[h2-1.3.160.jar:1.3.160]
at org.hibernate.type.descriptor.sql.IntegerTypeDescriptor$1.doBind(IntegerTypeDescriptor.java:52) ~[hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:91) ~[hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:283) ~[hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:278) ~[hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.type.ComponentType.nullSafeSet(ComponentType.java:340) ~[hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.type.ManyToOneType.nullSafeSet(ManyToOneType.java:121) ~[hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.persister.collection.AbstractCollectionPersister.writeElementToWhere(AbstractCollectionPersister.java:844) ~[hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.persister.collection.AbstractCollectionPersister.deleteRows(AbstractCollectionPersister.java:1316) ~[hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.action.CollectionUpdateAction.execute(CollectionUpdateAction.java:84) [hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273) [hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265) [hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:187) [hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321) [hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51) [hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216) [hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383) [hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133) [hibernate-core-3.6.7.Final.jar:3.6.7.Final]
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:76) [hibernate-entitymanager-3.6.7.Final.jar:3.6.7.Final]
This is even stranger as Hibernate still tries to nullify the name column and, additionally, tries to set the third parameter of a 2 parameter query.