title: How to persist a new entity containing multiple identical instances of another unpersisted entity with spring-boot and JPA?
body: <p><strong>Overview:</strong></p>
  
  <p>I'm building a spring-boot application which, in part, retrieves some entities from an external REST service and compares it to previous versions of the entity held locally in a database.</p>
  
  <p>I'm injecting <code>EntityManager</code> with <code>@PersistenceContext</code>, and using that to work with the database, as there are many entity types, and the type is initially unknown to the module. I could get a <code>JpaRepository</code> from a factory, but the number of different entity types is liable to grow, and I'd rather not rely on that if at all possible.</p>
  
  <p><strong>Problem:</strong></p>
  
  <p>When the module retrieves an entity which it doesn't hold in the database, it does some business logic and then tries to persist the new entity.</p>
  
  <p>The <code>Person</code> class, which is one of the entities in question, contains three fields of type <code>Site</code>, which often hold the same objects.</p>
  
  <p>When I try to persist a new <code>Person</code> which has the same <code>Site</code> object in multiple fields with <code>CascadeType.PERSIST</code>, I get an <code>EntityExistsException</code> (see stacktrace (1)).</p>
  
  <p>When I remove the CascadeType.PERSIST from the <code>Site</code> fields, and try to persist a new <code>Person</code> which has the same <code>Site</code> object in multiple fields, I get a <code>TransientPropertyValueException</code> (see stacktrace (2)).</p>
  
  <p>I think I understand the reasons why both exceptions occur:</p>
  
  <ul>
  <li><p>In the first case it's because after the first site field is cascade-persisted, it cannot be repersisted for the second field.</p></li>
  <li><p>The second case I think is because the the <code>@Transactional</code> annotation is trying to flush the transaction without the site instance(s) being persisted.</p></li>
  </ul>
  
  <p>I've tried removing the <code>@Transactional</code> annotation and beginning and commiting an EntityTransaction myself, but I get an <code>IllegalStateException</code> (see stacktrace (3)), though I think this is expected as spring should be handling the transactions itself.</p>
  
  <p>I've looked at answers to similar questions (e.g. <a href="https://stackoverflow.com/questions/9821914/how-to-persist-manytomany-relation-duplicate-entry-or-detached-entity">this</a>, <a href="https://stackoverflow.com/questions/2302802/object-references-an-unsaved-transient-instance-save-the-transient-instance-be">this</a>) but all suggest some variation of changing the CascadeType.</p>
  
  <p>In another question someone suggested to make sure that the entities in question were being evaluated correctly by the <code>equals()</code> method, so I checked in the debugger, and <code>((Person)newEntity).currentSite.equals(((Person)newEntity).homeSite)</code> evaluates to true.</p>
  
  <p>How can I go about consistently persisting/merging entities with the same object accross multiple fields?</p>
  
  <hr>
  
  <p><strong>Edit:</strong> I've also tried the various combinations of cascade types with <code>fetch = FetchType.EAGER</code>, but this produces no change in their respective exceptions.</p>
  
  <hr>
  
  <p><strong>Edit 2:</strong> I've tried using a <code>JpaRepository</code> instead of using the <code>EntityManager</code>, and get effectively the same set of exceptions depending on what cascade types I'm using.</p>
  
  <p>If I use <code>PERSIST</code>, but no <code>MERGE</code>, I get an <code>EntityNotFoundException</code> (see stacktrace (4)), and if I use both <code>PERSIST</code> and <code>MERGE</code> I get an InvalidDataAccessApiUsageException` (see stacktrace (5)).</p>
  
  <hr>
  
  <p><strong>Person:</strong></p>
  
  <pre><code>@EqualsAndHashCode(callSuper = true)
  @javax.persistence.Entity
  @XmlDiscriminatorValue("person")
  @XmlRootElement(name = "person")
  @XmlAccessorType(XmlAccessType.FIELD)
  @XmlSeeAlso({Subscriber.class})
  public class Person extends MobileResource implements Serializable {
  
      private static final Logger LOG = LogManager.getLogger(Person.class);
  
      private String firstName;
      private String surname;
  
      public Person() {
          super();
      }
  
      public Person(Long id) {
          super(id);
      }
  
      public Person(Person that) {
          super(that);
          this.firstName = that.firstName;
          this.surname = that.surname;
      }
  
      // getters &amp;&amp; setters
  }
  </code></pre>
  
  <p><strong>MobileResource:</strong></p>
  
  <pre><code>@EqualsAndHashCode(callSuper = true)
  @Entity
  @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
  @XmlRootElement(name = "resource")
  @XmlDiscriminatorNode("@type")
  @XmlAccessorType(XmlAccessType.FIELD)
  @XmlSeeAlso({Vehicle.class, Person.class})
  public abstract class MobileResource extends Resource implements Serializable {
  
      private static final Logger LOG = LogManager.getLogger(MobileResource.class);
  
      @ManyToOne(cascade = CascadeType.ALL)
      private MobileResourceStatus status;
      private Long                 incidentId;
      @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH})
      private Site                 homeSite;
      @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH})
      private Site                 currentSite;
      @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH})
      private Site                 relocationSite;
  
      public MobileResource() {
          super();
      }
  
      public MobileResource(Long id) {
          super(id);
      }
  
      public MobileResource(MobileResource that) {
          super(that);
          this.status = that.status;
          this.incidentId = that.incidentId;
          this.homeSite = that.homeSite;
          this.currentSite = that.currentSite;
          this.relocationSite = that.relocationSite;
      }
  
      // getters &amp;&amp; setters
  }
  </code></pre>
  
  <p><strong>Site:</strong></p>
  
  <pre><code>@EqualsAndHashCode(callSuper = true)
  @javax.persistence.Entity
  public class Site extends Resource implements Serializable {
  
      private static final Logger LOG = LogManager.getLogger(Site.class);
  
      private String location;
  
      public Site() {
          super();
      }
  
      public Site(Long id) {
          super(id);
      }
  
      public Site(Site that) {
          super(that);
          this.location = that.location;
      }
  }
  </code></pre>
  
  <p><strong>Resource:</strong></p>
  
  <pre><code>@EqualsAndHashCode
  @MappedSuperclass
  @XmlRootElement
  @XmlSeeAlso({MobileResource.class})
  public abstract class Resource implements Entity, Serializable {
  
      private static final Logger LOG = LogManager.getLogger(Resource.class);
  
      @Id
      private Long            id;
      private String          callSign;
      @XmlPath(".")
      private LatLon          latLon;
      private Long            brigadeId;
      private Long            batchId;
      @ManyToMany(cascade = CascadeType.ALL)
      private List&lt;Attribute&gt; attributes;
      @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH})
      private ResourceType    type;
  
      public Resource() {
      }
  
      public Resource(Long id) {
          this.id = id;
      }
  
      public Resource(Resource that) {
          this.id = that.id;
          this.callSign = that.callSign;
          this.latLon = that.latLon;
          this.attributes = that.attributes;
          this.batchId = that.batchId;
          this.brigadeId = that.brigadeId;
          this.type = that.type;
      }
  
      // getters &amp;&amp; setters
  }
  </code></pre>
  
  <p><strong>DefaultEntityMessageHandler:</strong></p>
  
  <pre><code>@Component
  public class DefaultEntityMessageHandler implements EntityMessageHandler {
  
  
      @PersistenceContext
      private EntityManager entityManager;
  
      @Override
      @Transactional
      public void handleEntityMessage(EntityMessageData data, Message message) {
  
          // business logic
          if (newEntity != null) {
              if (oldEntity != null)
                  entityManager.merge(newEntity);
              else
                  entityManager.persist(newEntity);
          }
      }
  }
  </code></pre>
  
  <p><strong>Stacktrace (1):</strong></p>
  
  <pre><code>2018-06-06 12:05:15,975 ERROR ActiveMQMessageConsumer - ID:cpt-9225-1528283097161-1:1:1:1 Exception while processing message: ID:cpt-8919-1528281875592-1:1:1:1:4 
  javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [my.class.path.entity.resource.site.Site#738]
  at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:118)
  at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:157)
  at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:164)
  at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:813)
  at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:773)
  at org.hibernate.jpa.event.internal.core.JpaPersistEventListener$1.cascade(JpaPersistEventListener.java:80)
  at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:467)
  at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:392)
  at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
  at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
  at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:414)
  at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:252)
  at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
  at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125)
  at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67)
  at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189)
  at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132)
  ...
  </code></pre>
  
  <p>Changing the cascade type in MobileResource:</p>
  
  <pre><code>@ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE, CascadeType.DETACH})
  private Site                 homeSite;
  @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE, CascadeType.DETACH})
  private Site                 currentSite;
  @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE, CascadeType.DETACH})
  </code></pre>
  
  <p><strong>Stacktrace (2):</strong></p>
  
  <pre><code>2018-06-06 12:19:24,084 ERROR ExceptionMapperStandardImpl - HHH000346: Error during managed flush [org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : my.class.path.entity.resource.mobile_resource.person.Person.currentSite -&gt; my.class.path.entity.resource.site.Site]
  2018-06-06 12:19:24,093 ERROR ActiveMQMessageConsumer - ID:cpt-9436-1528283955454-1:1:1:1 Exception while processing message: ID:cpt-8919-1528281875592-1:1:1:1:8
  org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : my.class.path.entity.resource.mobile_resource.person.Person.currentSite -&gt; my.class.path.entity.resource.site.Site; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : my.class.path.entity.resource.mobile_resource.person.Person.currentSite -&gt; my.class.path.entity.resource.site.Site
      at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:365)
      at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:227)
      at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:540)
      at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)
      at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
  ...
  </code></pre>
  
  <p><strong>Stacktrace (3):</strong></p>
  
  <pre><code>2018-06-06 13:29:35,594 ERROR ActiveMQMessageConsumer - ID:cpt-9864-1528288166188-1:1:1:1 Exception while processing message: ID:cpt-8919-1528281875592-1:1:1:1:9
  java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
      at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:254)
      at com.sun.proxy.$Proxy114.getTransaction(Unknown Source)
      at my.class.path.entity_controller.DefaultEntityMessageHandler.handleEntityMessage(DefaultEntityMessageHandler.java:60)
      at my.class.path.entity_listener.listeners.IdExtractorMessageListener.onMessage(IdExtractorMessageListener.java:41)
  ...
  </code></pre>
  
  <p><strong>Stacktrace (4)</strong></p>
  
  <pre><code>Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
  2018-06-06 15:26:36,143 ERROR SpringApplication - Application run failed
  java.lang.IllegalStateException: Failed to execute CommandLineRunner
      at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:793)
      at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774)
      at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
      at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246)
      at org.springframework.boot.SpringApplication.run(SpringApplication.java:1234)
      at my.class.path.OfficerSubscription.main(OfficerSubscription.java:44)
  Caused by: org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find my.class.path.entity.resource.site.Site with id 738; nested exception is javax.persistence.EntityNotFoundException: Unable to find my.class.path.entity.resource.site.Site with id 738
      at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:373)
      at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:227)
      at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:507)
      at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
      at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
      at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
      at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
  ...
  </code></pre>
  
  <p><strong>Stacktrace (5)</strong></p>
  
  <pre><code>Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
  2018-06-06 15:31:54,840 ERROR SpringApplication - Application run failed
  java.lang.IllegalStateException: Failed to execute CommandLineRunner
  at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:793)
  at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774)
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246)
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:1234)
  at my.class.path.OfficerSubscription.main(OfficerSubscription.java:44)
  Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Multiple representations of the same entity [my.class.path.entity.resource.site.Site#738] are being merged. Detached: [FJE84 - Uckfield]; Detached: [FJE84 - Uckfield]; nested exception is java.lang.IllegalStateException: Multiple representations of the same entity [my.class.path.entity.resource.site.Site#738] are being merged. Detached: [FJE84 - Uckfield]; Detached: [FJE84 - Uckfield]
  at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:365)
  at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:227)
  at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:507)
  at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
  at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
  at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
  at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:135)
  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
  at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
  at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
  at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
  at com.sun.proxy.$Proxy122.save(Unknown Source)
  at my.class.path.OfficerSubscription.run(OfficerSubscription.java:81)
  at my.class.path.OfficerSubscription$$FastClassBySpringCGLIB$$705870eb.invoke(&lt;generated&gt;)
      ...
  </code></pre>