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, Mongo DB.

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

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

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.

Пример

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"/>    <field id="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.

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

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

Провайдеры sql, rest и mongodb используют "ключ значение". Поэтому в маппинге нужно обращаться к ключу, например, ['name'].

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

Маппинг фильтров в sql, rest и 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>

Пример

Маппинг сортировки в 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>
Маппинг входных параметров операции 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"/>        <argument type="primitive"/>      </arguments>    </java>  </invocation>  <in>    <field id="a" mapping="[0]"/>    <field id="b" mapping="[1]"/>  </in></operation>
note

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

Маппинг сущности#
Метод 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>    <!-- Для type="entity" в mapping нет [0]. -->    <field id="firstName" mapping="name"/>    <field id="lastName" mapping="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>          <!-- Для type="criteria" в mapping нет [0]. -->          <more filter-id="birthdays.begin" mapping="['birthdayAfter']"/>          <less filter-id="birthdays.end" mapping="['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>
Маппинг результатов выборки 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>

Пример

Загрузка данных в виджет#

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

Источники данных виджета

ИсточникОписаниеСлучай использования
queryПолучение данных из выборки.Открытие формы на редактирование.
defaultsПолучение значений по умолчанию.Открытие формы на создание.
copyПолучение данных из выборки, за исключением полей с copied="false". При этом id будет null.Открытие формы для копирования существующей записи.
Источник данных на форме
<form  upload="query"  query-id="person">  ...</form>
Источник данных при открытии страницы
<button label="Создать">  <open-page    page-id="personCard"    upload="defaults"    submit-operation-id="create"/></button>

Модели виджета#

Данные виджета загружаются в формате json и хранятся в модели виджета. У виджета может быть несколько моделей:

Модели виджета

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

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

Ссылка на модель виджета в предустановленной фильтрации
<pre-filters>  <eq    field-id="org.id"    value="{id}"    ref-model="resolve"    ref-widget-id="organizations"/></pre-filters>

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

Ссылка на модель виджета в действиях
<button>  <invoke    operation-id="delete"    model="resolve"/></button>