EntityChanged Event Listener вопрос в случае связи типа Composition

Коллеги, добрый день!

Не могу понять, что я делаю не так. Помогите, пожалуйста. Перепробовал уже массу вариантов…
Вычленить демо-проект затруднительно, поэтому приведу детальное описание ниже.

Задача: при сохранении экземпляра базовой сущности (Entry) необходимо создавать (и инициализировать по определенным правилам) экземпляр связанной (1:1 композиция) сущности (LegacyIdMap). Ну и устанавливать связь между ними.

Решение описанное ниже приводит к очень странному поведению:
После сохранения базовой сущности вначале (на экране базовой сущности) как будто LegacyIdMap не создалось и связь не установилась (повторный логин в приложение, повторное открытие экрана - ни к чему не приводит). Но, если перезапустить Tomcat, то при открытии экрана базовой сущности можно наблюдать новую связанную сущность!

Вот первая сущность:

@Entity(name = "myproj_Entry")
@NamePattern("%s|id")
public class Entry extends StandardEntity {
    private static final long serialVersionUID = 7203339880710608935L;

    @Composition
    @OnDelete(DeletePolicy.CASCADE)
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "LEGACY_ID")
    private LegacyIDMap legacyId;
}

Вот вторая сущность:

@Entity(name = "myproj_LegacyIDMap")
@NamePattern("%s|value")
public class LegacyIDMap extends StandardEntity {
    private static final long serialVersionUID = 1976972529771139284L;

    @Column(name = "VALUE_", nullable = false, length = 32)
    @Length(message = "{msg://myproj_LegacyIDMap.value.validation.Length}", max = 32)
    @NotNull
    private String value;
}

Для решения создан слушатель:

    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    public void beforeCommit(EntityChangedEvent<Entry, UUID> event) {

        // текущий элемент
        //EntityManager em = persistence.getEntityManager();
        //Entry entry = em.find(Entry.class, event.getEntityId().getValue(), "entry-with-classifier");
        Entry entry = transactionalDataManager
                .load(event.getEntityId())
                .view("entry-with-classifier")
                .one();

        if (EntityChangedEvent.Type.CREATED.equals(event.getType())) {
            // todo... запись нового вычисленного LegacyId...
            // todo... ИТОГОВАЯ ЗАПИСЬ ПРОХОДИТ ПОСЛЕ ПЕРЕЗАГРУЗКИ СЕРВЕРА!!!
            // создать запись для хранения LegacyId
            LegacyIDMap legacyIDMap = metadata.create(LegacyIDMap.class);
            Integer maxLegacyId = myConfigService.getMaxLegacyId();
            legacyIDMap.setValue(Integer.toString(maxLegacyId + 1));
            transactionalDataManager.save(legacyIDMap);

            // update текущей Entry
            entry.setLegacyId(legacyIDMap);
            transactionalDataManager.save(entry, "entry-with-classifier");
       }
    }

А вот так прописан просмотр связи в экране базовой сущности (public class EntryEdit extends StandardEditor<Entry>):

    <data>
        <instance id="entryDc"
                  class="com.company.myproj.entity.Entry">
            <view extends="entry-with-classifier" systemProperties="true">
                <property name="legacyId" view="_minimal"/>
            </view>
            <loader/>
            <instance id="legacyIdDc" property="legacyId"/>
        </instance>
    </data>
...
...
    <pickerField id="legacyIdField" dataContainer="entryDc" property="legacyId" width="140">
    	<actions>
        	<action id="open_composition" type="picker_open_composition"/>
                <action id="clear" type="picker_clear"/>
        </actions>
    </pickerField>

Перепробовал уже массу вариантов… Выручайте :slight_smile:

transactionalDataManager.save(legacyIDMap); возвращает сохраненный экземпляр. Попробуйте именного его сетить здесь entry.setLegacyId(legacyIDMap);.

Хотя может быть это вообще не при чем, потому что я не понимаю как это работает:

Михаил, спасибо за вариант!
Попробовал сделать, как вы предложили - работает аналогично моему варианту (только после перезапуска приложения). Магия :slight_smile: ?

