Chapter 27 · Application Integration: Drivers, Pools, Session State, Transactions, and Resilience

ORMs, SQL Visibility, N+1, Pagination, Schema Migrations, and Deployment Coordination

Make ORM-generated SQL visible, reproduce N+1 and deep OFFSET behavior, use keyset pagination, and deploy schema changes with expand/backfill/validate/contract plus DDL-lock and pool-version coordination.

Advanced130–150 minutesN+1/pagination/expand-contract migration labDDL lock/time-out evidenceDrain old pools before contractLast reviewed: August 2026

Learning outcomes

ServiceHub adopts an Object-Relational Mapper (ORM). Unit tests pass, but production executes one technician query per returned work order, deep pagination gets slower, and an automatic migration waits on a DDL lock while old application instances still expect the previous schema. An ORM is an application abstraction; Oracle still executes SQL, holds locks, uses plans, and enforces schema contracts.

01

Detect N+1 from generated SQL/module execution counts and repair it with set-oriented fetching when semantics allow.

02

Explain deep OFFSET costs and implement deterministic keyset pagination.

03

Use DBMS_XPLAN runtime evidence before blaming or hinting ORM SQL.

04

Reproduce ORA-00054 from a DDL lock conflict and identify the blocker safely.

05

Deploy backward-compatible schema changes through expand/backfill/validate/contract while coordinating old/new binaries and connection pools.

Generation-time baseline, drivers, licensing, services, and lab boundary

Mandatory database examples target Oracle AI Database Free 26ai, reviewed against RU 23.26.3, SQL Developer 26.2, SQLcl 26.2.1.222.1617, JDBC/UCP 23.26.3.0, ODP.NET 23.26.3, python-oracledb 4.0.2, node-oracledb 7.0.1, and Oracle Instant Client 23.26.3 where Thick mode is discussed. Free is limited to 2 foreground CPU cores, 2 GB database RAM, 12 GB user data, and one installation per logical environment, with no Release Update patching or Oracle Support SRs. The course baseline is CDB/instance FREE, application PDB/service FREEPDB1 on port 1521, owner SERVICEHUB_OWNER, and runtime user SERVICEHUB_APP. JDBC Thin is the preferred Java path; JDBC OCI/Type 2 is deprecated in 26ai. python-oracledb and node-oracledb default to Thin mode and use Oracle Client libraries only if Thick mode is explicitly initialized. Application Continuity and Transaction Guard are not licensed in Free. On EE/EE-ES, Application Continuity requires Active Data Guard, RAC One Node, or RAC. DRCP and RESET_STATE are separate mechanisms. TCPS examples require a configured TLS listener/server certificate and trusted client configuration. No real password, wallet, private key, or provider secret is embedded, no mandatory lab changes COMPATIBLE, and no remote GitHub file is modified.

1. Make generated SQL observable

sql · request identity
BEGIN  DBMS_APPLICATION_INFO.SET_MODULE(    'SH27_ORM_API','LIST_WORK_ORDERS'  );  DBMS_SESSION.SET_IDENTIFIER('req-orm-001');END;/
sql · server-side SQL evidence
SELECT  sql_id, executions, parse_calls, rows_processed,  buffer_gets, elapsed_time,  substr(sql_text,1,160) AS sql_textFROM v$sqlWHERE module='SH27_ORM_API'ORDER BY last_active_time DESC;

Capture generated SQL with bind types/values as permitted by security policy. The optimizer sees SQL and statistics, not entity classes.

2. Build an N+1/pagination dataset

