I'm getting an OptimisticLockException that makes no sense to me when trying to cascade merge across an explicit association entity. My annotations may be screwed up (I'm fairly new to JPA). Here's the code for my entities:
Code:
package com.foo.persist;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Version;
@Entity
@Table(name="RPR_ROOT_PARENT")
public class RootParent {
private Long internalId;
private String rootParentId;
private Integer version;
private Set<Association> associationSet;
@OneToMany(mappedBy="rootParent", cascade={CascadeType.PERSIST, CascadeType.MERGE})
public Set<Association> getAssociationSet() {
return associationSet;
}
public void setAssociationSet(Set<Association> associationSet) {
this.associationSet = associationSet;
}
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="RPR_UID")
public Long getInternalId() {
return internalId;
}
public void setInternalId(Long internalId) {
this.internalId = internalId;
}
@Column(name="RPR_ID")
public String getRootParentId() {
return rootParentId;
}
public void setRootParentId(String rootParentId) {
this.rootParentId = rootParentId;
}
@Version
@Column(name="RPR_VERSION")
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((rootParentId == null) ? 0 : rootParentId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final RootParent other = (RootParent) obj;
if (rootParentId == null) {
if (other.rootParentId != null)
return false;
} else if (!rootParentId.equals(other.rootParentId))
return false;
return true;
}
}
package com.foo.persist;
import java.io.Serializable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Version;
@Entity
@IdClass(AssociationPK.class)
@Table(name="ASC_ASSOCIATION")
public class Association implements Serializable {
/**
*
*/
private static final long serialVersionUID = -3746181614360357763L;
private long rootParentUid;
private RootParent rootParent;
private long leafParentUid;
private LeafParent leafParent;
private Integer version;
@Id
@Column(name="ASC_LPR_UID", updatable=false, insertable=false)
public long getLeafParentUid() {
return leafParentUid;
}
public void setLeafParentUid(long leafParentUid) {
this.leafParentUid = leafParentUid;
}
@Id
@Column(name="ASC_RPR_UID", updatable=false, insertable=false)
public long getRootParentUid() {
return rootParentUid;
}
public void setRootParentUid(long rootParentUid) {
this.rootParentUid = rootParentUid;
}
@ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name="ASC_LPR_UID")
public LeafParent getLeafParent() {
return leafParent;
}
public void setLeafParent(LeafParent leafParent) {
this.leafParent = leafParent;
}
@ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name="ASC_RPR_UID")
public RootParent getRootParent() {
return rootParent;
}
public void setRootParent(RootParent rootParent) {
this.rootParent = rootParent;
}
@Version
@Column(name="ASC_VERSION")
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((leafParent == null) ? 0 : leafParent.hashCode());
result = PRIME * result + ((rootParent == null) ? 0 : rootParent.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Association other = (Association) obj;
if (leafParent == null) {
if (other.leafParent != null)
return false;
} else if (!leafParent.equals(other.leafParent))
return false;
if (rootParent == null) {
if (other.rootParent != null)
return false;
} else if (!rootParent.equals(other.rootParent))
return false;
return true;
}
}
package com.foo.persist;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Id;
@SuppressWarnings("unused")
public class AssociationPK implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private long leafParentUid;
private long rootParentUid;
public AssociationPK() {}
public AssociationPK(long leafParentUid, long rootParentUid) {
this.leafParentUid = leafParentUid;
this.rootParentUid = rootParentUid;
}
public long getLeafParentUid() {
return leafParentUid;
}
public void setLeafParentUid(long leafParentUid) {
this.leafParentUid = leafParentUid;
}
public long getRootParentUid() {
return rootParentUid;
}
public void setRootParentUid(long rootParentUid) {
this.rootParentUid = rootParentUid;
}
}
package com.foo.persist;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;
@Entity
@Table(name="LPR_LEAF_PARENT")
public class LeafParent {
private Long internalId;
private String leafParentId;
private Integer version;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="LPR_UID")
public Long getInternalId() {
return internalId;
}
public void setInternalId(Long internalId) {
this.internalId = internalId;
}
@Column(name="LPR_ID")
public String getLeafParentId() {
return leafParentId;
}
public void setLeafParentId(String leafParentId) {
this.leafParentId = leafParentId;
}
@Version
@Column(name="LPR_VERSION")
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((leafParentId == null) ? 0 : leafParentId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final LeafParent other = (LeafParent) obj;
if (leafParentId == null) {
if (other.leafParentId != null)
return false;
} else if (!leafParentId.equals(other.leafParentId))
return false;
return true;
}
}
And here's my unit test:
Code:
package com.foo.test;
import java.util.HashSet;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
import junit.framework.TestCase;
import com.foo.persist.Association;
import com.foo.persist.LeafParent;
import com.foo.persist.RootParent;
public class JpaTest extends TestCase {
private static EntityManagerFactory emf;
private static EntityManager em;
private static boolean init = false;
@Override
protected void setUp() throws Exception {
if (!init) {
// Create EntityManagerFactory for persistent unit named "pu1"
// to be used in this test
emf = Persistence.createEntityManagerFactory("pu1");
// Create a new EntityManager
em = emf.createEntityManager();
init = true;
}
}
private void doMerge(Object pObj) {
em.getTransaction().begin();
em.merge(pObj);
em.getTransaction().commit();
}
private void doPersist(Object pObj) {
em.getTransaction().begin();
em.persist(pObj);
em.getTransaction().commit();
}
public void testUpdateRootParentWithTransientLeafParent() throws Exception {
Query q = em.createQuery("select r from RootParent r where r.rootParentId = 'RPR1'");
RootParent r = (RootParent)q.getSingleResult();
LeafParent l = new LeafParent();
l.setLeafParentId("TRANSIENTLEAF");
Association a = new Association();
a.setLeafParent(l);
a.setRootParent(r);
r.getAssociationSet().add(a);
doMerge(r);
}
}
I'm getting the following stacktrace from testUpdateRootParentWithTransientLeafParent:
javax.persistence.OptimisticLockException
at org.hibernate.ejb.AbstractEntityManagerImpl.wrapStaleStateException(AbstractEntityManagerImpl.java:642)
at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:599)
at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:236)
at com.foo.test.JpaTest.doMerge(JpaTest.java:39)
at com.foo.test.JpaTest.testUpdateRootParentWithTransientLeafParent(JpaTest.java:84)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at junit.framework.TestCase.runTest(TestCase.java:164)
at junit.framework.TestCase.runBare(TestCase.java:130)
at junit.framework.TestResult$1.protect(TestResult.java:110)
at junit.framework.TestResult.runProtected(TestResult.java:128)
at junit.framework.TestResult.run(TestResult.java:113)
at junit.framework.TestCase.run(TestCase.java:120)
at junit.framework.TestSuite.runTest(TestSuite.java:228)
at junit.framework.TestSuite.run(TestSuite.java:223)
at org.junit.internal.runners.OldTestClassRunner.run(OldTestClassRunner.java:35)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:38)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.foo.persist.Association#com.foo.persist.AssociationPK@1bdbfec]
at org.hibernate.event.def.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:261)
at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:120)
at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:687)
at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:669)
at org.hibernate.engine.CascadingAction$6.cascade(CascadingAction.java:245)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:268)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:216)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296)
at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:219)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
at org.hibernate.event.def.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:407)
at org.hibernate.event.def.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:152)
at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:126)
at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:53)
at org.hibernate.impl.SessionImpl.fireMerge(SessionImpl.java:677)
at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:661)
at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:665)
at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:227)
... 21 more
I'm testing this specifically because I have multiple use cases where I have an association entity with extra information in it, but the primary key is a composite key, and I can't use a surrogate key because it's a legacy db that I'm not allowed to change.
Any help here?
Thanks, Greg