Такое ощущение, что тут что-то не то с транзакциями…
Как будто какие-то изменения накапливаются в некотором буфере, а рестарт сервера приводит к их коммиту…
Я не знаю как это еще объяснить.

Может быть у вас включен entity cache.
Нужно включать дебаг-логгер eclipselink.sql и смотреть, какие запросы идут к базе данных при повторном открытии экрана той же сущности.
Ну и проверить через независимый SQL клиент, создается ли сущность-спутник или нет.

Про кэш вот так прописано в проекте (файл app.properties):

eclipselink.cache.shared.centralizedclassifiers_Entry = true
eclipselink.cache.size.centralizedclassifiers_Entry = 1000

Сущность-спутник точно создается, иначе, как я её вижу после перезапуска приложения? ))

Закомментировал описания кэша сущностей в app.properties и всё заработало!!!

НО. Этот кэш в приложении нужен.

Как сделать так, чтобы мое решении работало при включенном кэше?
Может, как-то убирать сущности, с которыми я работаю в примере, их этого кэша? Или временно отключить этот кэш?..

Продолжение истории… ))
Вернул кэширование Entry и добавил кэширование сущности-спутника, что дало хорошие результаты - всё отрабатывает как надо.

eclipselink.cache.shared.centralizedclassifiers_Entry = true
eclipselink.cache.size.centralizedclassifiers_Entry = 1000
eclipselink.cache.shared.centralizedclassifiers_LegacyIDMap = true
eclipselink.cache.size.centralizedclassifiers_LegacyIDMap = 1000

Единственное, для обновления данных пока требуется переоткрыть экран Entry.
Принудительное перечитывание почему-то не подтягивает LegacyId…

entryDc.setItem(dataManager.reload(getEditedEntity(), "entry-with-classifier"));
    <view entity="centralizedclassifiers_Entry" name="entry-with-classifier" extends="_local">
        <property name="classifier" view="_minimal"/>
        <property name="legacyId" view="_minimal"/>
        <property name="parentEntry" view="_minimal"/>
    </view>

Всем доброе утро!
Есть ли какие-то рекомендации тут?..

В экране вот так:

        <instance id="entryDc"
                  class="ru.roe.centralizedclassifiers.entity.Entry">
            <view extends="entry-with-classifier" systemProperties="true">
                <property name="legacyId" view="_minimal"/>
                <property name="classifier" view="classifier-with-attribute-descriptions-view"/>
                <property name="parentEntry" view="_minimal"/>
            </view>
            <loader/>
            <instance id="legacyIdDc" property="legacyId"/>
        </instance>

При повторном открытии экрана данные обновляются как надо.
Но почему-то принудительное перечитывание данных не даёт результата в части legacyId:

        entryDc.setItem(dataManager.reload(getEditedEntity(), "entry-with-classifier"));

Кэш как-то влияет на результат reload, видимо…

Доброе утро,
А в dataManager.reload(getEditedEntity(), "entry-with-classifier") какие данные возвращает - есть ли там LegacyIdMap?

Андрей, здравствуйте!
После reload тут entryDc.getItem().getLegacyId(); получаем null.
Закрыл, открыл экран - getLegacyId заполняется корректно.

Будем пробовать воспроизводить проблему - возможно не происходит инвалидации Entity Cache при определенных кейсах создания сущности.
Попробуйте сделать обходное решение с ручной инвалидацией кэша по сущности:

EntityManagerFactory emf = cont.entityManager().getDelegate().getEntityManagerFactory();
Cache cache = emf.getCache();
cache.evict(entity, id);
2 симпатии

Здравствуйте,
Пока что не удалось воспроизвести проблему по сходному кейсу на проекте-примере. Видимо есть какая то дополнительная логика, которая не учтена.
Прикладываю проект на котором пробовал воспроизвести. В проекте ParentEntity = Entry, ChildEntity = LegacyIdMap
При создание сущности ParentEntity происходит заполнение поля c типом ChildEntity при нажатие на кнопку Save. Воспроизвести можно через редактор сущности ParentEntity.

Возможно вы сможете добавить какие то уточнения.

tentcache.zip (91.1 КБ)