Нарушено ограничение уникальности

Добрый день, имеются три вложенные сущности:

public class ProcessInformation extends StandardEntity {
    private static final long serialVersionUID = -1368462870266829602L;

    @Column(name = "NAME", nullable = false)
    protected String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "PROCESS_DEFINITION_ID")
    protected ProcDefinition processDefinition;

    @Composition
    @OnDelete(DeletePolicy.CASCADE)
    @OneToMany(mappedBy = "processInformation")
    protected List<ProcessTaskProperties> processProperties;

    @Composition
    @OnDelete(DeletePolicy.CASCADE)
    @OneToMany(mappedBy = "processInformation")
    protected List<ProcTaskAssignment> procTaskAssignments;

public class ProcessTaskProperties extends StandardEntity {
private static final long serialVersionUID = -8181610280178441202L;

@NotNull
@OnDeleteInverse(DeletePolicy.CASCADE)
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinColumn(name = "PROCESS_INFORMATION_ID")
protected ProcessInformation processInformation;

@Column(name = "PROPERTY_TYPE")
protected String propertyType;

@Column(name = "PROCESS_TASK", nullable = false)
protected String processTask;

@Column(name = "TASK_OUTCOME")
protected String taskOutcome;

@Composition
@OnDelete(DeletePolicy.CASCADE)
@OneToMany(mappedBy = "processTaskProperties")
protected Set<ProcessTaskProperty> properties;

public class ProcessTaskProperty extends StandardEntity {
private static final long serialVersionUID = 4792852034858844430L;

@NotNull
@OnDeleteInverse(DeletePolicy.CASCADE)
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinColumn(name = "PROCESS_TASK_PROPERTIES_ID")
protected ProcessTaskProperties processTaskProperties;

@Column(name = "PROPERTY", nullable = false)
protected String property;

@NotNull
@Column(name = "PROPERTY_VALUE", nullable = false)
protected String propertyValue;

при попытке сохранить сущность ProcessInformation появляется ошибка о нарушении уникальности. В классе ProcessTaskPropertiesListener был создан метод для удаления сущности из контекста, однако это не сработало, сущность сохраняется в любом случае, одна ошибка не пропадает. Что делать?

@Override
public void onBeforeInsert(ProcessTaskProperties processTaskProperties, EntityManager entityManager) {
    if (processTaskProperties.getProcessInformation() != null) {
        entityManager.remove(processTaskProperties.getProcessInformation());
        entityManager.flush();
    }

Версия кубы 6.10.2.

Добрый день,

Вы не приложили ошибку - сообщение, стектрейс. Без этого трудно понять что пошло не так.

Можно посоветовать две вещи:

  1. CascadeType.PERSIST на связях в CUBA не рекомендуется использовать.
    https://doc.cuba-platform.com/manual-7.1-ru/entity_attr_annotations.html#manyToOne_annotation
    Это как раз может приводить к лишним попыткам insert в базу данных.

  2. Обновите версию платформы до последнего багфикс-выпуска 6.10.13.
    Иначе будете мучиться и бороться с багами, которые давным-давно исправлены.

Стек трейс описанного случая:

Caused by: com.haulmont.cuba.core.global.RemoteException: ERROR: duplicate key value violates unique constraint “soesg_process_information_pkey”
Подробности: Key (id)=(a4fc612f-6776-d71c-501b-bc99c16c03d2) already exists.
at com.haulmont.cuba.core.sys.ServiceInterceptor.aroundInvoke(ServiceInterceptor.java:129) ~[na:na]
at sun.reflect.GeneratedMethodAccessor222.invoke(Unknown Source) ~[na:na]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_231]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_231]
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:627) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:616) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
at com.sun.proxy.$Proxy249.commit(Unknown Source) ~[na:na]
at sun.reflect.GeneratedMethodAccessor301.invoke(Unknown Source) ~[na:na]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_231]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_231]
at com.haulmont.cuba.core.sys.remoting.LocalServiceInvokerImpl.invoke(LocalServiceInvokerImpl.java:94) ~[na:na]
at com.haulmont.cuba.web.sys.remoting.LocalServiceProxy$LocalServiceInvocationHandler.invoke(LocalServiceProxy.java:154) ~[cuba-web-6.10.2.jar:6.10.2]
at com.sun.proxy.$Proxy34.commit(Unknown Source) ~[na:na]
at com.haulmont.cuba.client.sys.DataManagerClientImpl.commit(DataManagerClientImpl.java:100) ~[cuba-client-6.10.2.jar:6.10.2]
at com.haulmont.cuba.gui.data.impl.GenericDataSupplier.commit(GenericDataSupplier.java:105) ~[cuba-gui-6.10.2.jar:6.10.2]
at com.haulmont.cuba.gui.data.impl.DsContextImpl.commit(DsContextImpl.java:166) ~[cuba-gui-6.10.2.jar:6.10.2]
at com.haulmont.cuba.gui.components.EditorWindowDelegate.commit(EditorWindowDelegate.java:271) ~[cuba-gui-6.10.2.jar:6.10.2]
at com.haulmont.cuba.web.gui.WebWindow$Editor.commitAndClose(WebWindow.java:1773) ~[cuba-web-6.10.2.jar:6.10.2]
at com.haulmont.cuba.gui.components.AbstractEditor.commitAndClose(AbstractEditor.java:111) ~[cuba-gui-6.10.2.jar:6.10.2]
at com.haulmont.cuba.gui.components.EditorWindowDelegate.lambda$wrapBy$1(EditorWindowDelegate.java:94) ~[cuba-gui-6.10.2.jar:6.10.2]
at com.haulmont.cuba.gui.components.actions.BaseAction.actionPerform(BaseAction.java:228) ~[cuba-gui-6.10.2.jar:6.10.2]
at com.haulmont.cuba.web.gui.components.WebButton.performAction(WebButton.java:46) ~[cuba-web-6.10.2.jar:6.10.2]
at com.haulmont.cuba.web.gui.components.WebButton.lambda$new$61446b05$1(WebButton.java:38) ~[cuba-web-6.10.2.jar:6.10.2]
at sun.reflect.GeneratedMethodAccessor568.invoke(Unknown Source) ~[na:na]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_231]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_231]
at com.vaadin.event.ListenerMethod.receiveEvent(ListenerMethod.java:510) ~[vaadin-server-7.7.14.cuba.0.jar:7.7.14.cuba.0]
… 48 common frames omitted

