Problem with transaction when last statement is throwing sql-exception

Hi,
I have a problem when I try to manipulate multiple entities in one single transaction.
The model is a self referenced entity with a parent-child structure.

When I want to delete an entity within the hierarchy I have to rebind the childs to the parent of the of the actual entity.
It is all done in a service using the TransactionalDataManager.
I create a new transaction, load all the childs from the given entity and rebind the Parent-Id to the parent of the current. Then I delete the current entity.

Now, when the current entity can not be deleted, because there is a foreign key database constraint, the save-operations of the child-entities in the for-loop are not being rolled back.
In my understanding the operation should be rolled back, when the last operation is not going through?
I am using an oracleDB, and the call to the service is done from within a remove-operation of a hierarchical table. I do the complete remove-operation in the service.

public class LagerplatzDeleteServiceBean implements LagerplatzDeleteService {

        @Inject
        private TransactionalDataManager transactionalDataManager;


        public void LinkChildsToRoot(JtLagerplatz lagerplatz){

            LoadContext<JtLagerplatz> loadContextLagerplatz
                    = LoadContext.create(JtLagerplatz.class).setQuery(
                    LoadContext.createQuery("select p from roman_JtLagerplatz p where p.idLagerplatzParent = :idLagerplatz" )
                            .setParameter("idLagerplatz",lagerplatz)
            ).setView("jtLagerplatz-view");


            // run all in one transaction
            Transaction tx = transactionalDataManager.transactions().get();
            try {
                // load list of child Entities
                List<JtLagerplatz> lRet = transactionalDataManager.loadList(loadContextLagerplatz);

                JtLagerplatz lagParent = lagerplatz.getIdLagerplatzParent();

                // manipulate dhild entities
                for (JtLagerplatz l : lRet) {
                    l.setIdLagerplatzParent(lagParent);
                    transactionalDataManager.save(l);
                }
                // delete entity
                transactionalDataManager.remove(lagerplatz);

                tx.commit();
            } finally {
                tx.end();
            }

        }
    }

The calling action:

   @Subscribe("jtLagerplatzesTable1.remove")
    public void onJtLagerplatzesTable1Remove(Action.ActionPerformedEvent event) {
        JtLagerplatz curr = jtLagerplatzesHierarchicalDc.getItem();


        jtLagerplatzesTable1Remove.setConfirmation(false);
        dialogs.createOptionDialog()
                .withCaption("Löschen des Lagerplatzes")
                .withMessage("Untergeordnete Lagerplätze werden nach oben verlinkt.")
                .withActions(
                        new DialogAction(DialogAction.Type.YES)
                                .withHandler(e ->  {
                                    lagerplatzDeleteService.LinkChildsToRoot(curr);
                                    jtLagerplatzesHierarchicalDl.load();
                                }), // execute action
                        new DialogAction(DialogAction.Type.NO)
                )
                .show();
    }

What am I doing wrong?

Regards Manfred

Hi,
Your code looks correct, it should work.

It would be great if you could reproduce the problem on a small demo project, to make sure that the case is isolated from specifics of your project.
Then CUBA team will be able to investigate this project.

Hi,
I will try to set up the demo in the near future and send it to you.

Regards,
Manfred

Hi,
I created a testproject. It needs an external datastore in an oracle. Scripts are included.
I hope I did not forget anything and it helps to find the problem.

refintegrity_testproject.zip (79,2 KB) .

Regards
Manfred

Hi @manfred_p,

Entities are stored in an additional Oracle datastore, but the sample code creates a transaction in the main CUBA store:

transactionalDataManager.transactions().get()

-> so each transactionalDataManager.save/remove executed in a separate transaction on the additional store. TransactionalDataManager creates a transaction for the store if it doesn’t exist.

In order to fix the error, you need to create a transaction for a corresponding store using:

Transaction tx = transactionalDataManager.transactions().get("cuba_test")

From the JavaDoc:


    /**
     * Creates a new transaction in the main data store if there is no one at the moment.
     *
     * @see #get(String)
     */
    Transaction get();

    /**
     * Creates a new transaction if there is no one at the moment.
     * <p>If a transaction exists, joins the current transaction. In this case:
     * <ul>
     *     <li>Subsequent invocation of {@link Transaction#commit()} does not affect current transaction.</li>
     *     <li>If {@link Transaction#end()} is called without previous {@link Transaction#commit()}, current
     *     transaction is marked as rollback-only, so any attempt to commit the surrounding {@link Transaction} will
     *     throw an exception.</li>
     * </ul>
     *
     * @param storeName data store name
     * @return object to control the transaction
     */
    Transaction get(String storeName);
1 Like

Hello Andrey,

thanks for the help. It works!