Как реализовать вычисляемые свойства (колонки) сущности с возможностью фильтрации и сортировки

Есть сущность Лицо.
Есть сущность хранящая историю изменения лица. (Исходники во вложении)
Задача:
вытащить полное имя лица (Склеить Ф + И + О) в зависимости от:

  • Текущего момента «в машине времени», брать соответствующую строку
  • Текущего языка сессии пользователя, брать соответствующие колонки

Реализация в виде мета-колонки не позволяет сортировать и фильтровать по данной вычисляемой колонке в JPQL источниках данных и компонентах типа Table

Другими словами, скажите пожалуйста каком образом в cuba platform 6.8 реализовать такие подзадачи:

  • Как сделать вычисляемую колонку-свойство fullName состоящую из простой конкатенации ФИО;
  • Как сделать вычисляемую колонку-свойство, которая вместо коллекции возвращала бы один объект-строку, в зависимости от текущего параметра (дата) в сессии пользователя и колонок startDate и endDate сущности для которой используется мягкое удаление;
  • Как сделать вычисляемую колонку-свойство, которая берёт ту или иную колонку-свойство в зависимости от параметра сессии пользователя (язык);

…по которым (колонкам) можно сортировать и фильтровать в JPQL запросах

Заранее благодарю

Как реализовать вычисляемые свойства (колонки) с возможностью сортировки и фильтрации.docx (16.0 КБ)

Например, вы можете вычислять и сохранять такие данные в СУБД при помощи Entity Listener.

Использовать EntityListener нельзя потому, что на момент сохранения не известно какие параметры будут при чтении (дата и язык сессии) которые меняются динамически при работе

Решение вашей задачи зависит от технических требований.

Если сущность Лицо содержит немного записей - скажем 100 или 1000 - тогда вы можете загружать все сущности в память и реализовать фильтрацию и сортировку по любым вычисляемым свойствам в памяти.
Для этого нужно будет детально разобраться в классе CollectionDatasourceImpl, реализовать своего наследника этого класса с необходимой логикой. Например сортировка по колонке выполняется в методе CollectionDatasourceImpl#sort

Если же справочник большой и обрабатывать весь список сущностей в памяти не получится, то у вас нет других вариантов, кроме как добавлять дополнительные поля в модель данных, в базу данных.
Дело в том, что постраничная загрузка данных (пейджинг), фильтрация, сортировка по столбцу - чтобы эти операции быстро отрабатывали на больших справочниках, они должны выполняться в базе данных.
Сортировка транслируется в JPQL запросе как “sort by e.columnName”.
Фильтрация - в “where e.fieldName = :value”
Пейджинг - в “query.setFirstResult() / setMaxResults()”.
Тогда ORM формирует хороший SQL запрос к базе данных и быстро выполняет загрузку.

В вашем случае вы можете сделать так называемую денормализацию схемы БД - т.е. добавить дополнительные вычисляемые столбцы в таблицу, которые дублируют уже имеющиеся данные, но позволяют более удобно и быстро делать запросы к БД.

Например fullName - это отдельная колонка, которая может заполняться в CUBA приложении entity listener-ом или триггером в БД.

“вычисляемую колонку-свойство, которая вместо коллекции возвращала бы один объект-строку в зависимости от текущего параметра (дата)” - здесь посложнее, но я думаю что можно будет что-то придумать, если очень нужно.

“вычисляемую колонку-свойство, которая берёт ту или иную колонку-свойство в зависимости от параметра сессии пользователя (язык)” - набор языков в вашем приложении ограничен, не так ли? Русский, английский, еще какой-нибудь. Вы можете сделать 3-4 отдельных поля DESCRIPTION_RU, DESCRIPTION_EN, DESCRIPTION_KZ для всех локализаций, заполнять их через entity listeners и потом показывать одно из них в таблице в зависимости от языка текущей пользовательской сессии.

2 симпатии

Благодарю за ответ!

Если сущность … содержит немного записей - скажем 100 или 1000 … CollectionDatasourceImpl …

Понял. Буду использовать как-нибудь в другом месте, потому что …

Если же справочник большой …

Да. В одном из проектов 35 000 лиц (работники, кандидаты и т.д.)

… то у вас нет других вариантов, кроме как добавлять дополнительные поля в модель данных, в базу данных

вот этого то и хотелось бы избежать, потому что даже на текущий момент может быть как минимум 3 поля: shortName (Имя и Фамилия), fullName (ФИО) и fullNameNumber (ФИО и табельный номер)
и это всё на 2-х языках (минимум). Уже 6 колонок.