В случае если из сущности ProcessTaskProperties с поля processInformation и из сущности ProcessTaskProperty с поля processTaskProperties убрать cascade = CascadeType.PERSIST,
стек трейс будет такой:

Caused by: com.haulmont.cuba.core.global.RemoteException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: org.altarix.soesg.entity.ProcessTaskProperties-5aad5b53-4f8e-c6fd-474c-a953584d4455 [new].
at com.haulmont.cuba.core.sys.ServiceInterceptor.aroundInvoke(ServiceInterceptor.java:129) ~[na:na]
at sun.reflect.GeneratedMethodAccessor222.invoke(Unknown Source) ~[na:na]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_231]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_231]
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:627) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:616) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:168) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.18.RELEASE.jar:4.3.18.RELEASE]
at com.sun.proxy.$Proxy249.commit(Unknown Source) ~[na:na]
at sun.reflect.GeneratedMethodAccessor298.invoke(Unknown Source) ~[na:na]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_231]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_231]
at com.haulmont.cuba.core.sys.remoting.LocalServiceInvokerImpl.invoke(LocalServiceInvokerImpl.java:94) ~[na:na]
at com.haulmont.cuba.web.sys.remoting.LocalServiceProxy$LocalServiceInvocationHandler.invoke(LocalServiceProxy.java:154) ~[cuba-web-6.10.2.jar:6.10.2]
at com.sun.proxy.$Proxy34.commit(Unknown Source) ~[na:na]
at com.haulmont.cuba.client.sys.DataManagerClientImpl.commit(DataManagerClientImpl.java:100) ~[cuba-client-6.10.2.jar:6.10.2]
at com.haulmont.cuba.gui.data.impl.GenericDataSupplier.commit(GenericDataSupplier.java:105) ~[cuba-gui-6.10.2.jar:6.10.2]
at com.haulmont.cuba.gui.data.impl.DsContextImpl.commit(DsContextImpl.java:166) ~[cuba-gui-6.10.2.jar:6.10.2]
at com.haulmont.cuba.gui.components.EditorWindowDelegate.commit(EditorWindowDelegate.java:271) ~[cuba-gui-6.10.2.jar:6.10.2]
at com.haulmont.cuba.web.gui.WebWindow$Editor.commitAndClose(WebWindow.java:1773) ~[cuba-web-6.10.2.jar:6.10.2]
at com.haulmont.cuba.gui.components.AbstractEditor.commitAndClose(AbstractEditor.java:111) ~[cuba-gui-6.10.2.jar:6.10.2]
at com.haulmont.cuba.gui.components.EditorWindowDelegate.lambda$wrapBy$1(EditorWindowDelegate.java:94) ~[cuba-gui-6.10.2.jar:6.10.2]
at com.haulmont.cuba.gui.components.actions.BaseAction.actionPerform(BaseAction.java:228) ~[cuba-gui-6.10.2.jar:6.10.2]
at com.haulmont.cuba.web.gui.components.WebButton.performAction(WebButton.java:46) ~[cuba-web-6.10.2.jar:6.10.2]
at com.haulmont.cuba.web.gui.components.WebButton.lambda$new$61446b05$1(WebButton.java:38) ~[cuba-web-6.10.2.jar:6.10.2]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_231]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_231]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_231]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_231]
at com.vaadin.event.ListenerMethod.receiveEvent(ListenerMethod.java:510) ~[vaadin-server-7.7.14.cuba.0.jar:7.7.14.cuba.0]
… 49 common frames omitted

