Skip to main content

Связывание с данными

В N2O Framework визуальные компоненты связываются с данными через объекты и выборки. Объекты и выборки делегируют свои вызовы провайдерам данных.

Объект#

Объект — это сущность предметной области. Он объединяет в себе все операции над этой сущностью и её валидации.

Объекты создаются с помощью файлов [id].object.xml.

<?xml version='1.0' encoding='UTF-8'?><object xmlns="http://n2oapp.net/framework/config/schema/object-4.0"  name="Мой объект">  <operations>    <!-- Операции объекта -->    <operation id="create">...</operation>    <operation id="update">...</operation>    <operation id="delete">...</operation>  </operations>  <validations>    <!-- Валидации объекта -->    <constraint id="uniqueName">...</constraint>    <condition id="dateInPast">...</condition>  </validations></object>

Подробнее об объектах

Операции объекта#

Над объектом можно выполнять операции, например, создание или удаление. Операция определяет входные, выходные данные для провайдера и задаёт список валидаций.

Операция объекта
<operation  id="create"  name="Создание">  <invocation>...</invocation><!--Провайдер данных-->  <in>      <!--Входные данные-->      <field id="name"/>      <field id="birthday"/>  </in>  <out>      <!--Выходные данные-->      <field id="id"/>  </out>  <fail-out>      <!--Выходные данные в случае ошибки операции-->      <field id="message" mapping="#this.getMessage()"/>  </fail-out>  <validations>...</validations><!--Валидации операций--></operation>

Валидации объекта#

Валидации — это проверки объекта на корректность.

Проверки могут быть на удовлетворённость данных какому-либо условию. Например, что дата не может быть в прошлом. Они задаются элементом <condition>:

<validations>  <condition    id="dateInPast"    on="birthday"    message="Дата рождения не может быть в будущем">    birthday <= today()  </condition></validations>

Условия пишутся на языке JavaScript.

Так же проверки могут быть выполнены в базе данных или сервисах. Например, что наименование должно быть уникальным. Такие проверки задаются в элементе <constraint>:

<validations>  <constraint    id="uniqueName"    message="Имя {name} уже существует"    result="cnt == 0">    <invocation>...</invocation><!--Провайдер данных-->    <in>        <!--Входные данные-->        <field id="name"/>        <field id="id"/>    </in>  </constraint></validations>

Вызов проверки происходит аналогично вызову операции объекта, т.е. определяет входные данные для провайдера и обрабатывает результат выполнения.

Выборка#

Выборка — это срез данных объекта. Выборки позволяют порционно получать данные объекта, фильтровать, сортировать и группировать их.

Выборки создаются с помощью файлов [id].query.xml.

Структура выборки
<?xml version='1.0' encoding='UTF-8'?><query xmlns="http://n2oapp.net/framework/config/schema/query-4.0"  name="Моя выборка"  object-id="myObject">  <list>...</list> <!--Постраничное получение записей-->  <count>...</count> <!--Получение общего количества записей-->  <unique>...</unique> <!--Получение уникальной записи-->  <fields>    <!-- Поля выборки -->    <field id="firstName"> ... </field>    <field id="lastName"> ... </field>  </fields></query>

За получение списка записей отвечает элемент <list>. За получение общего количества записей — элемент <count>.

А за получение одной уникальной записи — <unique>.

Элементов <list>, <count>, <unique> может быть несколько с разными наборами фильтров (атрибут filters).

Структура выборки
<list filters="firstName, lastName">  ...</list>

Подробнее о выборках

Поля выборки#

Поле выборки — это информация о способе получения, фильтрации или сортировки данных одного поля объекта.

За каждый способ отвечает соответствующий элемент. Например, за получение результатов отвечает <select>. Если элемент не объявлен, то соответствующий способ взаимодействия с полем невозможен. Например, если не объявлен элемент <sorting>, то сортировка по полю невозможна.

Поле выборки
<field id="name">  <select>...</select><!--Способ получения данных-->  <filters>...</filters><!--Способ фильтрации данных-->  <sorting>...</sorting><!--Способ сортировки данных--></field>
Получение результатов выборки#

Для того, чтобы получить значения полей выборки, в некоторых случаях эти поля нужно передать на вход провайдеру данных. Это можно сделать с помощью элемента <select>.

Задание выражения для получения значения поля выборки sql запроса
<field id="firstName">  <select>t.name as firstName</select></field>

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

В теле <select> записывается выражение, которое можно вставить в sql или rest запрос с помощью переменной select.

Задание шаблона select команды sql запроса
<list>    <sql>SELECT :select FROM mytalbe</sql><list>

Фильтры выборки#

Фильтров у одного поля выборки может быть много. Различаются они по типу фильтрации.

Существует несколько типов фильтраций. Каждый из них задаётся соответствующим элементом:

Типы фильтраций

ТипОписаниеТип данных
eqЭквивалентностьЛюбой
likeСтрока содержит подстрокуСтроковые
likeStartСтрока начинается с подстрокиСтроковые
inВходит в списокПростые типы
isNullЯвляется nullЛюбой
containsВходит в множествоСписковые типы
overlapsПересекается с множествомСписковые типы
moreСтрого большеЧисла и даты
lessСтрого меньшеЧисла и даты

