In migrating to Hibernate 5.x from 4.x we found something that broke in our application. We're using @JoinColumn to specify a non-primary key in a @ManyToOne association. In researching this a bit it seems to be an optional part of the JPA 2.0 spec, though 4.x seemed to support it.
I'm curious whether this would be considered a bug, or whether the removal of support was intentional.
In 5.x it seems to create the schema as expected and is able to store an association correctly. Where it breaks is in setting the association to null; on tx commit we get the following exception:
Code:
javax.persistence.PersistenceException: org.hibernate.property.access.spi.PropertyAccessException: Error accessing field [private long com.nokia.nspos.persistence.impl.db.entities.CompositeId.lsb] by reflection for persistent property [com.nokia.nspos.persistence.impl.db.entities.CompositeId#lsb] : com.nokia.nspos.persistence.impl.db.entities.Referee@41c3069e
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1692)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1602)
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:68)
... 52 more
Caused by: org.hibernate.property.access.spi.PropertyAccessException: Error accessing field [private long com.nokia.nspos.persistence.impl.db.entities.CompositeId.lsb] by reflection for persistent property [com.nokia.nspos.persistence.impl.db.entities.CompositeId#lsb] : com.nokia.nspos.persistence.impl.db.entities.Referee@41c3069e
at org.hibernate.property.access.spi.GetterFieldImpl.get(GetterFieldImpl.java:43)
at org.hibernate.tuple.component.AbstractComponentTuplizer.getPropertyValue(AbstractComponentTuplizer.java:58)
at org.hibernate.type.ComponentType.getPropertyValue(ComponentType.java:414)
at org.hibernate.type.ComponentType.isDirty(ComponentType.java:254)
at org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:282)
at org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:291)
at org.hibernate.type.TypeHelper.findDirty(TypeHelper.java:296)
at org.hibernate.persister.entity.AbstractEntityPersister.findDirty(AbstractEntityPersister.java:4204)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.dirtyCheck(DefaultFlushEntityEventListener.java:528)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.isUpdateNecessary(DefaultFlushEntityEventListener.java:215)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:142)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:216)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:85)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:38)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1295)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:468)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3135)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2352)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:491)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:61)
... 52 more
Caused by: java.lang.IllegalArgumentException: Can not set long field com.nokia.nspos.persistence.impl.db.entities.CompositeId.lsb to com.nokia.nspos.persistence.impl.db.entities.Referee
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58)
at sun.reflect.UnsafeLongFieldAccessorImpl.getLong(UnsafeLongFieldAccessorImpl.java:60)
at sun.reflect.UnsafeLongFieldAccessorImpl.get(UnsafeLongFieldAccessorImpl.java:36)
at java.lang.reflect.Field.get(Field.java:393)
at org.hibernate.property.access.spi.GetterFieldImpl.get(GetterFieldImpl.java:39)
... 75 more
Here's the object model I created to reproduce it in a unit test. It's simplified as much as possible
Code:
@Embeddable
public class CompositeId implements Serializable {
@Column(nullable = false)
private long lsb;
@Column(nullable = false)
private long msb;
public CompositeId() {
}
public CompositeId(long lsb, long msb) {
this.lsb = lsb;
this.msb = msb;
}
public long getLsb() {
return lsb;
}
public void setLsb(long lsb) {
this.lsb = lsb;
}
public long getMsb() {
return msb;
}
public void setMsb(long msb) {
this.msb = msb;
}
}
Code:
@Entity(name = "TestReferee")
public class Referee implements Serializable {
@EmbeddedId
private CompositeId id;
@Column(unique = true, nullable = false)
private String stringId;
public Referee(CompositeId id) {
this.id = id;
}
public Referee() {
}
public CompositeId getId() {
return id;
}
public void setId(CompositeId id) {
this.id = id;
}
public String getStringId() {
return stringId;
}
public void setStringId(String stringId) {
this.stringId = stringId;
}
}
Code:
@Entity(name = "TestReferer")
public class Referer implements Serializable {
@Id
private long id;
private String otherField;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "referee", referencedColumnName = "stringId")
private Referee referee;
public Referer() {
}
public Referer(long id) {
this.id = id;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getOtherField() {
return otherField;
}
public void setOtherField(String otherField) {
this.otherField = otherField;
}
public Referee getReferee() {
return referee;
}
public void setReferee(Referee referee) {
this.referee = referee;
}
}