Modifying composition of Entities in Entity manager in transaction

I have a service that copies values from one entity to another. The operation is multilevel in that it copies some values from entity A to entity B and then attempts to overwrite the set of child entities © from entity A to Entity B.

First i delete all of the C entities in B. and then add cloned versions of the C entities from the A parent instance.

I’ve tried various implementations where I tried to do this in one transaction but all have failed.

My current implementation works but is performed in two separate transactions. The first copies the fields from A to B and deletes the C records from B.

The second copies the C records from A to B.

For the transaction I’m using a Utility

Here Is the service implementation

package com.yieldmo.ymcuba.services.adbuilder;

import com.haulmont.cuba.core.EntityManager;
import com.haulmont.cuba.core.Persistence;
import com.haulmont.cuba.core.global.LoadContext;
import com.yieldmo.ymcuba.adbuilder.AdBuilderCreativeCopyService;
import com.yieldmo.ymcuba.entity.Creative;
import com.yieldmo.ymcuba.entity.CreativeConfig;
import com.yieldmo.ymcuba.services.store.YmStoreManager;
import com.yieldmo.ymcuba.util.EntityLoader;

import org.springframework.stereotype.Service;

import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static com.haulmont.cuba.core.global.LoadContext.createQuery;

import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;

@Service(AdBuilderCreativeCopyService.NAME)
public class AdBuilderCreativeCopier implements AdBuilderCreativeCopyService {

  private static final String ID = "adBuilderCreativeId";
  public static final String CREATIVE_FOR_CLONING_VIEW = "creative-manager-creative";
  public static final String CREATIVE_BY_ADBUILDER_QUERY = "select c from ymcuba$Creative c" +
          " where c.adBuilderCreativeId = :" + ID;
  private Creative sourceCreative;
  private EntityLoader entityLoader;
  private YmStoreManager storeManager;
  private Persistence persistence;

  public AdBuilderCreativeCopier(EntityLoader entityLoader, YmStoreManager storeManager, Persistence persistence){
    this.entityLoader = entityLoader;
    this.storeManager = storeManager;
    this.persistence = persistence;
  }

  @Override
  public void copyToTargetCreatives(Set<Creative> targets, String adBuilderCreativeId){
    this.sourceCreative = loadCreative(adBuilderCreativeId);

     for(Creative target: targets){
        dupeTo(target);
     }
  }

  private Creative loadCreative(String adBuilderCreativeId) {
    LoadContext.Query query = createQuery(CREATIVE_BY_ADBUILDER_QUERY).setParameter(ID, adBuilderCreativeId);

    return entityLoader.loadEntity(Creative.class, CREATIVE_FOR_CLONING_VIEW, query);
  }

  private Creative dupeTo(Creative target){
    target.setSharingVisibility(sourceCreative.getSharingVisibility());
    target.setFormatGroup(sourceCreative.getFormatGroup());
    target.setStatus(sourceCreative.getStatus());
    persistCreative(target);
    return target;
  }

  private void persistCreative(Creative target) {

   // FIRST TRANSACTION
    storeManager.transact((EntityManager em) -> {
      em.merge(target);
      clearConfigs(target, em);
    });

   // SECOND TRANSACTION
    storeManager.transact((EntityManager em) -> {
      cloneConfigs(target,em);
    });
  }

  private void cloneConfigs(Creative target, EntityManager em) {
    Set<CreativeConfig> configs= Optional.ofNullable(sourceCreative.getConfigs())
            .map(this::cloneConfigs).get();

    setConfigs(configs, target, em);
  }

  private  Set<CreativeConfig> cloneConfigs(Set<CreativeConfig> configs) {
    return configs.stream()
            .map(CreativeConfig::clone)
            .collect(Collectors.toSet());
  }

  public void clearConfigs(Creative target, EntityManager em){
      target.getConfigs().forEach(config -> em.remove(config));
  }

  private void setConfigs(Set<CreativeConfig> clonedConfigs, Creative target, EntityManager em) {
    if (isNotEmpty(clonedConfigs)) {
      clonedConfigs.forEach((config) -> {
        config.setCreative(target);
        em.persist(config);
      });
    }
  }
}


Here is the implementation of the YmStoreManager referenced in the service above

package com.yieldmo.ymcuba.services.store;

import javax.inject.Inject;

import org.springframework.stereotype.Component;

import com.haulmont.cuba.core.Persistence;
import com.haulmont.cuba.core.Transaction;

import com.yieldmo.ymcuba.ExcludeFromCodeCoverage;

@Component
public class YmStoreManager {

  public static final String STORE_NAME = "yieldmo";

  private final Persistence persistence;

  @Inject
  public YmStoreManager(Persistence persistence) {
    this.persistence = persistence;
  }

  @ExcludeFromCodeCoverage
  public <T> T transactReturning(Transaction.Callable<T> action) {
    Transaction tx = persistence.createTransaction(STORE_NAME);
    return tx.execute(STORE_NAME, action);
}

  @ExcludeFromCodeCoverage
  public void transact(Transaction.Runnable action) {
    Transaction tx = persistence.createTransaction(STORE_NAME);
    tx.execute(STORE_NAME, action);
  }
}

When I tried to everything within the same transaction I got a variety of errors depending on what I tried. Here is an example

com.haulmont.cuba.core.global.RemoteException:
---
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.2.cuba23): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1967330851960070600-cta' for key 'crtv_name_unq'

Error Code: 1062
Call: INSERT INTO creative_config (group_element, grouping, name, value, creative_id) VALUES (?, ?, ?, ?, ?)
	bind => [null, null, cta, none, 1967330851960070600]
Query: InsertObjectQuery(com.yieldmo.ymcuba.entity.CreativeConfig-8388692119038344336 [managed])
---
javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.2.cuba23): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1967330851960070600-cta' for key 'crtv_name_unq'
Error Code: 1062
Call: INSERT INTO creative_config (group_element, grouping, name, value, creative_id) VALUES (?, ?, ?, ?, ?)
	bind => [null, null, cta, none, 1967330851960070600]
Query: InsertObjectQuery(com.yieldmo.ymcuba.entity.CreativeConfig-8388692119038344336 [managed])
---
org.eclipse.persistence.exceptions.DatabaseException: 
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1967330851960070600-cta' for key 'crtv_name_unq'
Error Code: 1062
Call: INSERT INTO creative_config (group_element, grouping, name, value, creative_id) VALUES (?, ?, ?, ?, ?)
	bind => [null, null, cta, none, 1967330851960070600]
Query: InsertObjectQuery(com.yieldmo.ymcuba.entity.CreativeConfig-8388692119038344336 [detached])
---
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1967330851960070600-cta' for key 'crtv_name_unq'
	at com.haulmont.cuba.core.sys.ServiceInterceptor.aroundInvoke(ServiceInterceptor.java:129)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:629)
	at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:618)
	at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168)
	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:213)
	at com.yieldmo.ymcuba.adbuilder.AdBuilderCreativeCopyServiceUnMockedTest.testCloneCreatives(AdBuilderCreativeCopyServiceUnMockedTest.java:38)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

Figured it out thanks.