Есть ли другие варианты решения проблемы кроме как переход на более позднюю версию?

Понятно, у вас есть экран где редактируются вместе ProcessInformation, ProcessTaskProperties и ProcessTaskProperty.
При этом при коммите экрана часть новых объектов не попадают в контекст изменений, отсюда и возникает “During synchronization a new object was found through a relationship that was not marked cascade PERSIST”.

Чтобы изменения всех объектов, которые редактируются и создаются в экране, отслеживались - нужно чтобы эти сущности лежали в datasources (даже если нет UI компонентов, где эти объекты отображаются). Тогда экран будет иметь возможность отследить и новые и измененные объекты и закоммитить изменения без каскадов.

Экранов два, первый сущность ProcessInformation, на втором одновременно создаются ProcessTaskProperties и ProcessTaskProperty. В данный момент контекст в process-task-property-edit.xml файла выглядит следующим образом:

 <dsContext>
        <datasource id="processTaskPropertyDs"
                    class="org.altarix.soesg.entity.ProcessTaskProperty"
                    view="processTaskProperty-reload">
            <datasource id="propertyDs" property="processTaskProperties"/>
        </datasource>
        <collectionDatasource id="processInformationsDs"
                              class="org.altarix.soesg.entity.ProcessInformation"
                              view="processInformation-edit">
            <query>
                <![CDATA[select e from soesg$ProcessInformation e]]>
            </query>
        </collectionDatasource>
    </dsContext>

Появляется ошибка:

IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: org.altarix.soesg.entity.ProcessInformation-9be94548-c3ab-3b39-b794-7933ad33b9dc [new].

Я так понимаю нужно добавить поле processInformation в класс ProcessTaskProperty, как это можно сделать чтобы связь в базе не создавалась, тк сущности ProcessTaskProperty и ProcessInformation уже связаны через ProcessTaskProperties?

Вы говорите, что у вас два экрана.
Они связаны между собой, второй вложен в первый?
Если так, то тогда нужно продумать каскадность сохранения сущностей.
Либо все сущности должны сохраниться вместе при коммите внешнего редактора.

Либо редактор не-каскадный, т.е. сохранение изменений вложенного редактора происходит сразу.
Но тогда для нового ProcessInformation создание и сохранение ProcessTaskProperties и ProcessTaskProperty должно быть недоступно, закрыто - ведь связанный с ними объект ProcessInformation ещё не сохранен в БД и может быть вообще не будет сохранен, если пользователь нажмет Отмена во внешнем редакторе.

Проблема решилась добавлением в файл process-information-edit.xml дата сорса ProcessTaskProperty который вложен в ProcessTaskProperties

<dsContext>
        <datasource id="processInformationDs"
                    class="org.altarix.soesg.entity.ProcessInformation"
                    view="processInformation-edit">
            <collectionDatasource id="procTaskAssignmentsDs"
                                  property="procTaskAssignments"/>
            <groupDatasource id="processPropertiesDs"
                             property="processProperties">
                <collectionDatasource id="propertiesDs"
                                      property="properties"/>
            </groupDatasource>
        </datasource>
    </dsContext>

Здравствуйте! У меня похожая проблема, на ту, что описана в данной теме. При создании нового объекта и создании вложенных в него объектов нужно коммитить все сразу. Сейчас временное решение - использование CascadeType.PERSIST. Однако хотелось бы точно понять, как обойти это нерекомендуемое решение. Требуется именно сохранять и внешнюю и вложенные сущности при коммите внешнего редактора. Я использую DataContainer и CollectionContainer а не Datasourсe.

Вам нужно ознакомиться с гайдом: https://www.cuba-platform.com/guides/data-modelling-composition

Он демонстрирует использование composition, каскадных редакторов.

Я читал данный гайд и он работает, только если использовать стандартные действия Create для таблицы. У меня же не совсем стандартно создается вложенная сущность.
Более подробно здесь: Каскадное создание сущностей

В двух словах понять вашу проблему и объяснить решение сложно.
Если создадите маленький демо-проектик (лучше к той теме), то можно будет посмотреть и попробовать предложить решение.

Прикрепил тестовый проект к главной теме