sql · setup
BEGIN EXECUTE IMMEDIATE 'DROP TABLE sh27_orm_order PURGE';EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;/BEGIN EXECUTE IMMEDIATE 'DROP TABLE sh27_orm_tech PURGE';EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;/CREATE TABLE sh27_orm_tech (  technician_id NUMBER PRIMARY KEY,  technician_name VARCHAR2(80) NOT NULL);CREATE TABLE sh27_orm_order (  work_order_id NUMBER PRIMARY KEY,  technician_id NUMBER NOT NULL    REFERENCES sh27_orm_tech(technician_id),  status_code VARCHAR2(12) NOT NULL,  created_at TIMESTAMP NOT NULL);INSERT INTO sh27_orm_techSELECT LEVEL,'Tech '||LEVELFROM dual CONNECT BY LEVEL <= 100;INSERT INTO sh27_orm_orderSELECT  LEVEL,  MOD(LEVEL-1,100)+1,  CASE MOD(LEVEL,3) WHEN 0 THEN 'CLOSED' ELSE 'OPEN' END,  TIMESTAMP '2026-01-01 00:00:00'    + NUMTODSINTERVAL(LEVEL,'MINUTE')FROM dual CONNECT BY LEVEL <= 500;CREATE INDEX sh27_orm_order_page_ixON sh27_orm_order(created_at,work_order_id);COMMIT;BEGIN  DBMS_STATS.GATHER_TABLE_STATS(USER,'SH27_ORM_ORDER',cascade=>TRUE);  DBMS_STATS.GATHER_TABLE_STATS(USER,'SH27_ORM_TECH');END;/

3. Deliberate N+1

python · ORM-like pseudocode
orders = orm.query(    "select work_order_id, technician_id "    "from sh27_orm_order where status_code = :status",    status="OPEN")for order in orders:    technician = orm.query_one(        "select technician_name from sh27_orm_tech "        "where technician_id = :id",        id=order.technician_id    )

One parent query plus N child queries creates N+1 database executions/round trips. Statement caching may reduce parsing but does not remove those executions.

4. Set-oriented repair

sql · one join
SELECT  o.work_order_id,  o.created_at,  t.technician_id,  t.technician_nameFROM sh27_orm_order oJOIN sh27_orm_tech t  ON t.technician_id=o.technician_idWHERE o.status_code='OPEN'ORDER BY o.created_at,o.work_order_id;

Use the ORM's eager-load/fetch-join or a purposeful query. For one-to-many collections, one huge join can duplicate parent columns heavily, so two batched set queries may be better. Measure.

5. OFFSET/FETCH needs deterministic total order

sql · offset pagination
SELECT  work_order_id, technician_id, created_atFROM sh27_orm_orderWHERE status_code='OPEN'ORDER BY created_at,work_order_idOFFSET :offset_rows ROWSFETCH NEXT :page_size ROWS ONLY;

The PK tiebreaker makes the order deterministic. Deep OFFSET still requires finding and discarding earlier ordered rows, so work can grow with page depth.

6. Keyset pagination seeks after the last key

sql · keyset/seek query
SELECT  work_order_id, technician_id, created_atFROM sh27_orm_orderWHERE status_code='OPEN'  AND (       created_at > :last_created_at       OR (            created_at = :last_created_at            AND work_order_id > :last_work_order_id          )      )ORDER BY created_at,work_order_idFETCH FIRST :page_size ROWS ONLY;

Keyset pagination fits next/previous feeds and continuation tokens; it does not naturally provide arbitrary “jump to page 847.” The matching ordered index can let Oracle seek rather than skip a huge prefix.

7. Prove actual row-source work

sql · runtime plan
SELECT /*+ gather_plan_statistics */  work_order_id, created_atFROM sh27_orm_orderWHERE status_code='OPEN'  AND (       created_at > :last_created_at       OR (            created_at = :last_created_at            AND work_order_id > :last_work_order_id          )      )ORDER BY created_at,work_order_idFETCH FIRST 25 ROWS ONLY;SELECT *FROM TABLE(  DBMS_XPLAN.DISPLAY_CURSOR(    NULL,NULL,'ALLSTATS LAST +PREDICATE'  ));

Compare actual rows, buffers, starts, and access path at representative continuation points before declaring the ORM or pagination style fast/slow.

8. DDL is lock-taking production work

Schema DDL needs object/metadata locks. An ORM migration framework can generate valid ALTER TABLE and still fail because a production transaction owns a conflicting DML/DDL lock.

9. Reproduce ORA-00054

sql · Session A
UPDATE sh27_orm_orderSET status_code='OPEN'WHERE work_order_id=1;-- Hold the transaction open.
sql · Session B
ALTER SESSION SET ddl_lock_timeout=3;ALTER TABLE sh27_orm_order  ADD (priority_label VARCHAR2(20));-- Typical result after the bounded wait:-- ORA-00054: resource busy and acquire with NOWAIT specified-- or timeout expired

