Доступ о основному хранилищу компонента

Здравствуйте!

Вопрос: возможно ли в платформе из основного приложения получить доступ к основному хранилищу компонента?

Поясню. Предположим, что будет 2 проекта (приложение main - основное, которое будет использовать приложение test в качестве компонента):

1)В проекте test (бд автоматически создалась под названием test) создал одну сущность TestEntity, добавил 2 String поля (хотя это не особо важно), CUBA автоматически создала в базе таблицу test_test_entity. Создал 2 экземпляра сущности TestEntity через развернутое приложение test.

2)Сгенерировал app component descriptor для приложения test. После этого смог проект test загрузил в удаленный репозиторий. Все, как описано тут: https://doc.cuba-platform.com/manual-6.10-ru/app_components_dev.html и тут: https://doc.cuba-platform.com/manual-6.10-ru/app_components_sample.html

3)В проекте main (бд автоматически создалась под названием main) вообще не создавал сущностей.

4)Добавил custom component в проект main (компонент test из удаленного репозитория из 1го пункта). Цель - получить из основного приложения main экземпляры сущностей проекта test, созданных на 1ом этапе. Но, если теперь попытаться задеплоить проект main, то cuba выдаст warning о том, что БД основного приложения main не содержит некоторые таблицы для компонентов приложения и предложит обновить БД основного приложения main. Дальше 2 варианта:
4.1)Игнорирую и запускаю сервер. Тогда в развернутом приложении в инспекторе сущностей вообще нет сущностей. Все правильно, т. к. я не запускал скрипты обновления бд.
4.2)Соглашаюсь на Update БД. Тогда CUBA создает новую таблицу test_test_entity в БД main. В развернутом приложении в инспекторе сущностей теперь можно выбрать сущность test$TestEntity, но записей в ней нет, т. к. CUBA создала пустую таблицу test_test_entity в БД main.

5)Добавляю Additional data store, указываю в нем Data store name: test, URL: localhost/test. CUBA тем не менее все равно читает записи TestEntity из основного хранилища приложения main.

Менял пункты 4 и 5 местами (т. е. сначала добавлял additional datastore, а потом добавлял custom component), но это также ничего не меняет. CUBA создает таблицы пустые таблицы в своем основном датасторе.

Похожий вопрос был: Additional datastore компонента. Но в этой теме речь шла о получении из основного приложения доступа к дополнительному датастору компонента, меня же интересует возможность механизмов платформы для получения доступа к основному датастору компонента.

Возможно ли все-таки с помощью механизмов платформы получить уже созданные записи в проекте test?

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

1 симпатия

Сделал так:

  1. Подключил компонент test к основному проекту main.
  2. Добавил основной датастор компонента test как additional store для проекта main - Data store name: testDs

Теперь хочу в коде основного проекта main получить экземпляры сущностей дополнительного хранилища в виде TestEntity (опредленные в компоненте test), используя TypedQuery (как описано здесь, к примеру).

  1. через SQL:

    package com.company.main.service;

    import com.groupstp.test.entity.TestEntity;
    import com.haulmont.cuba.core.*;
    import com.haulmont.cuba.core.global.Metadata;
    import org.springframework.stereotype.Service;

    import javax.inject.Inject;
    import java.util.List;

    @Service(EntityImportService.NAME)
    public class EntityImportServiceBean implements EntityImportService {

    @Inject
    private Persistence persistence;

    @Override
    public void importEntities() {
        try (Transaction tx = persistence.createTransaction("testDs")) {
            EntityManager entityManager = persistence.getEntityManager("testDs");
            TypedQuery<TestEntity> testEntityQuery = entityManager.createNativeQuery(
                    "select * from test_test_entity", TestEntity.class);
            List<TestEntity> testEntities = testEntityQuery.getResultList();
            }
        }
    }

Тогда, при выполнении getResultList() получаю exception:

