Перейти к основному содержанию
`Ctrl`+`K`

# Работа с кэшем

## Применение refreshByParent и refreshByKey

Методы `refreshByParent` и `refreshByKey` всегда перезапрашивают данные из БД, плюс берут данные которые находятся в кэше, чтобы все данные были актуальными.

Методы `byParent` и `byKey` берут данные из кэша, но если идет первое обращение по ключу, то тоже сначала обращаются к БД. Если было обращение `byParent` и `byKey` по ключу и затем уже произошла вставка новых записей по этим же ключам, то последующие обращения могут вернуть не полный перечень данных.

Поэтому при написании логики важно понимать какой метод использовать, чтобы не было постоянных запросов к БД и в то же время не было не корректных данных. Например, при переводе состояния документа, так как коллекции уже не будут добавляться, то для них следует использовать byParent, а не refreshByParent. А например, при удалении документа, следует использовать `refreshByParent`, чтобы не получить ошибки о зависимых объектах.

## Применение Session.commit, Session.commitWork, flush и flush(true)

`session.flush` - применяет изменения сессии к базе данных без завершения транзакции. Есть параметр `cleanTransactCache`, если true очищает транзакционный кэш (загруженные объекты) после операции, обнуляет счетчик загруженных ячеек. Если false (по умолчанию), то происходит только синхронизация данных в БД, загруженные объекты остаются в кеше.

`session.commit` - применяет изменения сессии к базе данных с успешным завершением транзакции, очищает транзакционный кэш после операции, обнуляет счетчик загруженных ячеек.

`session.commitWork` - применяет изменения сессии к базе данных без завершения завершения транзакции, очищает транзакционный кэш после операции, обнуляет счетчик загруженных ячеек.

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

          var counter = 0
          for (rv <- new ASelect {
            val id: NLongColumn = asNLong("id")
            val idBalAcc: NLongColumn = asNLong("idBalAcc")
            val nSum: NNumberColumn = asNNumber("nSum")
            SQL(sQuery)
            on("dExecDate" -> dExecDate, "idaDep" -> LongPgArray(idaDep), "idaEmployee" -> LongPgArray(idaEmployee), "idaAcc"-> LongPgArray(idaAcc.toList), "idBisObj" -> idBisObj, "idDepOwner" -> idDepOwner)
          }) {
            counter += 1
            insertByParent(Asf_InventoryApi().load(idpInventory)) :/ { rop =>
              setnOrder(rop, counter.nn)
              setidInvNumb(rop, rv.id())
              setidAcc(rop, rv.idBalAcc())
              setnSum(rop, rv.nSum())
              setnQtyRemain(rop, 1.nn)
              rop
            }
            if (counter % 1000 === 0)
              session.flush(true)
          }

          session.flush(true)

## Загрузка множества объектов

Если требуется загрузить множество объектов, с последующим использованием `rop`, например для изменения, то можно использовать `OQuery` или `tx` индекс или `byParent` (для коллекций), в других случаях можно обойтись запросами через ATSQL или ASelect, чтобы не загружать оперативную память сервера лишними данными.

1. Используйте OQuery когда нужны `rop`, нет соединений с другими таблицами, условия фильтрации идут по одному или нескольким полям и все объекты уже обязательно находятся в БД.

2. Используйте `tx` индекс или `byParent` (для коллекций) когда нужны rop, нет соединений с другими таблицами, основное условие фильтрации идет по одному полю (доп. условия можно накладывать через filter) и объекты могут не находится в БД.

3. Используйте ATSQL или ASelect когда `rop` не нужны, но нужны данные по одному или нескольким столбцам, могут быть соединения с другими таблицами, могут быть сложные условия фильтрации и все объекты уже обязательно находятся в БД.

4. Используйте `Btk_BulkProcessPkg()`.`chunkedQuery ко`гда нужны `rop`, могут быть соединения с другими таблицами, могут быть сложные условия фильтрации и все объекты уже обязательно находятся в БД. Так же в этом случае можно применить п.3 для нахождения списка идентификаторов и затем применить п.1

## Предварительная прогрузку данных в кэш через batchIn и queryKeys

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

Пример прогрузки для `tx` индекса:

        //прогружаем данные из БД по всем ключам в кэш через queryKeys
        idxGidSrcObject.queryKeys(gidapSrc)
        //при использовании byKey по этим ключам, данные будут браться уже из кэша
        val result = gidapSrc.flatMap(idxGidSrcObject.byKey)

Пример прогрузки с помощью `OQuery`:

        //прогружаем данные из БД с коллекциями через batchIn
        new OQuery(entityAta.Type) {
          where(t.id === idp)
          batchIn(Stk_WarrantInDetAta, Stk_WarrantInDetItemAta, Stk_ObjNeedDetAta, Stk_DMDKByDocAta, Stk_OperationAta)
        }.foreach { rop => }
        //при использовании load или byParent, данные будут браться уже из кэша, если они были в запросе в OQuery
        val rop = Stk_WarrantInApi().load(idp)
        val ropDet = Stk_WarrantInDetApi().byParent(rop)

## Массовая обработка объектов через Btk_BulkProcessPkg().chunkedQuery

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

        Btk_BulkProcessPkg().chunkedQuery(
            Asf_InvNumbSumRegApi(),
            5000,
            s"""
                select t.id
                from Asf_InvNumbSumReg t
                where t.gidSrc in  (select unnest({gidap}))
                """,
            Seq("gidap" -> GidPgArray(idap.map(id => getGid(id))))
          ) { ropa =>
            ropa.foreach { rop =>
              if (rop.exists())
                Asf_InvNumbSumRegApi().delete(rop)
            }
            session.flush(true)
          }

## При массовой регистрации объектов используйте Btk_BulkProcessPkg().findObjects.

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

        case class objStatusPlanReg(idGdsPlanReg: NLong
                                    , idEndowStatus: NLong)
        val avObjStatusPlanReg = ArrayBuffer[objStatusPlanReg]()
    ...
        Btk_BulkProcessPkg().findObjects(
          filters = avObjStatusPlanReg.toVector,
          api = this,
          sqlConditions =
            s"""t.idGdsPlanReg = f.idGdsPlanReg"""
        ) { res =>
          //если запись найдена она будет прогружена в кэш, если не нашли запись то создаем ее
          val rop = res.getRopOrElseUpdate {
            insert() :/ { rop =>
              setidGdsPlanReg(rop, res.filter.idGdsPlanReg)
              rop
            }
          }
          if (res.filter.idEndowStatus.isDistinct(rop.get(_.idEndowStatus))) {
            setidEndowStatus(rop, res.filter.idEndowStatus)
          }
        }