Всем Арбуэ!
Такая штука интересная нужна в плане лейзи лоадинга(и не недогрузить и не перегрузить) и никак не вытанцовывается....
Суть вот в чём:
Данные: Есть таблица InputData - это денормализованная таблица, которая содержит в себе записи из трёх сущностей. После процессинга она перебирается и распаковывается в три таблицы: Amount, Account, Customer(эти самые сущности).
Соответственно, все они many-to-one (у кастомера много эккаунтов, а у эккаунтов много эмаунтов) А вот InputData <-> Amount - один к одному (по сути, InputData - это Amount с довесками Account и Customer).
Что делаем: Надо создать запись ReportData. Это таблица, которая содержит уже как бы аналитику на основе InputData данных. Она Many-to-one к InputData и остальным этим таблицам (аналитики много и всякой).
Что мы делаем - мы делаем запрос, вытягиваем исходные данные из InputData и Amount(Account, Customer), вычисляем, записываем в новенькую ReportData, линкуем её с имеющимися Amount, Account, Customer, InputData и записываем в БД.
Нюансы: Эта группа(сей кусок аналитики) довольно большая. Просто физически вытянуть весь рекордсет не выйдет. Поэтому пользуется iterate(), который пользует оракловый курсор. А это означает прямой запрет JOIN FETCH :(
Работать должна довольно быстро. Поэтому подвыборки отметаются. Это долго. Должно хватать одного селекта.
Проблема: Селект(чисто сгенерированый скльный, который идёт на нужды курсора) достаёт всё что надо... Но потом хибернейт то слишком ленится, то слишком усердствует в инициализации объектов.
Здесь происходит создание ReportData,заполнение линков, данных и её save. Комментами помечены строки где сетаются линки на объекты, которые мы вытянули своим запросом
| Код | final ReportData rd = dataBuilder.createReportDataForSubtran(DataBuilder.GROUPING_11_64); rd.setAmount(new BigDecimal(amount2).negate()); rd.setStatutoryCode(stc); rd.setReportType(reportType); rd.setInputRowAmount(amount); // lazy object rd.setAccount(account); // lazy object rd.setCustomer(customer); // lazy object rd.setInputData(inputData); // lazy object rd.setBank(bank); rdd.save(rd);
|
Для всего этого мы используем примерно такой HQL запрос(234-й вариант ):
| Код | SELECT ind, -- InputData object ind.amountRow, -- Amount object am.account, -- Account object am.account.customer, -- Customer object ind.guaranteeType, -- just a String ind.creditorSector, -- just a String ind.sectorCode, -- just a String ind.amount2 -- just a BigDecimal FROM InputData ind JOIN ind.amountRow am WHERE ind.bank.id = ? AND ind.amountType = '120' AND ind.origination <> 'MI' AND ind.origination <> 'correction' AND ind.ultimo <> true AND ind.recordStatus = 3
|
Теперь что интересно... если InputData указать в select clause как am.inputDataRow - то в выборке этот объект фигурирует как NULL (!) Просто типа нету InputData и всё! Customer Account - в наличии и здравии. Amount тоже. Конкретные поля из InputData тоже нормально вытянуты.. А вот объект InputData - отсуйствует.
Но если указать его явно по алиасу, типа: SELECT ind ... то он выбирается. Но тут же хибернейт запуливает туеву хучу сабселектов, которые тянут одиночными запросами InputDat'ы из базы. Это полностью сокрушает скорость.
Причём, такая лажа именно с объектом, который идёт первым после FROM. Если поменять запрос и отталкиваться от Amount - то будет такая же лажа с Amount вместо InputData
Генерит вот что(разные варианты были также):
| Код | select inputdata0_.P_INPUT_DATA_OID as col_0_0_, inputdata0_.P_INPUT_DATA_OID as col_1_0_, amount1_.F_ACC_NUMBER as col_2_0_, account3_.F_CUSTOMER_ID as col_3_0_, inputdata0_.GUARANTEE_TYPE as col_4_0_, inputdata0_.CREDITOR_SECTOR as col_5_0_, inputdata0_.SECTOR as col_6_0_, inputdata0_.AMOUNT_2 as col_7_0_ from vos_statistics_200812.INPUT_DATA inputdata0_, vos_statistics_200812.AMOUNT amount1_, vos_statistics_200812.ACCOUNT account3_, vos_statistics_200812.CUSTOMER customer5_ where inputdata0_.P_INPUT_DATA_OID=amount1_.F_INPUT_DATA_ID and amount1_.F_ACC_NUMBER=account3_.P_ACC_OID and account3_.F_CUSTOMER_ID=customer5_.P_CUSTOMER_OID and inputdata0_.F_BANK_ID=? and inputdata0_.AMOUNT_TYPE='120' and inputdata0_.ORIGINATION<>'MI' and inputdata0_.ORIGINATION<>'correction' and inputdata0_.IS_ULTIMO<>1 and inputdata0_.RECORD_STATUS=3
|
Айдишки есть... Но объект-заглушку почему-то не создаёт :(
Customer и Account прекрасно управляются только с айдишкой. Достал их, переложил в репортдату - сейв, готово. Остальные проперти мирно дрыхнут в БД.
Хочу просто доставать Amount и перекладывать в ReportData без всяких сабселектов...
Порезал много пропертей, которые будут запутывать. Если что - выложу остальные материалы для размышлизма по запросу.
Итак, как победить нашего "союзника, который облегчает нам жизнь"? 
| Код |
<hibernate-mapping> <class name="com.edb.finance.product.bank.statistics.domain.Amount" table="AMOUNT" batch-size="50"> <id name="id"> <column name="P_AMOUNT_ROW_OID"/> <generator class="native"/> </id> <many-to-one name="account" class="com.edb.finance.product.bank.statistics.domain.Account" fetch="select"> <column name="F_ACC_NUMBER" not-null="false"> <comment>Reference to the account row</comment> </column> </many-to-one> <many-to-one name="inputData" class="com.edb.finance.product.bank.statistics.domain.InputData" column="F_INPUT_DATA_ID" unique="true" fetch="select"/> </class> </hibernate-mapping>
<hibernate-mapping> <class name="com.edb.finance.product.bank.statistics.domain.InputData" table="INPUT_DATA" batch-size="50"> <id name="id" > <column name="P_INPUT_DATA_OID"/> <generator class="increment"/> </id> <one-to-one name="amountRow" class="com.edb.finance.product.bank.statistics.domain.Amount" property-ref="inputData" fetch="select"/> </class> </hibernate-mapping>
<hibernate-mapping> <class name="com.edb.finance.product.bank.statistics.domain.Account" table="ACCOUNT"> <id name="id"> <column name="P_ACC_OID"/> <generator class="native"/> </id> <set name="amounts" inverse="true" lazy="true" fetch="select" > <key> <column name="F_ACC_NUMBER" not-null="false"> <comment></comment> </column> </key> <one-to-many class="com.edb.finance.product.bank.statistics.domain.Amount"/> </set> </class> </hibernate-mapping>
<hibernate-mapping> <class name="com.edb.finance.product.bank.statistics.domain.Customer" table="CUSTOMER"> <id name="id"> <column name="P_CUSTOMER_OID"/> <generator class="native"/> </id> <set name="accounts" inverse="true" lazy="true" fetch="select"> <key> <column name="F_CUSTOMER_ID" not-null="false"/> </key> <one-to-many class="com.edb.finance.product.bank.statistics.domain.Account"/> </set> </class> </hibernate-mapping>
<hibernate-mapping> <class name="com.edb.finance.product.bank.statistics.domain.ReportData" table="REPORT_DATA" batch-size="50"> <id name="id"> <column name="P_RPTD_OID" /> <generator class="native"/> </id> <many-to-one name="inputData" class="com.edb.finance.product.bank.statistics.domain.InputData" fetch="select"> <column name="F_INPUT_DATA_ID"> <comment>Keep track to input data</comment> </column> </many-to-one> <many-to-one name="inputRowAmount" class="com.edb.finance.product.bank.statistics.domain.Amount" fetch="select"> <column name="F_AMOUNT_ROW_OID" not-null="false"> <comment>Keep track to Amount data</comment> </column> </many-to-one> <many-to-one name="account" class="com.edb.finance.product.bank.statistics.domain.Account" fetch="select"> <column name="F_ACC_NUMBER" not-null="false" /> </many-to-one> <many-to-one name="customer" class="com.edb.finance.product.bank.statistics.domain.Customer" fetch="select"> <column name="F_CUSTOMER_ID" not-null="false" /> </many-to-one> </class> </hibernate-mapping>
|
|