QueryException:
Exception Description: Missing descriptor for [class com.company.test.entity.TestEntity].
Query: ReadAllQuery(referenceClass=TestEntity sql=“select * from test_test_entity”)

  1. через JPQL:

    package com.company.main.service;

    import com.groupstp.test.entity.TestEntity;
    import com.haulmont.cuba.core.*;
    import com.haulmont.cuba.core.global.Metadata;
    import org.springframework.stereotype.Service;

    import javax.inject.Inject;
    import java.util.List;

    @Service(EntityImportService.NAME)
    public class EntityImportServiceBean implements EntityImportService {

    @Inject
    private Persistence persistence;

    @Override
    public void importEntities() {
        try (Transaction tx = persistence.createTransaction("testDs")) {
            EntityManager entityManager = persistence.getEntityManager("testDs");
            TypedQuery<TestEntity> testEntityQuery = entityManager.createNativeQuery(
                    "select * from test_test_entity", TestEntity.class);
            List<TestEntity> testEntities = testEntityQuery.getResultList();
            }
        }
    }

Тогда, при выполнении getResultList() получаю exception:

JPQLException:
Exception Description: Problem compiling [select t from test$TestEntity t].
[14, 35] The abstract schema type ‘test$TestEntity’ is unknown.

Что я еще забыл сделать?

Этих exception’ов не будет, если объявить сущность TestEntity в проекте main (например, сделав “Generate Model” в хранилище testDs), но тогда появится лишняя сущность TestEntity в проекте main - а ведь она уже объявлена в компоненте test и хотелось бы использовать именно ее, не плодя сущностей-копий в основном проекте.

  1. Возможно также написать так:

    package com.company.main.service;

    import com.groupstp.test.entity.TestEntity;
    import com.haulmont.cuba.core.*;
    import com.haulmont.cuba.core.global.Metadata;
    import org.springframework.stereotype.Service;

    import javax.inject.Inject;
    import java.util.List;

    @Service(EntityImportService.NAME)
    public class EntityImportServiceBean implements EntityImportService {

    @Inject
    private Persistence persistence;

    @Override
    public void importEntities() {
        try (Transaction tx = persistence.createTransaction("testDs")) {
            EntityManager entityManager = persistence.getEntityManager("testDs");
            Query testEntityQuery = entityManager.createNativeQuery(
                    "select * from test_test_entity");
            List testEntities = testEntityQuery.getResultList();
            }
        }
    }

Но это уже означает, что нужно вручную обрабатывать результат запроса (не ORM) и мапить на объект TestEntity…

Возможно ли мапить автоматически, как в первых 2ух случаях?

Добрый день,
По всей видимости у вас проблема с указанием “прописки” сущностей через файлы persistence.xml и свойства cuba.persistenceConfig.

Как должно быть:
Допустим у вас есть аддон “myaddon”, и в нем в главном datastore описаны сущности.
Тогда все эти сущности появляются в файле com/company/myaddon/persistence.xml.

Далее, у аддона есть app component descriptor - app-component.xml, расположенный в глобальном модуле. В нем указывается экспорт разных свойств.
Так вот, в нём не должно быть такой записи:

<property name="cuba.persistenceConfig" value="+com/company/myaddon/persistence.xml"/>

Если эта запись есть, то ваш главный проект подхватит сущности аддона в свой основной datastore, а вам это не нужно.

Далее, в основном проекте вы создаете дополнительное хранилище, например “Archive”.
У вас в app.properties появляется новое свойств:

cuba.persistenceConfig_Archive = +com/company/playground/Archive-persistence.xml

Вот туда и нужно дописать ссылку (или вообще заменить) на persistence-файл, чтобы сущности аддона оказались прописаны в дополнительном хранилище:

cuba.persistenceConfig_Archive = +com/company/myaddon/persistence.xml
2 симпатии

Спасибо за ответ!
Да, действительно, удалил свойство cuba.persistenceConfig в аддоне myaddon, указав эту ссылку в app.properties вместо того, что там было, и сущности в проекте playground отобразились (еще сделал то же самое и в файле web-app.properties - тогда увидел эти сущности в инспекторе сущностей). Но возникла другая проблема…

Это работает только если в сущности аддона нет ссылки на файл (sys$FileDescriptor). Сделал для примера в аддоне myaddon 2 сущности: Contract и Document.

  1. Contract. Он может содержать в себе несколько документов:
