Use OData $batch for multiple operations in one HTTP call. Implement deep inserts for compositions.
$batch request format and when to use itUI5 applications batch multiple OData calls into one HTTP request to reduce latency. Deep inserts are the standard pattern for creating an Order with its Items in a single transaction. CAP handles both patterns automatically, but you need to know what arrives at your handlers.
POST /orders/$batch
Content-Type: multipart/mixed; boundary=batch_1
--batch_1
Content-Type: application/http
POST /orders/Orders HTTP/1.1
Content-Type: application/json
{ "ID": "...", "status": "DRAFT" }
--batch_1
Content-Type: application/http
GET /orders/Orders?$top=5 HTTP/1.1
--batch_1--
CAP parses the multipart format, executes each request, and returns a multipart response. No handler code needed — it works automatically.
Each request in the batch runs in its own transaction.
If request 2 fails, requests 1 and 3 are not rolled back.
--batch_1
Content-Type: multipart/mixed; boundary=changeset_1
--changeset_1
Content-Type: application/http
POST /orders/Orders HTTP/1.1
{ ... }
--changeset_1
Content-Type: application/http
POST /orders/OrderItems HTTP/1.1
{ ... }
--changeset_1--
--batch_1--
Requests inside a change set share one transaction — if any fails, all are rolled back.
// POST /orders/Orders
{
"ID": "c0f0b8a0-0000-0000-0000-000000000001",
"status": "DRAFT",
"buyer": "alice",
"items": [
{ "book_ID": "book-001", "quantity": 2, "price": 29.99 },
{ "book_ID": "book-002", "quantity": 1, "price": 14.99 }
]
}
CAP inserts Orders first, then inserts each items row with order_ID set automatically — because items is a Composition in CDS.
entity Orders {
key ID : UUID;
// ...
items : Composition of many OrderItems on items.order = $self;
}
Deep inserts only work on Compositions — not Associations.
// The full nested payload arrives in req.data
this.before('CREATE', 'Orders', (req) => {
const { items } = req.data
if (!items || items.length === 0)
return req.reject(400, 'Order must have at least one item')
// Compute total from items
req.data.total = items.reduce((sum, i) => sum + i.price * i.quantity, 0)
})
// PATCH /orders/Orders('c0f0b8a0-...')
{
"status": "SUBMITTED",
"items": [
{ "ID": "item-001", "quantity": 3 }, // update existing item
{ "book_ID": "book-003", "quantity": 1, "price": 9.99 } // add new item
]
}
CAP deep-updates composition children: existing items are matched by key and updated; items without a key are inserted.
// Run multiple queries in sequence within one transaction
const tx = cds.transaction(req)
const [order, items] = await Promise.all([
tx.run(INSERT.into('Orders').entries(orderData)),
tx.run(INSERT.into('OrderItems').entries(itemsData.map(i => ({ ...i, order_ID: orderData.ID }))))
])
// Multipart response — each part has its own status
--batch_response_1
Content-Type: application/http
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": {
"code": "400",
"message": "Order must have at least one item"
}
}
--batch_response_1--
CAP automatically wraps errors in the multipart format.
// UI5 OData V4 model batches all requests in one changeset by default
const oModel = this.getView().getModel()
oModel.setAutoExpandSelect(true)
// All pending operations submitted as one $batch
oModel.submitBatch('myBatchGroup')
Orders with items: Composition of many OrderItemstotal from items on deep insert$batch is handled automatically by CAP — no handler code neededreq.data.items contains the nested children in a deep insert handler