I have three jpa entities I’m trying to get working together. BoxProfile, BoxProfileItemAssignment and BoxItem all code listed below. BoxProfileItemAssignment has a @EmbeddedId using @MapId to map the composite key.
BoxProfile has a set of BoxProfileItemAssignments, the assignments are a BoxItem and quantity value. I want to be able to persist BoxProfileItemAssignments while persisting a new BoxProfile. Each BoxItem in a BoxProfileItemAssignments has already been persisted when a BoxProfile is being created.
I'm using spring data JpaRepository interfaces to persist my BoxProfile entities and accessing the repo through a service layer BoxProfileService.
When I attempt to persist a new BoxProfile entity I get a PersistenceException due to a detached entity. I understand that the BoxItem that is nested in the BoxProfileItemAssignment entity I'm passing in is detached but I'm not looking to make and changes or updates to said entity I just want to use it create the BoxProfileItemAssignment entry.
After much research I can't seem to find an example of Cascading persisted with a nested composite key entity which itself has a nested entity.
I would appreciate if someone could tell me what the correct combination of annotations are to achieve my goal.
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.quadrimular.fyfe.fulfillment.domain.BoxItem at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1763) at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677) at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1683) at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1187) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:291) at com.sun.proxy.$Proxy53.persist(Unknown Source) at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:394) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:442) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:427) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:381) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:267) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.invoke(CrudMethodMetadataPostProcessor.java:122) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) at com.sun.proxy.$Proxy65.save(Unknown Source) at com.quadrimular.fyfe.fulfillment.service.BoxProfileServiceImpl.addBoxProfile(BoxProfileServiceImpl.java:48) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:267) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) at com.sun.proxy.$Proxy66.addBoxProfile(Unknown Source) at com.quadrimular.fyfe.fulfillment.integration.ITBoxProfile.addBoxProfileDatabase(ITBoxProfile.java:96) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:73) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:217) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.quadrimular.fyfe.fulfillment.domain.BoxItem at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:139) at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:801) at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:794) at org.hibernate.jpa.event.internal.core.JpaPersistEventListener$1.cascade(JpaPersistEventListener.java:97) at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:350) at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:293) at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:161) at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:118) at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:432) at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:265) at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:194) at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:137) at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:84) at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:206) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:149) at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:801) at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:794) at org.hibernate.jpa.event.internal.core.JpaPersistEventListener$1.cascade(JpaPersistEventListener.java:97) at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:350) at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:293) at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:161) at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:379) at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:319) at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:296) at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:161) at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:118) at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:460) at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:294) at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:194) at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125) at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:84) at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:206) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:149) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:75) at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:811) at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:784) at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:789) at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1181) ... 72 more
BoxProfile test method
@Test
@ExpectedDatabase(value = "boxProfileData-add.xml", assertionMode = DatabaseAssertionMode.NON_STRICT)
public void addBoxProfileDatabase() throws Exception {
BoxProfileItemAssignment itemAssignment = new BoxProfileItemAssignment.Builder(BOX_ITEM_ONE, new BigDecimal("2.88")).build();
BoxProfile original = new BoxProfile.Builder("example 3").itemAssignments((new HashSet(Arrays.asList(itemAssignment)))).sizes(new HashSet(Arrays.asList(BOX_SIZE))).selected(true).sequencer(3).build();
BoxProfile returned = boxProfileService.addBoxProfile(original);
assertNotNull(returned);
assertThat(returned.getId(), instanceOf(Long.class));
assertNotNull(returned.getId());
}
BoxProfileRepository.java
public interface BoxProfileRepository extends JpaRepository<BoxProfile, Long> {
}
BoxProfileServiceImpl.java
@Service
@Transactional("mainTransactionManager")
public class BoxProfileServiceImpl implements BoxProfileService {
private static final Logger LOG = LoggerFactory
.getLogger(BoxProfileServiceImpl.class);
private BoxProfileRepository repo;
private BoxItemService boxItemService;
@Autowired
public BoxProfileServiceImpl(BoxProfileRepository repo, BoxItemService boxItemService) {
this.repo = repo;
this.boxItemService = boxItemService;
}
@Transactional("mainTransactionManager")
public BoxProfile addBoxProfile(BoxProfile boxProfile) {
LOG.debug("Adding boxProfile with information: " + boxProfile);
BoxProfile toReturn = repo.save(boxProfile);
LOG.debug("BoxProfile id: " + toReturn);
return toReturn;
}
}
BoxProfile.java
@Entity
@Table
public class BoxProfile implements Serializable {
private static final long serialVersionUID = 9091824819977165224L;
@Id
@GeneratedValue
private Long id;
private String description;
private boolean selected;
private int sequencer;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "boxProfileSizes", joinColumns = { @JoinColumn(name = "BOX_PROFILE_ID", referencedColumnName = "id") }, inverseJoinColumns = { @JoinColumn(name = "SIZE_ID", referencedColumnName = "id") })
private Set<BoxSize> sizes;
@OneToMany(mappedBy = "boxProfile", cascade={CascadeType.PERSIST, CascadeType.REMOVE}, fetch = FetchType.EAGER)
private Set<BoxProfileItemAssignment> itemAssignments;
// Modification times
private Date creationTime;
private Date modificationTime;
@PreUpdate
public void preUpdate() {
setModificationTime(new Date());
}
@PrePersist
public void prePersist() {
Date now = new Date();
setCreationTime(now);
setModificationTime(now);
}
public BoxProfile() {
}
private BoxProfile(Builder b) {
this.description = b.description;
this.id = b.id;
this.selected = b.selected;
this.sequencer = b.sequencer;
this.sizes = b.sizes;
}
public static class Builder {
// Mandatory Fields
private final String description;
// Optional Fields
private Long id = null;
private boolean selected = false;
private int sequencer = -1;
private Set<BoxSize> sizes = new HashSet<BoxSize>();
private Set<BoxProfileItemAssignment> itemAssignments = new HashSet<BoxProfileItemAssignment>();
public Builder(String description) {
this.description = description;
}
public Builder sequencer(int sequencer) {
this.sequencer = sequencer;
return this;
}
public Builder sizes(Set<BoxSize> sizes) {
this.sizes = sizes;
return this;
}
public Builder addSize(BoxSize size) {
this.sizes.add(size);
return this;
}
public Builder itemAssignments(
Set<BoxProfileItemAssignment> itemAssignments) {
this.itemAssignments = itemAssignments;
return this;
}
public Builder id(Long id) {
this.id = id;
return this;
}
public Builder selected(boolean selected) {
this.selected = selected;
return this;
}
public BoxProfile build() {
BoxProfile boxProfile = new BoxProfile(this);
// Add the new box profile to the box profile assigned fish.
for (BoxProfileItemAssignment assignment : itemAssignments) {
assignment.setBoxProfile(boxProfile);
}
// Set the updated fish assignments on the box profile
boxProfile.setItemAssignements(itemAssignments);
return boxProfile;
}
}
// Getters setters hashcode equals to string
}
BoxProfileItemAssignment.java
@Entity
@Table(name = "BOX_PROFILE_ITEM")
public class BoxProfileItemAssignment implements Serializable{
private static final long serialVersionUID = 3331165661732043732L;
@EmbeddedId
private BoxProfileItemAssignmentId id = new BoxProfileItemAssignmentId();
@MapsId("boxProfileId")
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "BOX_PROFILE_ID", referencedColumnName = "id")
private BoxProfile boxProfile;
@MapsId("itemId")
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "ITEM_ID", referencedColumnName = "id")
private BoxItem item;
private BigDecimal quantity;
// Modification times
private Date creationTime;
private Date modificationTime;
@PreUpdate
public void preUpdate() {
setModificationTime(new Date());
}
@PrePersist
public void prePersist() {
Date now = new Date();
setCreationTime(now);
setModificationTime(now);
}
private BoxProfileItemAssignment(Builder b){
this.boxProfile = b.boxProfile;
this.item = b.item;
this.quantity = b.quantity;
this.id = b.id;
}
public BoxProfileItemAssignment(){}
public static class Builder {
private final BoxItem item;
private final BigDecimal quantity;
private BoxProfile boxProfile;
private BoxProfileItemAssignmentId id = new BoxProfileItemAssignmentId();
public Builder(BoxItem item, BigDecimal quantity){
this.item = item;
this.quantity = quantity;
}
public Builder boxProfile(BoxProfile boxProfile){
this.boxProfile = boxProfile;
return this;
}
public Builder id(BoxProfileItemAssignmentId id){
this.id = id;
return this;
}
public BoxProfileItemAssignment build(){
return new BoxProfileItemAssignment(this);
}
}
// Getters setters hashcode equals to string
}
BoxProfileItemAssignmentId
@Embeddable
public class BoxProfileItemAssignmentId implements Serializable{
private static final long serialVersionUID = -7936926474216068447L;
@Column(name = "BOX_PROFILE_ID")
private Long boxProfileId;
@Column(name = "ITEM_ID")
private Long itemId;
public BoxProfileItemAssignmentId(){}
private BoxProfileItemAssignmentId(Builder b){
this.boxProfileId = b.boxProfileId;
this.itemId = b.itemId;
}
public static class Builder{
private final Long boxProfileId;
private final Long itemId;
public Builder(Long boxProfileId, Long itemId){
this.boxProfileId = boxProfileId;
this.itemId = itemId;
}
public BoxProfileItemAssignmentId build(){
return new BoxProfileItemAssignmentId(this);
}
}
// Getters setters hashcode equals to string
}
BoxItem.java
@Entity
@Table
public class BoxItem implements Serializable {
private static final long serialVersionUID = -6146188094809573420L;
@Id
@GeneratedValue
private Long id;
@NotNull
private BoxItemType type;
@NotNull
private MeasurementUnit unit;
@NotNull
@Size(min=2, max=30)
private String name;
@NotNull
private BigDecimal costPerUnit;
// Modification times
private Date creationTime;
private Date modificationTime;
@PreUpdate
public void preUpdate() {
modificationTime = new Date();
}
@PrePersist
public void prePersist() {
Date now = new Date();
creationTime = now;
modificationTime = now;
}
public BoxItem(){}
private BoxItem(Builder b){
this.type = b.type;
this.name = b.name;
this.costPerUnit = b.costPerUnit;
this.id = b.id;
this.unit = b.unit;
}
public static class Builder{
private BoxItemType type;
private MeasurementUnit unit;
private String name;
private BigDecimal costPerUnit;
private Long id;
public Builder(String name, BoxItemType type, MeasurementUnit unit, BigDecimal costPerUnit){
this.name = name;
this.type = type;
this.unit = unit;
this.costPerUnit = costPerUnit;
}
public Builder id(Long id){
this.id = id;
return this;
}
public BoxItem build(){
return new BoxItem(this);
}
}
// Getters setters hashcode equals to string
}