package com.company.myaddon.entity;

import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Column;
import com.haulmont.cuba.core.entity.StandardEntity;
import com.haulmont.chile.core.annotations.NamePattern;
import java.util.List;
import javax.persistence.OneToMany;

@NamePattern("%s|name")
@Table(name = "MYADDON_CONTRACT")
@Entity(name = "myaddon$Contract")
public class Contract extends StandardEntity {
    private static final long serialVersionUID = 1007867341796001655L;

    @Column(name = "NAME")
    protected String name;

    @OneToMany(mappedBy = "contract")
    protected List<Document> documents;

    public void setDocuments(List<Document> documents) {
        this.documents = documents;
    }
    public List<Document> getDocuments() {
        return documents;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}
  1. Document. Он содержит в себе ссылку на файл (поле pdf):
package com.company.myaddon.entity;

import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Column;
import com.haulmont.cuba.core.entity.StandardEntity;
import com.haulmont.chile.core.annotations.NamePattern;
import com.haulmont.cuba.core.entity.FileDescriptor;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.ManyToOne;

@NamePattern("%s|name")
@Table(name = "MYADDON_DOCUMENT")
@Entity(name = "myaddon$Document")
public class Document extends StandardEntity {
    private static final long serialVersionUID = 6666159404527875757L;

    @Column(name = "NAME")
    protected String name;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "PDF_ID")
    protected FileDescriptor pdf;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "CONTRACT_ID")
    protected Contract contract;

    public void setContract(Contract contract) {
        this.contract = contract;
    }
    public Contract getContract() {
        return contract;
    }
    public void setPdf(FileDescriptor pdf) {
        this.pdf = pdf;
    }
    public FileDescriptor getPdf() {
        return pdf;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

В таком случае, при попытке развернуть приложение playground, я получаю такую ошибку:

ERROR [localhost-startStop-1] com.haulmont.cuba.core.sys.AbstractWebAppContextLoader - Error initializing application
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory_Archive' defined in class path resource [com/company/playground/spring.xml]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: Exception [EclipseLink-28018] (Eclipse Persistence Services - 2.6.2.cuba24): org.eclipse.persistence.exceptions.EntityManagerSetupException
Exception Description: Predeployment of PersistenceUnit [myaddon] failed.
Internal Exception: Exception [EclipseLink-7250] (Eclipse Persistence Services - 2.6.2.cuba24): org.eclipse.persistence.exceptions.ValidationException
Exception Description: [class com.company.myaddon.entity.Document] uses a non-entity [class com.haulmont.cuba.core.entity.FileDescriptor] as target entity in the relationship attribute [field pdf].

Как быть?

Для начала вам нужно определиться, где вы хотите хранить объекты FileDescriptor, связанные с myaddon$Document. Одна и та же сущность не может находиться в двух data stores. Сущности вашего аддона живут сами по себе, в отдельном data store не будет CUBA таблиц.

Если вас устраивает что FileDescriptor останутся в основном хранилище, то вам сюда:
https://doc.cuba-platform.com/manual-7.1-ru/data_store.html#cross_datastore_ref

1 симпатия

Сейчас они хранятся в основном хранилище аддона. Вообще, планируется сущности из аддона (вместе с файлами) скопировать в основной датастор.

Если под “основным хранилищем” подразумевается датастор playground, то да, с этим хранилищем нужно потом будет работать, хранилище myaddon будет не нужно. То есть, FileDescriptor должен остаться в датасторе playground.

Исходя из документации, не очень понял, что мне нужно сделать в ситуации с FileDescriptor. Сейчас в playground вообще нет сущностей.
Нужно завести сущность (к примеру File) в playground и в ней сделать ссылку на FileDescriptor (как в документации)? Но как тогда указать, что я хочу по этой ссылке получать FileDescriptor именно myaddon?

Добрый день,

Вам нужно изменить определение Document, чтобы отразить, что сущности Document и FileDescriptor находятся в разных data stores:

public class Document extends StandardEntity {
...
@SystemLevel
@Column(name = "PDF_ID")
private Long pdfId;

@Transient
@MetaProperty(related = "pdfId")
private FileDescriptor pdf;