Практика код
Contents
Практика код#
Вычисление суммы без использования буфера#
Пример, где подсчитывается сумма по записям коллекции Oil_PlanFactoryShipPos:
//получаем все записи коллекции по родителю (объект rop)
Oil_PlanFactoryShipPosApi().byParent(rop)
//получаем только значение поля nQtyLoad
//в случае незаполненности поля необходимо иметь значение 0, иначе в дальнейшем сумма с null = null
.map(_.get(_.nQtyLoad).nvl(0.nn))
//сложение, результат которого будет обернут безопасной конструкцией Option
.reduceOption(_ + _)
//распаковка Option
//если внутри был null или в коллекции не было записей, то вернётся указанное значение 0.nn
.getOrElse(0.nn)
Группировка объектов с использованием null-типов#
При группировке объектов коллекций, в которых используется null-типы нужно учитывать, что метод groupBy
считает хэши группируемых объектов,
поэтому перед группировкой, нужно убедиться, что параметр, используемый для группировки, не равен null.
Пример, с NPE:
List(
asd(123.nl, "asd".ns),
asd(None.nl, "asd".ns), // У NLong с underlying = null хэш не посчитается
).groupBy(_.id).foreach { case (_, name) =>
println(name)
}
Перед использованием groupBy
необходимо отфильтровать значения или использовать метод nvl
.
Сравнение диапазона дат#
Допустим документы имеют поля dBeginDoc и dEndDoc.
Фильтр имеет 2 поля dBeginFilter и dEndFilter.
Чтобы найти все документы, которые началом или окончанием входят в диапазон, указанный в фильтре, нужно использовать следующее условие:
dBeginDoc <= dEndFilter && dEndDoc >= dBeginFilter
.distinct или .toSet для scala-коллекций и особенности применения#
Если необходимо в scala-коллекции держать только уникальные объекты, можно использовать методы:
.distinct
.toSet
Перед использованием метода .distinct
scala-коллекцию необходимо подготовить: убрать значения null. Иначе в ходе выполнения программы выпадет ошибка java.lang.NullPointerException
.
Пример использования и демонстрация поведения методов:
test("distinctOrToSet") {
val data: Seq[NString] = Seq("h".ns, "i".ns, "i".ns, None.ns, None.ns)
println("Результат работы .distinct с null внутри scala-коллекции:")
try {
println(data.distinct)
} catch {
case e: Throwable => println("Ошибка:\n" + "java.lang.NullPointerException")
} finally {
println("\nРезультат работы .filter(_.isNotNull).distinct:")
println(data.filter(_.isNotNull).distinct)
try {
println("\nРезультат работы .toSet с null внутри scala-коллекции:")
println(data.toSet)
} catch {
case e: Throwable => println("Ошибка:\n" + "java.lang.NullPointerException\n")
}
}
}
Результат:
Примечание
Результат работы .distinct с null внутри scala-коллекции: Ошибка: java.lang.NullPointerException
Результат работы .filter(_.isNotNull).distinct: List(h, i)
Результат работы .toSet с null внутри scala-коллекции: Set(h, i, Null)
immutable.Map.builder
вместо mutable.Map
#
Если по результату сформированной Map она больше не изменяется, то для формирования лучше использовать конструктор immutable.Map.builder
, чем mutable.Map
.
Пример:
val map = Map.newBuilder[NString, NString]
map ++= Map("1" -> "11")
map += "2" -> "22"
map.result() //Map("1" -> "11", "2" -> "22")
Признак наличия модуля на проекте#
val isInstallProModule = session.sbtClassLoader.getModuleMap.containsKey("pro")
Вернёт true, если такой модуль есть в проекте, иначе false.
Предупреждение
Поддержка этого метода не гарантируется.
Применение ASQL/ ASelect/ OQuery/ TxIndex/ refreshByParent и byParent#
Про данные инструменты можно почитать здесь.
ASQL#
Выполнение реляционного запроса на чтение.
Внимание
Вызывает транзакцию в БД, не учитывая значения в кэше.
Удобен для получения значения по одному столбцу результата запроса.
val idaWagonByTrain = ASQL"""
select string_agg(cast(rwt.idWagon as varchar), ', ')
from Rzd_TrainWagon rwt
where rwt.idTrain = $idpTask
"""
//берется 1ый столбец результата запроса, как NString
//если результат запроса вернул несколько строк, то будет взята первая в конструкции Option,
// но предполагается, что результат вернёт максимум 1 строку
.as(nStr(1).singleOpt)
//если Option пустой, т.е. в результате запроса не было строк, то вернётся None.ns
.getOrElse(None.ns)
$idpTask
это подстановка значения из переменной scala idpTask
в запрос связанной переменной (binding).
Внимание
Нельзя использовать внутри цикла. Это приведёт к многочилсенным транзакциям в БД.
Вместо использования ASQL
внутри цикла с множественными транзакциями в БД нужно перед циклом одной транзакцией сформировать массив данных, который дальше будет использоваться внутри цикла.
ATSQL#
Выполнение реляционного запроса с изменением данных или блокировками.
Используется редко, в основном в ядровых процедурах, потому что минует серверную логику, записи в системные миксины, ведение аудитов и другое.
ASelect#
Выполнение реляционного запроса на чтение/запись.
Внимание
Вызывает транзакцию в БД, не учитывая значения в кэше.
Используется в основном для чтения, потому что при изменении данных минует серверную логику, записи в системные миксины, ведение аудитов и другое.
Удобен для получения значений по нескольким столбцам результата запроса.
val mapPerson: Map[NLong, NString] = new ASelect {
val sCode = asNString("sCode")
val id = asNLong("id")
SQL"""
select p.id
,p.sCode
from Bs_Person p
where idObjectType = $idvObjectType
"""
}.map { rv => //rv представляет собой одну строку результата запроса
rv.id() -> rv.sCode()
}.toMap
$idvObjectType
это подстановка значения из переменной scala idvObjectType
в запрос связанной переменной (binding).
Внимание
Нельзя использовать внутри цикла. Это приведёт к многочилсенным транзакциям в БД.
Вместо использования ASelect
внутри цикла с множественными транзакциями в БД нужно перед циклом одной транзакцией сформировать массив данных, который дальше будет использоваться внутри цикла.
Когда использовать ASQL, а когда ASelect#
ASQL удобен для получения значения по одному столбцу результата запроса.
ASelect удобен для получения значений по нескольким столбцам результата запроса.
Подстановка связанных переменных (binding)#
Актуально для инструментов ASQL, ATSQL, ASelect.
Примечание
Использование ASQL с подстановкой связанных переменных является полезной практикой, потому что запрос остаётся неизменным, меняется лишь значение параметра. Тем самым запрос не воспринимается системой, как новый, и будет записан в системную таблицу запросов единожды, что не приводит к распуханию БД.
Инструмент ASQL"""<текст запроса>"""
подставляет бинды с учётом типа данных переменной.
Если бы была подстановка
NString
, тоASQL
сам обернул бы значение в одинарные кавычки, т.е. нет необходимости их указывать вручную.Если
NString
подставляется в текстs"""<текст запроса>"""
, то одинарные кавычки необходимо указывать вручную.
Если текст запроса для ASQL"""<текст запроса>"""
собирается динамически средствами scala, в том числе название таблицы для select формируется переменной, то его необходимо подставлять через #$bind
, чтобы ASQL не обернул подставляемое значение в одинарные кавычки.
val sNameTb = {
if (a = 1) "Bs_Goods".ns
else "Bs_Person".ns
}
val ida: List[NLong] = ASQL"""
select t.id
from #$sNameTb
where idObjectType = $idvObjectType
""".as(nLong(1).*)
Существует иной формат binding, когда запрос формируется в переменной String/NString,
где указываются ключи для подстановки значения, карта подстановки указывается в .on()
val svRequest =
s"""
select t.id
from Bs_Goods
where idObjectType = {idObjectType}
"""
ASQL(svRequest).on("idObjectType" -> idvObjectType)
new ASelect {
val sCode = asNString("sCode")
val id = asNLong("id")
SQL(s"""
select p.id
,p.sCode
from Bs_Person p
where idObjectType = {idObjectType}
""")
.on("idObjectType" -> idvObjectType)
}.map { rv => //rv представляет собой одну строку результата запроса
rv.id() -> rv.sCode()
}.toMap
Может быть полезно, если запрос собирается в переменной String/NString.
OQuery#
Объектный запрос
Синтаксис схож с реляционным запросом
Возвращает результат с учётом кэша
Результат - список Rop, что удобно для использования методов, которым требуется Rop
Пример запроса:
new OQuery(Bs_GoodsAta.Type) {
where(t.sSystemName === spMnemoCode)
}.toVector
Особенности, которые нужно знать про OQuery:
Условие применяется для запроса в БД#
Условие применяется для запроса в БД. Поэтому поиск записей идёт в БД без учёта кэша, а результат возвращается с учётом кэша. Пример:
val idvGds = 121.nl
val ropGds = Bs_GoodsApi().load(idvGds)
//ТМЦ переводится в состояние Отменено (0)
Bs_GoodsApi().setidState(ropGds, idvStateCancel)
//Поиск неотмененных ТМЦ с условием по категории
new OQuery(Bs_GoodsAta.Type) {
where(t.idCategory = idvGdsCategory
and t.idStateMC >> 0.nn)
}.toVector
В результате OQuery будет запись Bs_Goods c id = 121, при чём с состоянием «Отменено» , потому что:
в БД эта запись не Отменена (t.idStateMC > 0) , а изменение по отмене записи находится в кэше и не синхронизировано с БД
результат OQuery возвращается с учётом кэша , поэтому на запись c id = 121 применён кэш - смена состояния
Решить можно 2 способами:
/** Вариант 1 - синхронизация с БД */
//синхронизация с БД
session.flush()
//Поиск неотмененных ТМЦ с условием по категории
new OQuery(Bs_GoodsAta.Type) {
where(t.idCategory = idvGdsCategory
and t.idStateMC >> 0.nn)
}.toVector
/** Вариант 2 - вынос условия из OQuery, фильтрация результата OQuery */
//Поиск неотмененных ТМЦ с условием по категории
new OQuery(Bs_GoodsAta.Type) {
where(t.idCategory = idvGdsCategory)
}
//вынос условия из OQuery
.filter(_.get(_.idStateMC) > 0.nn)
Предупреждение
Вариант 1 - синхронизация с БД с помощью session.flush() имеет недостаток: у пользователя пропадает возможность отменить изменения на выборке.
Вариант 2 - вынос условия из OQuery, фильтрация результата OQuery имеет недостаток: OQuery в память выгружает больше записей из БД, что может требовать приемов по работе с большими данными (см. раздел «Разработка под высокую нагрузку»).
Для предварительной прогрузки записей в кэш необходима инициализация OQuery#
Про предварительную прогрузку записей в кэш можно прочитать здесь.
Чтобы данные были загружены в кэш, необходима инициализация OQuery
, которая происходит при использовании обходчика foreach
или преобразовании OQuery к иной коллекции:
//Предварительная выгрузка записей в кэш
new OQuery(Bs_GoodsAta.Type) {
where(t.sSystemName === spMnemoCode
and t.idStateMC >> 0.nn)
}
//для инициализации OQuery
.toVector
Транзакционный индекс#
Объектный запрос
Поиск по одному полю по условию равенства
Нет возможности искать по составному индексу из нескольких полей класса
Ищет и возвращает результат с учётом кэша
Результат - список Rop, что удобно для использования методов, которым требуется Rop
Пример запроса:
//Объявляется обычно в Api класса соответствующего Ata (у примера Bs_GoodsApi)
lazy val idxCategory = TxIndex(Bs_GoodsAta.Type)(_.idCategory)
/** Поиск неотмененных ТМЦ по категории */
def byCategoryNotCanceled(): Iterable[ApiRop] = {
idxCategory.byKey(idvGdsCategory)
.filter(_.get(_.idStateMC) > 0.nn)
}
Если нужен запрос по нескольким условиям,
то используйте индекс по полю, который вернёт меньшее кол-во записей в память,
после чего примените остальные условия используя .filter()
.
Если транзакционный индекс используется в цикле, то необходимо предварительно загрузить результаты в память по множеству ключей. Об этом можно почитать здесь.
refreshByParent и byParent#
Объектный запрос
Имеется только у коллекций
Поиск по ссылочному полю на мастера
Возвращает результат с учётом кэша
Результат - список Rop, что удобно для использования методов, которым требуется Rop
refreshByParent
инвалидирует записи из БД и возвращает актуальные данные, гарантированно делает транзакцию в БД.byParent
возвращает данные из памяти, если они там были, иначе первично получает актуальные данные из БД, делая транзакцию.Перед использованием
byParent
в цикле для предварительной прогрузки в кэш можно использоватьOQuery
(предварительная прогрузка.)
Про разницу между refreshByParent
и byParent
можно почитать
здесь.
Какой инструмент использовать#
Если нужны записи с учётом кэша, то используйте:
объектный запрос
реляционный запрос с предварительным session.flush (не для построения выборок)
Предупреждение
Синхронизация с БД с помощью session.flush() имеет недостаток: у пользователя пропадает возможность отменить изменения на выборке.
Для построения выборок, в которых ведутся изменения объекта, используйте объектный запрос.