Почти на каждый из перечисленных типов есть тип с отрицанием, например, notEq.

Задание фильтров в выборке
<filters>  <!-- Фильтр по "eq" -->  <eq filter-id="gender.id">...</eq>  <!-- Фильтр по "in" -->  <in filter-id="genders*.id">...</in></filters>

Атрибут filter-id задаёт уникальный идентификатор фильтра, по которому можно определить поле выборки и тип фильтрации.

В теле фильтра записывается выражение, которое можно вставить в sql или rest запрос с помощью переменной filters.

Задание выражения фильтрации для sql запроса
<filters>  <eq>t.id = :id</eq></filters>
Задание шаблона where команды sql запроса
<list>  <sql>SELECT t.name FROM mytable t WHERE :filters</sql></list>
Сортировка поля выборки#

Чтобы отсортировать поле выборки по возрастанию или по убыванию, необходимо отправить эту информацию на вход в провайдер данных.

Выражение для отправки можно сформировать в теле элемента <sorting>, которое можно вставить в sql или rest запрос с помощью переменной sortings.

Задание выражения сортировки для sql запроса
<field id="name">  <sorting>name :direction</sorting></field>

Переменная direction содержит в себе направление сортировки: ASC или DESC. Название переменной можно сменить с помощью маппинга.

Задание шаблона order by команды sql запроса
<list>  <sql>SELECT t.name FROM mytable t ORDER BY :sortings</sql></list>

Провайдеры данных#

Провайдеры — это универсальный способ обращения к источнику или к сервису предоставляющему данные. В N2O есть библиотека провайдеров данных: SQL, REST, EJB, Spring Framework, MongoDB, GraphQl.

Провайдер можно задать при получении выборки данных, при выполнении операций над объектом, в валидациях и в других случаях.

Подробнее о провайдерах

SQL провайдер данных#

SQL провайдер позволяет выполнять SQL запросы к базе данных, описанные прямо в XML файле.

Получение списка записей SQL провайдером
<query>  <list>    <sql>SELECT :select FROM mytable WHERE :filters ORDER BY :sortings</sql>  </list>  <count>    <sql>SELECT count(*) FROM mytable WHERE :filters</sql>  </count>  <fields>    <field id="name">      <select>name</select>      <filters>          <eq>name = :name</eq>      </filters>      <sorting>name :direction</sorting>    </field>  </fields></query>
Выполнение операции SQL провайдером
<operation id="create">  <invocation>    <sql>INSERT INTO mytable (first_name, last_name) VALUES (:firstName, :lastName)</sql>  </invocation>  <in>    <field id="firstName"/>    <field id="lastName"/>  </in></operation>

Настройки SQL провайдера

Если приложение обращается только к одной базе данных, то настройки подключения соответствуют настройкам spring.datasource.

Если необходимо подключиться к другой базе данных, то можно задать класс jdbc драйвера настройкой n2o.engine.jdbc.driver-class-name.

GraphQl провайдер данных#

GraphQl провайдер позволяет выполнять GraphQl запросы к backend сервисам.

Получение списка записей GraphQl провайдером
<query>  <list>    <graphql>        query persons(filters: { $$filters }, page: $$page, size: $$limit, sort: { $$sorting }) {           id           name           age        }    <graphql/>  </list>
  <fields>    <field id="id">      <select/>    </field>
    <field id="name">      <select/>      <filters>        <eq filter-id="name">{ name: {eq: $$name} }</eq>      </filters>      <sorting mapping="['nameDir']">{ name: $$nameDir }</sorting>    </field>
    <field id="age">      <select/>    </field>  </fields></query>
Выполнение операции GraphQl провайдером
<operation id="create">  <invocation>    <graphql>      mutation {         createPerson(name: $$name, age: $$age) {            id            name            age         }      }    </graphql>  </invocation>  <in>    <field id="name"/>    <field id="age"/>  </in></operation>

Настройки GraphQl провайдера

Для подключения к backend сервису используется атрибут endpoint. Вместо задания endpoint в каждом определении провайдера можно воспользоваться глобальной настройкой n2o.engine.graphql.endpoint.

Для обеспечения безопасного доступа к backend сервису необходимо использовать атрибут access-token или глобальную настройку n2o.engine.graphql.access-token.

REST провайдер данных#

REST провайдер выполняет http запросы к backend сервисам.

Получение списка записей REST провайдером
<query>  <list>    <rest>/api/myentity/items?{filters}&amp;{sortings}</rest>  </list>  <unique filters="id">    <rest>/api/mytable/{id}</rest>  </unique>  <fields>    <field id="name">      <select/>      <filters>          <eq>name={name}</eq>      </filters>      <sorting>sort=name:{direction}</sorting>    </field>  </fields></query>
Выполнение операции REST провайдером
<operation id="create">  <invocation>    <rest method="post">/api/myentity</rest>  </invocation>  <in>    <field id="firstName"/>    <field id="lastName"/>  </in></operation>