The exact current message can include blocker/session details. The mechanism is lock conflict; simply increasing DDL_LOCK_TIMEOUT can turn a fast failure into a longer deployment stall.

10. Diagnose the owner of the lock

sql · observer
SELECT  sid, serial#, username, module,  status, event, blocking_session, sql_idFROM v$sessionWHERE username IN ('SERVICEHUB_OWNER','SERVICEHUB_APP')ORDER BY sid;SELECT  session_id, owner, name, type,  mode_held, mode_requestedFROM dba_ddl_locksWHERE name='SH27_ORM_ORDER';

Use module/action/request identity to find the owner before killing anything. Killing a large transaction can trigger a long rollback and worsen the deployment.

11. Repair and perform the expand step

sql · Session A
ROLLBACK;
sql · Session B
ALTER SESSION SET ddl_lock_timeout=10;ALTER TABLE sh27_orm_order  ADD (priority_label VARCHAR2(20));

The nullable column expands the schema while old code that ignores it remains compatible.

12. Backfill in bounded batches

sql · batch-shaped backfill
UPDATE sh27_orm_orderSET priority_label =  CASE    WHEN MOD(work_order_id,5)=0 THEN 'HIGH'    ELSE 'NORMAL'  ENDWHERE priority_label IS NULL  AND work_order_id BETWEEN :low_id AND :high_id;COMMIT;

Choose batch size from measured undo/redo/lock/replication latency, not from an ORM migration generator's convenience.

13. Enforce/validate after writers conform

sql · constraint rollout
ALTER TABLE sh27_orm_order  ADD CONSTRAINT sh27_priority_ck  CHECK (priority_label IN ('NORMAL','HIGH'))  ENABLE NOVALIDATE;-- After backfill and application writers comply:ALTER TABLE sh27_orm_order  ENABLE VALIDATE CONSTRAINT sh27_priority_ck;

ENABLE NOVALIDATE can enforce compliant new/changed rows before historical validation. Validation is an operational step with its own scan/resource/lock behavior and must be tested.

14. Contract only after old binaries and pools are gone

  1. Expand the schema compatibly.
  2. Deploy code able to tolerate both schema states and write the new shape.
  3. Backfill and validate.
  4. Prove old SQL/application versions no longer depend on the old contract.
  5. Drain/recycle old pods/processes and their connection pools.
  6. Contract/drop old columns/indexes/views in a separate deployment.

A pool can keep old application code/session state alive after DDL finishes. Schema compatibility is a fleet property, not merely “the migration table says success.”

15. Deliberately wrong: rename/drop in the same release as replacement

Old instances still serving traffic get invalid-identifier failures and application rollback cannot restore compatibility because the old schema contract vanished. Expand/contract preserves rolling-deploy and rollback windows.

16. Cleanup and production judgment

sql · cleanup
DROP TABLE sh27_orm_order PURGE;DROP TABLE sh27_orm_tech PURGE;BEGIN  DBMS_APPLICATION_INFO.SET_MODULE(NULL,NULL);  DBMS_SESSION.CLEAR_IDENTIFIER;END;/

Log/trace ORM-generated SQL safely, bind values, measure executions/round trips/plans before tuning, and treat schema migration as live database work with lock, undo/redo, compatibility, and rollback consequences. Separate expand from contract and drain old connection pools before destructive DDL.

The mandatory lesson uses core Free SQL. DDL_LOCK_TIMEOUT is changed only at session scope; no pack, restart, or COMPATIBLE change is required. Chapter 28 can now apply the same compatibility/rollback discipline to database patching and upgrades.

Check your understanding

  1. What is N+1?
  2. Why does pagination need a unique ORDER BY?
  3. What does keyset avoid compared with deep OFFSET?
  4. What does ORA-00054 during ALTER TABLE indicate here?
  5. Why drain old pools before contract?
Review the answers

One parent query causes one additional child query per returned row, multiplying executions/round trips.

Otherwise equal sort keys can move between pages and cause duplicates/skips.

It seeks from the prior ordered key instead of discarding an increasingly large prefix.

The DDL could not acquire the needed metadata/object lock because another session held a conflicting lock.

Old binaries/sessions may still execute SQL against the old schema contract; dropping it breaks rolling deployment/rollback.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.