Вообще вопрос конечно я на форум задал в целом понять можно ли и как в платформе использовать калькулируемые на стороне БД колонки.
Типа добавляем на свойство сущности некую аннотацию
(a)Formula(expression = "last_name || ’ ’ || first_name || ’ ’ || middle_name || ’ (’ || employee_number || ‘)’ ")
и это бы выражение в ядре кубы подставлялось бы в SQL запросы фильтрации, сортировки и извлечения (select). Лица (работники) это просто пример. А так мы мы это решение использовали бы в куче мест.

вычисляемую колонку-свойство, которая вместо коллекции возвращала бы один объект-строку в зависимости от текущего параметра (дата)” - здесь посложнее, но я думаю что можно будет что-то придумать, если очень нужно

Очень нужно.
У нас в модулях HR очень много сущностей с историей изменения и с возможностью посмотреть состояние сущности на любой исторический момент времени.
Типа сейчас работник Иванова, но на 1 января она была Петрова, потому что ещё не вышла замуж и не сменила фамилию.
Или сейчас она старший бухгалтер. А на 1 января была младший бухгалтер. и т.д.

Вы можете сделать 3-4 отдельных поля … и потом показывать одно из них в таблице в зависимости от языка текущей пользовательской сессии.

Этот вариант у нас есть как обходной (workaround).
Но не хотелось бы его использовать как основной.
Представьте, у нас есть browse форма с 10 колонками. из которых 7 колонок это справочники (мультиязычные). У нас в системе заложено максимум 5 языков.
тогда нам в browse форме (в xml) пришлось бы выводить 3 + 7 * 5 = 38 колонок.
И потом в Java скрывать 7 * 4 = 28 колонок для языков несоответствующих текущему языку сессии.
И так по всей системе. Не совсем удобно в каждой browse форме дублировать колонки для мультиязычных справочников (они у нас все мультиязычные)

Вы можете добавлять колонки к таблице программно из контроллера экрана, нет нужды сразу добавлять все и потом скрывать неактуальные.

Бегло посмотрев “eclipselink calculated column” в поисковике, боюсь что вычисляемые столбцы в EclipseLink не поддерживаются.

Тут предлагают создать VIEW в базе данных и маппить на него сущность:


Тогда по этой сущности можно будет выполнять запросы на чтение.

Думаю, что это тоже можно реализовать через database view.

Глядя со стороны, в вашем случае будет успехом, если просто удастся реализовать данные функциональные требования. Удобно / неудобно в коде, уже второстепенный вопрос :slight_smile:

Как хранить многоязычные подписи - это интересный вопрос, его можно решить по-разному.

Можно просто дублировать столбцы: NAME_RU, NAME_EN, …
Это легко ложится на ORM маппинг и быстро по скорости выборки, но много дублирования в коде, и подписи размазаны по всей схеме.

Другой вариант - создать отдельную таблицу для хранения всех локализованных подписей в системе:
create table localized_caption (
id serial,
text_ru varchar(512),
text_en varchar(512),
text_kz varchar(512),
primary key (id)
);

И потом изо всех справочников ссылаться на эту таблицу.
Здесь будет больше порядка в структуре данных, все подписи в одном месте, и можно реализовать в проекте единый механизм отображения / ввода локализованных подписей.
Но при загрузке данных во все списки добавятся лишние joins.

Третий вариант - это например использовать нестандартный тип данных. В postgres это например JSONB. Хранить подписи как словарь: ключ - это язык, значение - локализованная подпись. В этом случае понадобится реализовывать JPA converter и расширять CubaPostgreSQLPlatform, чтобы сохранять и читать атрибуты нестандартного типа jsonb. Но зато избегаются лишние joins при загрузке.

2 симпатии

я б вообще сделал не text_ru, text_en а lang и text но это уже дело вкуса и необходимости
а по поводу доставания локализованных сотрудников, как вариант сделать отдельною сущьность LocalizedEmployee и сервис/датасорс который будет смореть локально пользователя и время и их исторических данных строить ее
с json тож интересное решение,но лично я не люблю всякие новые плюшки в базах

1 симпатия

@budarov
допустим есть view которая извлекает записи нужного языка на нужную дату.
Тогда вопрос: Как передать в БД параметры сессии язык и дата, когда у каждого пользователя они свои, а сервер приложений использует пул сессий в БД а не выделенные ?

Язык никак не передать, локализацию можно реализовать через несколько отдельных столбцов или несколько отдельных database view. Считаем, что поддерживаемых языков в системе немного.

А дата будет учитываться в where.

Если есть история изменения, то можно составить database view со структурой вроде:

  • employee_id
  • surname
  • change_date

Тогда при запросе вы пишете

select * from surname_history
where employee_id = :employee_id
and change_date < :date
limit 1

и получите последнюю версию фамилии на переданную дату

Если на view surname_history ориентировать сущность, то получится делать и JPQL-запросы к сущности.