Java провайдеры данных#

С помощью java провайдеров можно вызвать метод java класса.

Экземпляр класса можно получить с помощью IoC контейнера EJB или Spring. Либо можно вызвать статический метод класса.

Получение списка записей Java провайдером
<query>  <list>    <java      class="com.example.MyService"      method="getList">      <arguments>        <argument          type="criteria"          class="com.example.MyCriteria"/>      </arguments>      <spring/>    </java>  </list>  <fields>    <field id="name">      <select/>      <filters>          <eq/>      </filters>      <sorting/>    </field>  </fields></query>
Выполнение операции Java провайдером
<operation id="create">  <invocation>    <java class="com.example.MyService"          method="create">      <arguments>        <argument          type="entity"          class="com.example.MyEntity"/>      </arguments>      <spring/>    </java>  </invocation>  <in>    <field id="firstName" mapping="[0].firstName"/>    <field id="lastName" mapping="[0].lastName"/>  </in></operation>

Mongo DB провайдер данных#

Mondo DB провайдер выполняет запросы к Mongo DB базе данных.

Получение списка записей Mongo DB провайдером
<query>    <list>        <mongodb collection-name="person" operation="find"/>    </list>    <count>        <mongodb collection-name="person" operation="countDocuments"/>    </count>    <fields>        <field id="id" domain="string">            <select/>            <filters>                <eq filter-id="id"/>            </filters>            <sorting/>        </field>        <field id="name">            <select>name</select>        </field>    </fields></query>

В теле фильтров необходимо использовать синтаксис построения запросов в mongodb. В соответствии с официальной документацией https://docs.mongodb.com/manual/reference/operator/ . Используя плейсхолдер #, можно вставлять данные запроса(например значение фильтра)

