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

Применяйте правильно byParent и byKey, вместо 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)
      }
    }