Проблема с вложенной таблицей (Composition)

Добрый день!
Помогите, пожалуйста, разобраться с проблемой

Есть две сущности: MainEnitiy и SubEntity (связанные по Composition). При этом у SubEntity есть поле previousSubEntity (Association на SubEntity (на себя же) MtO optional)
Есть экран MainEntityEdit, содержащий вложенную таблицу со списком SubEnitiy.

Проблема воспроизводится вот так:

  1. Ввожу список Sub entities, сохраняю, потом еще раз редактирую и привожу к подобному виду (они связаны по цепочке). Сохраняю.
    image

  2. Захожу снова и пытаюсь по очереди эти SubEntity отредактировать: сначала Sub1, который ни с кем не связан:
    image

  3. Затем пытаюсь отредактировать Sub2, который имеет ссылку на Sub1:
    image
    После нажатия кнопки OK вижу следующее:
    image

Этот Sub2 сам отредактировался нормально, но при этом затёр мне изменения, внесенные в Sub1. Если потом отредактировать Sub3, то при этом точно так же затрутся изменения для Sub2

Почему они так со мной поступают и как их убедить этого не делать? )

P.S. понимаю, что тут однозначно влияет нахождение колонки Previous sub entity на экране. Если ее убрать (хотя бы visible=false), то всё нормально, изменения не затираются
CompositionIssue.zip (99.0 КБ)

Это не проблема - это у вас “так спроектировано” :slight_smile:

Есть две сущности: MainEnitiy и SubEntity (связанные по Composition).

Если это композиция, то строки с SubEntity сохраняются в БД только при сохранении основной сущности MainEnitiy. И по сути у вас контексты экранов редактирования MainEnitiy и SubEntity повязаны между собой.
На экран редактирования MainEnitiy читается начальное состояние строк SubEntity, включая ссылку на предыдущий экземпляр SubEntity.
Потом вы с экрана MainEnitiy открываете на редактирование строку композиции с Sub1, вносите ее изменения.
Но изменения в БД не сохраняются сохранения данных всего экрана редактирования MainEnitiy. Пока все это просто лежит в контексте экрана.
Потом вы редактируете вторую строку композиции. А в ней у вас в атрибуте previousSubEntity лежит старый экземпляр Sub1.
В результате, в контексте экрана MainEnitiy после редактирования второй строки, ранее измененный экземпляр Sub1 заменяется старым.

Если в SubEntity атрибут previousSubEntity сделать Ассоциацией - ваш экран заработает без дополнительных изменений. Потому что при каждом редактировании строки с SubEntity данные будут камититься в БД и обновляться в контексте экрана.

Но вообще, на экране редактирования впрямую подтягивать через FetchPlan такого рода ссылки на самого себя это “не есть хорошо”. Есть шанс получить и более мутные проблемы.
Кэш - он попой беспощадный :slight_smile:

Большое спасибо за ответ!
Но не совсем понимаю, что имеется в виду

Если в SubEntity атрибут previousSubEntity сделать Ассоциацией - ваш экран заработает без дополнительных изменений. Потому что при каждом редактировании строки с SubEntity данные будут камититься в БД и обновляться в контексте экрана.

В SubEntity атрибут previousSubEntity и так Ассоциация.

open class SubEntity {
<...>
    @JoinColumn(name = "MAIN_ENTITY_ID", nullable = false)
    @NotNull
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    var mainEntity: MainEntity? = null

    @JoinColumn(name = "PREVIOUS_SUB_ENTITY_ID")
    @ManyToOne(fetch = FetchType.LAZY)
    var previousSubEntity: SubEntity? = null

Это для MainEntity все SubEntity композиция.

open class MainEntity {
<...>
    @OnDelete(DeletePolicy.CASCADE)
    @Composition
    @OneToMany(mappedBy = "mainEntity")
    var subEntities: MutableList<SubEntity> = NotInstantiatedList()

Имелось в виду для MainEntity их сделать ассоциацией или что?

И если все-таки связь между MainEntity и SubEntity оставить композицией (SubEntity это что-то вроде позиций заказа, которые без своей шапки MainEntity нежизнеспособны), то, получается, на экране редактирования SubEntity связь между двумя SubEntity устанавливать нежелательно? Может быть, дадите какие-то рекомендации по методике, где и как тогда это лучше делать? Заранее спасибо)

Я имел в виду в сущности MainEntity атрибут SubEntity сделать не Композицией, а Ассоциацией.
Если по сути связь между MainEntity и SubEntity это действительно именно Композиция, тогда и интерфейс надо продумывать исходя из этого.

Сложно рекомендовать что-то конкретное не понимая конкретного бизнес-сценария для пользователя.

Если не отказываться от Композиции , то можно вместо стандартного действия edit сделать свою копку или действие и там, вызывая для строки экран редактирования SubEntity , после его закрытия по кнопке ОК, камитить данные экрана MainEntity.
Но в этом случае идейный смысл использования именно композиции слегка теряется.

Можно на экране редактирования MainEntity исключить ссылочный атрибут subEntities из загрузки в FetchPlan.
А для отображения неких сведений о SubEntity на экране редактирования MainEntity сделать генерируемую колонку и в нее подтягивать для отображения в UI тот же instanceName через dataManager.
В этом случае, у вас на экране редактирования MainEntity в контекст экрана не будут грузиться экземпляры SubEntity и все должно быть хорошо

В дескрипторе main-entity-edit.xml обновите fetchPlan так чтобы fetchPlan subEntities совпадал с fetchPlan редактора

        <instance id="mainEntityDc"
                  class="com.company.compositionissue.entity.MainEntity">
            <fetchPlan extends="_base">
                <property name="subEntities" fetchPlan="_base">
                    <property name="mainEntity" fetchPlan="_base"/>
                    <property name="previousSubEntity" fetchPlan="_base"/>
                </property>
            </fetchPlan>
            <loader/>
            <collection id="subEntitiesDc" property="subEntities"/>
        </instance>

У меня это решило проблему.

Как я понимаю в таком случае просто не происходит перезагрузка данных при открытии редактора и как следствие в поле previousSubEntity ставится обновленная версия, а не подгруженная из бд.

1 симпатия

Да, кстати, в Cuba это точно помогало в подобных случаях.
Но придется помнить о том, что надо в этом надо содержать fetchPlan этих атрибутов на разных экранах идентичным.
Как-то мы в итоге стали стараться избегать этого на корню генерируемыми колонками или неперсистентными атрибутами.

Чтоб fetchPlan был одинаков можно просто перенести его в jmix.core.fetch-plans-config