Пример
<query>    <list>        <mongodb collection-name="person" operation="find"/>    </list>    <fields>        <field id="id" domain="string">            <select mapping="['_id'].toString()">_id</select>            <filters>                <eq filter-id="id">{ _id: new ObjectId('#id') }</eq>            </filters>            <sorting/>        </field>        <field id="name" domain="string">            <select>name</select>            <filters>                <like filter-id="nameLike" mapping="['nameLikeMap']">{ name: { $regex: '.*#nameLikeMap.*'}}</like>                <likeStart filter-id="nameStart">{ name: {$regex: '#nameStart.*'}}</likeStart>            </filters>            <sorting mapping="['sortName']">name :sortName</sorting>        </field>        <field id="birthday" domain="localdate">            <select/>            <filters>                <more filter-id="birthdayMore">{birthday: {$gte: new ISODate(#birthdayMore)}}</more>                <less filter-id="birthdayLess">{birthday: {$lte: new ISODate(#birthdayLess)}}</less>            </filters>        </field>    </fields></query>

Автоматическая генерация для mongodb провайдера

В mongo db идентификатор записи всегда называется _id и имеет тип ObjectId, в N2O идентификатор записи должен называться id и иметь тип string или integer, поэтому:

  • <select/> для поля id преобразуется в <select mapping="['_id'].toString()">_id</select>
  • для всех остальных полей <select/> преобразуется в <select>id поля</select>
  • фильтр eq для поля id <eq filter-id="id"/> преобразуется в <eq filter-id="id">{ _id: new ObjectId('#id') }</eq> фильтры других типов для поля id необходимо прописывать полностью. Автоматическая генерация сработает только для типа eq.
  • для других полей автоматическая генерация тела фильтра работает для всех типов. Но необходимо учитывать, что она простая (для полей с типом дата необходимо писать самостоятельно, с учетом написания фильтров в mongodb).
  • для поля id сортировка <sorting/> преобразуется в <sorting>_id :idDirection</sorting>
  • для всех других полей, например name, <sorting/> преобразуется в <sorting>name :nameDirection</sorting>
Пример
<!-- Поле id --><field id="id" domain="string">    <select/>    <filters>        <eq filter-id="id"/>    </filters>    <sorting/></field>
<!-- трансформируется в --><field id="id" domain="string">    <select mapping="['_id'].toString()">_id</select>    <filters>        <eq filter-id="id">{ _id: new ObjectId('#id') }</eq>    </filters>    <sorting>_id :idDirection</sorting></field>
<!-- Поле name --><field id="name" domain="string">    <select/>    <filters>        <like filter-id="nameLike"/>        <likeStart filter-id="nameStart"/>    </filters>    <sorting/></field>
<!-- трансформируется в --><field id="name" domain="string">    <select>name</select>    <filters>        <like filter-id="nameLike">{ name: { $regex: '.*#nameLike.*'}}</like>        <likeStart filter-id="nameStart">{ name: {$regex: '#nameStart.*'}}</likeStart>    </filters>    <sorting>name :nameDirection</sorting></field>
<!-- Для даты тело фильтров необходимо прописывать самостоятельно --><field id="birthday" domain="localdate">    <select/>    <filters>        <more filter-id="birthdayMore">{birthday: {$gte: new ISODate(#birthdayMore)}}</more>        <less filter-id="birthdayLess">{birthday: {$lte: new ISODate(#birthdayLess)}}</less>    </filters></field>
Выполнение операции Mongo DB провайдером
<operation id="create">  <invocation>    <mongodb collection-name="person" operation="insertOne"/>  </invocation>  <in>    <field id="firstName"/>    <field id="lastName"/>  </in></operation>

Доступны операции insertOne, updateOne, deleteOne, deleteMany, countDocuments.

Типы данных#

Типы данных в N2O предназначены для правильной передачи значений от клиента к провайдерам данных.

Типы данных

ТипОписание
stringСтроки
integerЦелые числа
dateДата и время
localdateЛокальная Дата
localdatetimeЛокальная дата и время
booleanЛогический тип (true / false)
objectОбъект с вложенными свойствами
numericЧисло с точкой без округлений
longБольшое целое число
shortКороткое целое число

Любой из перечисленных типов может образовывать списковый тип данных, если добавить в конец квадратные скобки:

integer[]

Типы данных в XML элементах задаются ключевым словом domain.

Тип integer в поле выборки
<query>  ...  <fields>    <field id="gender.id" domain="integer">      ...    </field>  </fields></query>
Тип integer в параметрах операции
<operation>  ...  <in>    <field id="gender.id" domain="integer"/>  </in></operation>

Биндинг полей#

Поле ввода, поле выборки и параметр операции связываются друг с другом через идентификатор id:

Поле виджета
<input-text id="firstName"/>
Поле выборки
<field id="firstName"> ... </field>
Параметр операции
<field id="firstName"/>

Подобная связь называется биндингом.

Биндинг составных полей#

Составные поля обычно используются в компонентах выбора одного значения из списка:

<select id="gender">  ... <!-- Содержит id и name --></select>

В json представлении модель данных gender выглядит так:

{    "gender": {      "id" : 1,      "name" : "Мужской"    }}

Если мы хотим использовать только id, можно записать биндинг через "точку":

<field id="gender.id"/> <!-- 1 -->

Биндинг интервальных полей#

Интервальные поля — это поля, в которых можно задать начало и окончание:

<date-interval id="period">  ... <!-- Содержит begin и end --></date-interval>

В json представлении модель данных period выглядит так:

{    "period": {      "begin" : "01.01.2018 00:00",      "end" : "31.12.2018 00:00"    }}

При передаче в два параметра нужно использовать окончание .begin и .end:

<field id="period.begin"/> <!-- 01.01.2018 00:00 --><field id="period.end"/> <!-- 31.12.2018 00:00 -->

Биндинг полей множественного выбора#

Поля множественного выбора позволяют выбрать несколько значений из предложенных вариантов:

<select id="regions" type="multi">  ...<!-- Содержит несколько регионов --></select>

Модель данных regions в json:

{    "regions": [      {        "id" : 1,        "name" : "Адыгея"      },      {        "id" : 16,        "name" : "Татарстан"      }    ]}

Чтобы в параметре операции собрать только идентификаторы regions необходимо использовать "звёздочку":

<field id="regions*.id"/> <!-- [1,16] -->

Маппинг данных в провайдерах#

Входные и выходные параметры провайдера могут несоответствовать полям ввода. Для их приведения в соответствие используется атрибут mapping. Выражение в mapping записывается на языке SpEL.

Провайдеры используют тип входных параметров: "ключ значение".

note

Java провайдеры в качестве альтернативы могут использовать тип входных параметров "массив значений". Поэтому в маппинге java можно также обращаться по индексу аргумента, например, [0], [1].name.

Маппинг фильтров#

Маппинг фильтров в sql, rest, graphql и mongodb#
Маппинг фильтров в sql провайдере
<query>  <list>    <sql>SELECT t.first_name, t.gender_id FROM mytable t WHERE :filters</sql>  </list>  <fields>    <field id="firstName">      <filters>        <!-- Маппинг определяет ключ "first_name" в который будет скопировано значение фильтра "firstName" -->        <like mapping="['first_name']">          t.first_name like '%'||:first_name||'%'        </like>      </filters>    </field>    <field id="gender.id">      <filters>        <!-- Маппинг определяет ключ "gender_id" в который будет скопирован id фильтра "gender" -->        <eq mapping="['gender_id']">          t.gender_id = :gender_id        </eq>        <!-- Маппинг определяет ключ "genders" в который будет скопированы список id из фильтра "genders" -->        <in mapping="['genders']">          t.gender_id in (:genders)        </in>      </filters>    </field>  </fields></query>
Маппинг фильтров в rest провайдере
<query>  <list>    <rest>/api/myentity/items?{filters}</rest>  </list>  <fields>    <field id="firstName">      <filters>        <!-- Маппинг определяет ключ "first_name" в который будет скопировано значение фильтра "firstName" -->        <like mapping="['first_name']">          first_name_like={first_name}        </like>      </filters>    </field>    <field id="gender.id">      <filters>        <!-- Маппинг определяет ключ "gender_id" в который будет скопирован id фильтра "gender" -->        <eq mapping="['gender_id']">          gender_id={gender_id}        </eq>        <!-- Маппинг определяет ключ "genders" в который будет скопированы список id из фильтра "genders" -->        <in mapping="['genders']">          gender_id_in={genders}        </in>      </filters>    </field>  </fields></query>
Маппинг фильтров в graphql провайдере
<query>  <list>    <graphql filter-separator=", and:">      query posts(filter: $$filters) {        score        completed      }    </graphql>   </list>
  <fields>    <field id="score">      <select mapping="['score']"/>      <!-- при указании тела в фильтре оно будет добавлено в $$filters плейсхолдер -->      <filters>        <eq filter-id="score">{ score: {gt: $$score} }</eq>      </filters>    </field>    <field id="completed">      <select mapping="['completed']"/>      <filters>        <eq filter-id="completed">{ completed: $$completed }</eq>      </filters>    </field>       ...  </fields></query>
Маппинг сортировки в mongodb провайдере
<query>  <list>    <mongodb collection-name="user" operation="find"/>  </list>  <fields>      <field id="userAge" domain="integer">          <select/>          <!-- Маппинг определяет ключ "sortUserAge" в который будет скопировано значение фильтра поля "userAge" -->          <sorting mapping="['sortUserAge']">age :sortUserAge</sorting>      </field>  </fields></query>

Маппинг входных параметров операции#

Маппинг входных параметров операции sql#
Маппинг входных параметров в sql провайдере
<operation>  <invocation>    <sql>INSERT INTO mytable (first_name, gender_id) VALUES (:first_name, :gender_id)</sql>  </invocation>  <in>    <field id="name" mapping="['first_name']"/>    <field id="gender.id" mapping="['gender_id']"/>  </in></operation>
Маппинг входных параметров в graphql провайдере
<operation>  <invocation>    <graphql>mutation { create(score: $$user_score, completed: $$is_completed) {id score completed}</graphql>  </invocation>  <in>    <field id="score" mapping="['user_score']"/>    <field id="completed" mapping="['is_completed']"/>  </in></operation>
Маппинг входных параметров операции rest#
Запрос rest
POST /api/myentity
Тело запроса
{    "firstName" : "John",    "genderId" : 1}
Маппинг входных параметров в rest провайдере
<operation>  <invocation>    <rest method="post">/api/myentity</rest>  </invocation>  <in>    <field id="name" mapping="['firstName']"/>    <field id="gender.id" mapping="['genderId']"/>  </in></operation>
Маппинг входных параметров операции java#

Для вызова метода java класса необходимо передать аргументы вызова в виде массива Object[]. В java провайдере можно задать класс каждого аргумента. Существует 3 типа аргументов: примитивы, сущности, критерии.

Типы аргументов java провайдера

ТипОписание
primitiveПримитивные java классы: String, Integer, Boolean и т.п. Для них не нужно задавать атрибут class.
entityКласс сущности. Для них не нужно задавать атрибут class, если в объекте задан атрибут entity-class.
criteriaКласс, содержащий фильтры, сортировки и паджинацию.
Маппинг примитивов#

Предположим у нас есть метод java класса с примитивным типом аргументов:

Метод java класса с примитивным типом аргументов
package com.example;
class Calculator {  public static Long sum(Long a, Long b) {    return a + b;  }}

Чтобы смапить значение поля ввода в примитивный аргумент java метода, достаточно указать имя аргумента:

Маппинг примитивов в java провайдере
<operation>  <invocation>    <java class="com.example.Calculator" method="sum">      <arguments>        <argument type="primitive" name="arg1"/>        <argument type="primitive" name="arg2"/>      </arguments>    </java>  </invocation>  <in>    <field id="a" mapping="['arg1']"/>    <field id="b" mapping="['arg2']"/>  </in></operation>
note

Аргументы должны быть указаны в том же порядке, что и соответствующие аргументы java метода

Маппинг сущности#
Метод java класса с аргументом - сущностью
@Serviceclass MyService {  public Long create(MyEntity entity)  { ... }}
java
class MyEntity {  private String name;  private String surname;  //getters and setters}

Тип entity может быть задан только один раз среди всех аргументов.

Маппинг сущности в java провайдере
<operation>  <invocation>    <java class="com.example.MyService" method="create">      <arguments>        <argument type="entity" class="com.example.MyEntity"/>      </arguments>      <spring/>    </java>  </invocation>  <in>    <field id="firstName" mapping="[0].name"/>    <field id="lastName" mapping="[0].surname"/>  </in></operation>
Маппинг критериев#

Критерии предназначены для передачи параметров фильтрации, сортировки и паджинации в java провайдер. Как правило, фильтры задаются через поля класса, т.к. они уникальны для каждого случая. А сортировка и паджинация задаются через базовый класс наследник. N2O поддерживает несколько базовых классов критериев:

ТипОписание
org.springframework.data.domain.PageableИнтерфейс библиотеки spring-data для задания паджинации
org.springframework.data.domain.SortКласс библиотеки spring-data для задания сортировок
org.springframework.data.domain.ExampleИнтерфейс библиотеки spring-data для задания критериев по полям сущности
net.n2oapp.criteria.CriteriaКласс библиотеки criteria-api для задания сортировок и паджинации
Метод java класса с аргументом - критерием
@Serviceclass MyService {  public List<MyEntity> getList(MyCriteria criteria)  { ... }}
class MyCriteria extends Criteria {  private Date birtdayBefore;  private Date birtdayAfter;  //getters and setters}

Тип criteria может быть задан только один раз среди всех аргументов. Маппинга сортировки и паджинации не предусмотрено, они передаются через базовый класс наследник.

Задание фильтров в java провайдере
<query>  <list>    <java      class="com.example.MyService"      method="getList">      <arguments>        <argument          type="criteria"          class="com.example.MyCriteria"/>      </arguments>      <spring/>    </java>  </list>  <fields>    <field id="birthday">      <filters>          <more filter-id="birthdays.begin" mapping="[0].birthdayAfter"/>          <less filter-id="birthdays.end" mapping="[0].birthdayBefore"/>      </filters>      <sorting/>    </field>  </fields></query>

Маппинг результатов выборки#

Выборка возвращает список объектов при вызове через <list>, или один объект, при вызове через <unique>. Задача маппинга — задать соответствие между свойством вернувшегося объекта и полем выборки.

Маппинг результатов выборки sql#
Sql запрос
SELECT name as fname, surname as lname FROM mytable
Маппинг результатов выборки sql провайдера
<query>  <list>    <sql>SELECT name as fname, surname as lname FROM mytable</sql>  </list>  <count>    <sql>SELECT count(*) FROM mytable</sql>  </count>  <fields>    <field id="firstName">      <select mapping="['fname']"/>    </field>    <field id="lastName">      <select mapping="['lname']"/>    </field>  </fields></query>
Маппинг результатов выборки graphql#
GraphQl запрос
query persons() {name age}
Маппинг результатов выборки sql провайдера
<query>  <list result-mapping="['data.persons']">    <graphql>      query persons() {        name        age      }    </graphql>  </list>
  <fields>    <field id="personName">      <select mapping="['name']"/>    </field>    <field id="personAge">      <select mapping="['age']"/>    </field>  </fields></query>
Маппинг результатов выборки rest#
Запрос rest сервиса
GET /api/myentity/items
Ответ rest сервиса
{  "data" : [      {        "name" : "John",        "surname" : "Doe"      },      ...  ],  "cnt" : 123}
Маппинг результатов выборки из rest провайдера
<query>  <list>    <rest      result-mapping="data"      count-mapping="cnt">/api/myentity/items</rest>  </list>  <fields>    <field id="firstName">      <select mapping="['name']"/>    </field>    <field id="lastName">      <select mapping="['surname']"/>    </field>  </fields></query>
Маппинг результатов выборки java#
Метод java класса, возвращающий Spring Data Page
@Repositoryinterface MyRepository extends JpaRepository<MyEntity, Long> {  Page<MyEntity> findAll();}
class MyEntity {  private String name;  private String surname;  //getters and setters}
Маппинг результатов выборки в java провайдере
<query>  <list    result-mapping="content"    count-mapping="totalElements">    <java      class="com.example.MyRepository"      method="findAll">      <spring/>    </java>  </list>  <fields>    <field id="firstName">      <select mapping="['name']"/>    </field>    <field id="lastName">      <select mapping="['surname']"/>    </field>  </fields></query>
Маппинг результатов выборки в mongodb провайдере
<query>  <list>    <mongodb collection-name="user" operation="find"/>  </list>  <fields>      <field id="userAge" domain="integer">          <!-- маппинг определяет из какого поля результатов выборки из бд взять значение для userAge -->          <select mapping="['age']">age</select>      </field>  </fields></query>

Маппинг результатов операции#

Чтобы вернуть данные от провайдера, после выполнения операции, используется элемент <out>:

Маппинг результатов sql#
Получение результата выполнения sql провайдера
<operation>  <invocation>    <sql>INSERT INTO mytable (first_name, gender_id) VALUES (:first_name, :gender_id)</sql>  </invocation>  <out>    <field id="id" mapping="[0]"/>  </out></operation>

В примере результатом выполнения SQL запроса будет вставленная в таблицу запись. Эту запись можно получить обратным маппингом, где 0 - номер колонки вставленной записи.

Маппинг результатов rest#
Запрос rest
POST /api/myentity
Ответ rest
{  "result" : 123}
Получение результата выполнения rest провайдера
<operation>  <invocation>    <rest method="post">/api/myentity</rest>  </invocation>  <out>    <field id="id" mapping="['result']"/>  </out></operation>
Маппинг результатов mongodb#

Операция insertOne возвращает всегда id, операции updateOne, deleteOne и deleteMany не возвращают ничего, поэтому маппинг результатов имеет место только для insertOne.

Пример
<operation id="create">    <invocation>        <mongodb collection-name="user" operation="insertOne"/>    </invocation>    <in>        <field id="name" mapping="['name']"/>        <field id="age" mapping="['age']"/>    </in>    <out>        <field id="id" mapping="#this"/>    </out></operation>

Маппинг данных в Entity#

При использовании java провайдеров объект и выборка чаще всего работают с одной и той же сущностью. В N2O можно задать маппинг полей объекта на поля сущности в одном месте, и в дальнейшем не повторяться при выполнении операций, валидаций и выборок.

Для этого в объекте есть специальный атрибут entity-class и список полей <fields>:

Определение entity класса в объекте
<object  entity-class="com.example.MyEntity">  <fields>    ...<!--Маппинг полей Entity-->  </fields></object>
Маппинг простых полей сущности#

Поля делятся на простые и составные.

Простые поля имеют примитивный тип данных (Integer, String, Date и т.п.) Составные поля либо ссылаются на другие N2O объекты, либо имеют вложенные поля.

Класс сущности с простыми полями
@Entityclass MyEntity {  @Id  @Column  private Long id;  @Column  private Date birtDate;  //getters and setters}
Маппинг простых полей
<object entity-class="com.example.MyEntity">  <fields>    <!-- Простые поля -->    <field id="id" domain="long" mapping="['id']"/>    <field id="birthday" domain="date" mapping="['birthDate']"/>  </fields></object>

Атрибут id задаёт поле виджета, атрибут mapping - поле сущности.

Маппинг полей @ManyToOne и @OneToOne#
Класс сущности с @ManyToOne и @OneToOne
@Entityclass MyEntity {  @ManyToOne  private Gender gender;  @OneToOne  private Address addr;  //getters and setters}
Маппинг полей с @OneToOne
<fields>  <reference id="address"    mapping="['addr']">    <!-- Вложенные поля -->    <field id="home" domain="string"/>    <field id="work" domain="string"/>  </reference></fields>
Вариант с определением полей во внешнем файле
<fields>  <reference id="address"    mapping="['addr']"    object-id="address"/> <!-- Ссылка на объект address.object.xml --></fields>
address.object.xml
<!-- обратите внимание, что класс сущности может быть указан во внешнем файле --><object xmlns="http://n2oapp.net/framework/config/schema/object-4.0"        entity-class="org.example.Address">    <fields>      <field id="home" domain="string"/>      <field id="work" domain="string"/>    </fields></object>
note

Описывать поля можно внутри составного поля (reference, list или set) или во внешнем файле. Однако, если вы опишите поля в обоих местах, то более приоритетным будет вариант задания полей внутри составного поля. Все поля будут взяты из него, а для полей присутствующих в обоих файлах (т.е. с совпадающим id) будет произведено слияние в пользу текущего объекта.

Маппинг полей с @ManyToOne
<fields>  <reference id="sex"    mapping="['gender']"    required="true"    object-id="gender"/></fields>
Маппинг полей @OneToMany и @ManyToMany#

Поля объекта могут быть множественными. Есть несколько типов множественности:

Типы множественности

ТипОписание
listСписок значений
setНабор значений
Класс сущности с множественными полями
@Entityclass MyEntity {  @OneToMany  private Set<Status> statuses;  @ManyToMany  private List<Address> addrs;  //getters and setters}
Маппинг полей с @OneToMany и @ManyToMany
<fields>  <set id="statuses"    mapping="['statuses']">    <!--Вложенные поля-->    <field id="id" domain="integer"/>    <field id="name" domain="string"/>  </set>  <list id="addresses"    mapping="['addrs']"    object-id="address"/><!--Ссылка на объект--></fields>
Использование полей объекта#

Если поле было задано в полях объекта, то при описании операций объекта не требуется определять маппинг и прочие атрибуты, а также вложенную структуру. Достаточно задать только идентификаторы параметров. В случае, если поле будет присутствовать в обоих местах, то будет произведено слияние с приоритетом в пользу поля в операции.

@Serviceclass MyService {  MyEntity create(MyEntity entity) { ... }}
Использование полей объекта в операции
<object entity-class="com.example.Employee">  <!-- Маппинг и структура полей сущности -->  <fields>    <field id="name" mapping="['name']" normalize="..."/>    <reference id="org" mapping="['organization']" object-id="org"/>    <list id="departments" entity-class="com.example.Department">      <field id="id"/>      <field id="name"/>    </list>  </fields>
  <operations>    <operation>      <invocation>        <java method="create">          <arguments>            <argument type="entity"/>          </arguments>          <spring/>        </java>      </invocation>      <in>        <!-- Перечисление только нужных полей -->        <!-- Не нужно задавать ни структуру, ни атрибуты -->        <field id="name"/>        <reference id="org"/>        <list id="departments"/>      </in>      <out>        <field id="id"/>      </out>    </operation>  </operations></object>

Пример

Источники данных#

Источник данных - это способ получения данных на клиенте. С помощью него элементы страницы могут отображать свои данные. Источники могут задаваться на странице или в *.application.xml файлах с помощью <datasources>.

Стандартный источник данных#

Стандартные источники данных используются для отображения данных виджетов и, как правило, создаются на странице:

Источники данных на странице
<page xmlns="http://n2oapp.net/framework/config/schema/page-4.0">  <datasources>    <!-- Источники данных страницы -->    <datasource id="main"/>    <datasource id="docs"/>    <datasource id="addresses"/>  </datasources></page>

На источник данных можно сослаться из виджета:

Ссылка на источник данных
<form datasource="main">  ...</form>

Источник данных можно создать внутри виджета, если делиться своими данными виджету не требуется.

Вложенный источник данных
<form>  <datasource>    <!-- Источник данных виджета -->  </datasource></form>
note

Если у виджета никак не задан источник данных, то он создастся автоматически.

В источнике данных можно задать объект и выборку, чтобы загружать и сохранять данные через провайдеры. Выборку можно отфильтровать, а данные объекта валидировать:

Фильтры и валидации источника данных
<datasource id="main"            object-id="person"            query-id="person">    <filters>        ... <!-- фильтры выборки -->    </filters>    <validations>        ... <!-- валидации объекта -->    </validations></datasource>

Можно заполнить источник данных значениями по умолчанию:

Источник данных со значениями по умолчанию
<datasource id="main"            default-values-mode="defaults"/>

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

Источник данных со слиянием выборки и значений по умолчанию
<datasource id="main"            query-id="person"            default-values-mode="merge">    <filters>...</filters></datasource>

Источники данных можно передать на открываемую страницу:

Передача источников данных на открываемую страницу
<button label="Создать">    <open-page page-id="personCard">        <datasources>            ... <!-- Источники данных, которые будут переданы на страницу personCard -->        </datasources>    </open-page></button>

Если на открываемой странице уже существует источник данных с тем же id, то произойдет слияние двух источников данных в один. Приоритетным будет являться источник данных переданный из вне.

note

Для передачи источника данных на страницу simple-page можно не задавать id. Он будет сливаться с единственным источником данных который есть на странице.

Подробнее о стандартных источниках данных

Stomp источник данных#

Stomp источник данных получает данные с помощью STOMP протокола и задается элементом <stomp-datasource> в *.application.xml файлах.

Пожалуй наиболее частым случаем применения Stomp источника данных является асинхронное получение счетчиков.

Задание Stomp источника для получения счетчика и цвета
<application xmlns="http://n2oapp.net/framework/config/schema/application-2.0">    <datasources>        <!-- Stomp источник данных -->        <!-- клиент будет прослушивать данные, полученные по указанному destination -->        <stomp-datasource id="notifCount" destination="/notif">            <values>                <!-- значение счетчика на момент инициализации -->                <value count=""/>                 <!-- цвет значка на момент инициализации -->                <value color="success"/>            </values>        </stomp-datasource>    </datasources>    ...</application>

Здесь следует обратить внимание на обязательность задания атрибута destination. По этому пути клиент будет прослушивать информацию из STOMP протокола. С помощью values задаются значения при старте страницы, когда данные из STOMP еще не были получены.

Использование счетчика и цвета в элементах страницы
<application>    ...    <header title="Хедер">        <nav>            <!-- элемент меню, в котором отобразится обновленное значение счетчика и изменится цвет значка-->            <!-- атрибут должен ссылаться на имя поля, используемого в stomp-datasource (в данном случае count и color) -->            <menu-item id="notif"                       datasource="notifCount"                       icon="fa fa-bell"                       badge="{count}"                       badge-color="{color}">                ...            </menu-item>        </nav>    </header></application>

Подробнее о stomp источниках данных

Модели данных#

Источник данных загружает данные на клиент в формате json и перемещает их в Redux хранилище с ключем models.[model].[page_datasource], где

  • page - адрес страницы,
  • datasource - идентификатор источника данных страницы,
  • model - модель данных.

У источника данных может быть несколько моделей:

Модели данных

МодельОписаниеПример
datasourceВсе загруженные записиСписок записей у таблицы
resolveТекущая записьВыделенная запись таблицы
filterЗначения фильтровЗначения фильтров таблицы, заданные пользователем
multiВсе выделенные записиПомеченные чекбоксы в таблице
editНе сохраненные данныеФорма, открытая на редактирование, с изменёнными полями

На модель данных можно ссылаться, например, в зависимостях или в фильтрах:

Ссылка на модель данных в фильтрах
<datasource id="docs"            query-id="docs">    <filters>        <!-- Фильтр со значением, взятым из источника main с моделью resolve -->        <eq field-id="person.id"            value="{id}"            datasource="main"            model="resolve"/>    </filters></datasource>

На модель виджета так же ссылаются в действиях, чтобы задать область данных, над которой происходит действие.

Ссылка на модель данных в действиях
<button>   <!-- Вызов операции с данными из источника docs с моделью resolve -->    <invoke operation-id="delete"            datasource="docs"            model="resolve"/></button>