Связывание с данными
В 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>
.
<field id="firstName"> <select>t.name as firstName</select></field>
Чтобы получить значение этого поля, алиас столбца и идентификатор поля выборки должны совпадать. Если они не совпадают можно использовать маппинг.
В теле <select>
записывается выражение, которое можно вставить в sql или rest запрос с помощью переменной select
.
<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
.
<filters> <eq>t.id = :id</eq></filters>
<list> <sql>SELECT t.name FROM mytable t WHERE :filters</sql></list>
#
Сортировка поля выборкиЧтобы отсортировать поле выборки по возрастанию или по убыванию, необходимо отправить эту информацию на вход в провайдер данных.
Выражение для отправки можно сформировать в теле элемента <sorting>
, которое можно вставить в sql или rest запрос с помощью переменной sortings
.
<field id="name"> <sorting>name :direction</sorting></field>
Переменная direction
содержит в себе направление сортировки: ASC
или DESC
.
Название переменной можно сменить с помощью маппинга.
<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 файле.
<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>
<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 сервисам.
<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>
<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 сервисам.
<query> <list> <rest>/api/myentity/items?{filters}&{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>
<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. Либо можно вызвать статический метод класса.
<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>
<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 базе данных.
<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>
<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
.
<query> ... <fields> <field id="gender.id" domain="integer"> ... </field> </fields></query>
<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<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>
<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>
<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>
<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<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>
<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>
#
Маппинг входных параметров операции restPOST /api/myentity
{ "firstName" : "John", "genderId" : 1}
<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 класса с примитивным типом аргументов:
package com.example;
class Calculator { public static Long sum(Long a, Long b) { return a + b; }}
Чтобы смапить значение поля ввода в примитивный аргумент 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 метода
#
Маппинг сущности@Serviceclass MyService { public Long create(MyEntity entity) { ... }}
class MyEntity { private String name; private String surname; //getters and setters}
Тип entity
может быть задан только один раз среди всех аргументов.
<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 для задания сортировок и паджинации |
@Serviceclass MyService { public List<MyEntity> getList(MyCriteria criteria) { ... }}
class MyCriteria extends Criteria { private Date birtdayBefore; private Date birtdayAfter; //getters and setters}
Тип criteria
может быть задан только один раз среди всех аргументов.
Маппинга сортировки и паджинации не предусмотрено, они передаются через базовый класс наследник.
<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>
.
Задача маппинга — задать соответствие между свойством вернувшегося объекта и полем выборки.
#
Маппинг результатов выборки sqlSELECT name as fname, surname as lname FROM mytable
<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>
#
Маппинг результатов выборки graphqlquery persons() {name age}
<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>
#
Маппинг результатов выборки restGET /api/myentity/items
{ "data" : [ { "name" : "John", "surname" : "Doe" }, ... ], "cnt" : 123}
<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@Repositoryinterface MyRepository extends JpaRepository<MyEntity, Long> { Page<MyEntity> findAll();}
class MyEntity { private String name; private String surname; //getters and setters}
<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>
<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<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 - номер колонки вставленной записи.
#
Маппинг результатов restPOST /api/myentity
{ "result" : 123}
<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>
:
<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@Entityclass MyEntity { @ManyToOne private Gender gender; @OneToOne private Address addr; //getters and setters}
<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>
<!-- обратите внимание, что класс сущности может быть указан во внешнем файле --><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) будет произведено слияние в пользу текущего объекта.
<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}
<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 источника данных является асинхронное получение счетчиков.
<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>