This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new f345ef6a17a6 CAMEL-26148: camel-sql - sql and sql-stored batch update 
count and single Map body (#28918)
f345ef6a17a6 is described below

commit f345ef6a17a64734d31147c5ca6c3f476c7b7231
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 20:45:21 2026 +0100

    CAMEL-26148: camel-sql - sql and sql-stored batch update count and single 
Map body (#28918)
    
    * CAMEL-26148: camel-sql - sql-stored batch update count and single Map…
    
    … body
    
    With batch=true, sql-stored summed the counts of executeBatch into the
    CamelSqlStoredUpdateCount header, including Statement.SUCCESS_NO_INFO (-2),
    so a batch of 2 items on HSQLDB reported -4. Only the known (positive)
    counts are added now.
    
    A single Map body (or CamelSqlStoredParameters header) was converted to an
    Iterator of its entries, so each entry became a batch item and the call
    failed with an unclear SQLException. A single Map is now one batch item.
    
    * CAMEL-26148: camel-sql - sql batch update count and single Map body
    
    The sql producer had the same two batch bugs as sql-stored. It summed
    Statement.SUCCESS_NO_INFO (-2) into CamelSqlUpdateCount, so the header
    could be negative; only the known (positive) counts are added now. A
    single Map body (or CamelSqlParameters header) was iterated as its
    entries, so the named parameters were not found; it is now one batch item.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../camel/catalog/components/sql-stored.json       |   6 +-
 .../org/apache/camel/catalog/components/sql.json   |   4 +-
 .../apache/camel/catalog/docs/sql-component.adoc   |   4 +
 .../camel/catalog/docs/sql-stored-component.adoc   |  18 ++
 .../org/apache/camel/component/sql/sql.json        |   4 +-
 .../camel/component/sql/stored/sql-stored.json     |   6 +-
 .../camel-sql/src/main/docs/sql-component.adoc     |   4 +
 .../src/main/docs/sql-stored-component.adoc        |  18 ++
 .../apache/camel/component/sql/SqlConstants.java   |   5 +-
 .../apache/camel/component/sql/SqlEndpoint.java    |   4 +-
 .../apache/camel/component/sql/SqlProducer.java    |  20 ++-
 .../component/sql/stored/SqlStoredConstants.java   |   6 +-
 .../component/sql/stored/SqlStoredEndpoint.java    |   8 +-
 .../component/sql/stored/SqlStoredProducer.java    |  21 ++-
 .../camel/component/sql/SqlProducerBatchTest.java  | 198 +++++++++++++++++++++
 .../component/sql/stored/ProducerBatchTest.java    |  44 +++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  12 ++
 .../endpoint/dsl/SqlEndpointBuilderFactory.java    |   5 +-
 .../dsl/SqlStoredEndpointBuilderFactory.java       |   5 +-
 19 files changed, 365 insertions(+), 27 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql-stored.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql-stored.json
index 16606cd4dc34..a7abb1450ff7 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql-stored.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql-stored.json
@@ -33,11 +33,11 @@
   "headers": {
     "CamelSqlStoredTemplate": { "index": 0, "kind": "header", "displayName": 
"", "group": "producer", "label": "producer", "required": false, "javaType": 
"String", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "The stored-procedure template to execute. This 
header is ignored unless the endpoint enables allowTemplateFromHeader=true 
(disabled by default); when enabled it overrides the endpoint-configured 
template.", "constantName": "org.apa [...]
     "CamelSqlStoredParameters": { "index": 1, "kind": "header", "displayName": 
"", "group": "producer", "label": "producer", "required": false, "javaType": 
"Iterator", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "The parameters", "constantName": 
"org.apache.camel.component.sql.stored.SqlStoredConstants#SQL_STORED_PARAMETERS"
 },
-    "CamelSqlStoredUpdateCount": { "index": 2, "kind": "header", 
"displayName": "", "group": "producer", "label": "producer", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "important": true, "description": "The update count", 
"constantName": 
"org.apache.camel.component.sql.stored.SqlStoredConstants#SQL_STORED_UPDATE_COUNT"
 }
+    "CamelSqlStoredUpdateCount": { "index": 2, "kind": "header", 
"displayName": "", "group": "producer", "label": "producer", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "important": true, "description": "The update count. 
With batch=true, the sum of the update counts of the batch items; an item the 
JDBC driver returns no count for (Statement.SUCCESS_NO_INFO) is not counted, so 
the value can be lower than the  [...]
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": "batch", 
"value": "true", "carriers": [ "list", "iterator" ] }, { "option": 
"useMessageBodyForTemplate", "value": "true", "carriers": [ "text" ] } ], 
"description": "What the expressions of the IN parameters read: a :#name 
parameter takes its value from a Map body, or else from the header. With batch, 
a List or an Iterator of such bodies, one batch item each (a single Map is not 
a batch); with useMessageBodyForTemplate, [...]
-    "producerOut": { "carriers": [ "map" ], "javaType": "java.util.Map<String, 
Object>", "dependsOn": [ "outputHeader" ], "byOption": [ { "option": "noop", 
"value": "true", "carriers": [ "unchanged" ] }, { "option": "batch", "value": 
"true", "carriers": [ "unchanged" ] } ], "description": "A Map of the names of 
the OUT and INOUT parameters and their values, with a result set the procedure 
returns under #result-set-1 (a List of Maps) and the update count under 
#update-count-1, which is al [...]
+    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": "batch", 
"value": "true", "carriers": [ "list", "iterator", "map" ] }, { "option": 
"useMessageBodyForTemplate", "value": "true", "carriers": [ "text" ] } ], 
"description": "What the expressions of the IN parameters read: a :#name 
parameter takes its value from a Map body, or else from the header. With batch, 
a List or an Iterator of such bodies, one batch item each (a single Map is one 
batch item); with useMessageBodyFo [...]
+    "producerOut": { "carriers": [ "map" ], "javaType": "java.util.Map<String, 
Object>", "dependsOn": [ "outputHeader" ], "byOption": [ { "option": "noop", 
"value": "true", "carriers": [ "unchanged" ] }, { "option": "batch", "value": 
"true", "carriers": [ "unchanged" ] } ], "description": "A Map of the names of 
the OUT and INOUT parameters and their values, with a result set the procedure 
returns under #result-set-1 (a List of Maps) and the update count under 
#update-count-1, which is al [...]
   },
   "properties": {
     "template": { "index": 0, "kind": "path", "displayName": "Template", 
"group": "producer", "label": "", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "supportFileReference": true, 
"largeInput": true, "inputLanguage": "sql", "description": "Sets the stored 
procedure template to perform. You can externalize the template by using file: 
or classpath: as prefix and specify the location of [...]
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql.json
index 1c905fbc57cb..4641f8fd3aec 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/sql.json
@@ -39,7 +39,7 @@
   },
   "headers": {
     "CamelSqlQuery": { "index": 0, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"String", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Query to execute. This header is ignored 
unless the endpoint enables allowQueryFromHeader=true (disabled by default); 
when enabled it takes precedence over the query specified in the endpoint URI. 
Note that query parameters in the header ar [...]
-    "CamelSqlUpdateCount": { "index": 1, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"Integer", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "important": true, "description": "The number of rows updated 
for update operations, returned as an Integer object. This header is not 
provided when using outputType=StreamList.", "constantName": 
"org.apache.camel.component.sql.SqlConstants#SQL_UPDA [...]
+    "CamelSqlUpdateCount": { "index": 1, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"Integer", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "important": true, "description": "The number of rows updated 
for update operations, returned as an Integer object. This header is not 
provided when using outputType=StreamList. With batch=true, the sum of the 
update counts of the batch items; an it [...]
     "CamelSqlRowCount": { "index": 2, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"Integer", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "important": true, "description": "The number of rows returned 
for select operations, returned as an Integer object. This header is not 
provided when using outputType=StreamList.", "constantName": 
"org.apache.camel.component.sql.SqlConstants#SQL_ROW_COUNT" },
     "CamelSqlRetrieveGeneratedKeys": { "index": 3, "kind": "header", 
"displayName": "", "group": "producer", "label": "producer", "required": false, 
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "defaultValue": "false", "description": "Set its value 
to true to retrieve generated keys", "constantName": 
"org.apache.camel.component.sql.SqlConstants#SQL_RETRIEVE_GENERATED_KEYS" },
     "CamelSqlGeneratedColumns": { "index": 4, "kind": "header", "displayName": 
"", "group": "producer", "label": "producer", "required": false, "javaType": 
"String[] or int[]", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Set it to specify the expected 
generated columns", "constantName": 
"org.apache.camel.component.sql.SqlConstants#SQL_GENERATED_COLUMNS" },
@@ -50,7 +50,7 @@
   },
   "bodyTypes": {
     "consumerOut": { "carriers": [ "map" ], "javaType": "java.util.Map<String, 
Object>", "dependsOn": [ "outputHeader", "useIterator", "outputType" ], 
"byOption": [ { "option": "useIterator", "value": "true", "carriers": [ "map" 
], "javaType": "java.util.Map<String, Object>", "javaTypeOption": "outputClass" 
}, { "option": "useIterator", "value": "false", "carriers": [ "list" ] }, { 
"option": "outputType", "value": "SelectList", "carriers": [ "map" ], 
"javaType": "java.util.Map<String, Ob [...]
-    "producerIn": { "carriers": [ "any" ], "description": "The parameters of 
the query: a Map for the named ones, a List or an array for the # ones; the 
query itself with useMessageBodyForSql" },
+    "producerIn": { "carriers": [ "any" ], "description": "The parameters of 
the query: a Map for the named ones, a List or an array for the # ones; the 
query itself with useMessageBodyForSql. With batch, a List or an Iterator of 
such parameters, one batch item each (a single Map is one batch item)" },
     "producerOut": { "carriers": [ "list", "unchanged" ], "dependsOn": [ 
"outputHeader", "noop" ], "byOption": [ { "option": "outputType", "value": 
"SelectOne", "carriers": [ "map", "value", "null" ], "javaTypeOption": 
"outputClass" }, { "option": "outputType", "value": "StreamList", "carriers": [ 
"iterator" ] } ], "description": "The rows of a select: a List of Maps of the 
column names and their values (objects of outputClass when set). A query that 
is not a select leaves the body uncha [...]
   },
   "properties": {
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/sql-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/sql-component.adoc
index f48fed0af171..225964f01b22 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/sql-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/sql-component.adoc
@@ -139,6 +139,10 @@ If `batch` is set to `true`, then the interpretation of 
the inbound
 message body changes slightly – instead of an iterator of parameters,
 the component expects an iterator that contains the parameter iterators;
 the size of the outer iterator determines the batch size.
+A single `Map` body (named parameters) is one batch item.
+The `CamelSqlUpdateCount` header holds the sum of the update counts of the 
batch items.
+A JDBC driver may not know the update count of an item and return 
`Statement.SUCCESS_NO_INFO` instead;
+such an item is not counted, so the header can be lower than the number of 
rows updated, or `0`.
 
 You can use the option `useMessageBodyForSql` that
 allows to use the message body as the SQL statement, and then the SQL
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/sql-stored-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/sql-stored-component.adoc
index b41a11269a7c..6dd4404ca94a 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/sql-stored-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/sql-stored-component.adoc
@@ -221,6 +221,24 @@ You can configure query timeout (via 
`template.queryTimeout`) on statements used
 
 This will be overridden by the remaining transaction timeout when executing 
within a transaction that has a timeout specified at the transaction level.
 
+==== Batch
+
+With `batch=true` the stored procedure is called once for each batch item, and 
all calls are sent to the database as one JDBC batch.
+The batch items are taken from the message body (or from the 
`CamelSqlStoredParameters` header when `useMessageBodyForTemplate=true`):
+
+* a `List` or an `Iterator`, where each element is one batch item, typically a 
`Map` with the values of the IN parameters.
+* a single `Map`, which is one batch item.
+
+._XML-only: stored procedure call in batch mode_
+[source,xml]
+----
+<to uri="sql-stored:BATCHFN(INTEGER :#num)?batch=true"/>
+----
+
+The message body is not changed, and the `CamelSqlStoredUpdateCount` header 
holds the sum of the update counts of the batch items.
+A JDBC driver may not know the update count of an item and return 
`Statement.SUCCESS_NO_INFO` instead (HSQLDB does this for stored procedure 
calls).
+Such an item is not counted, so the header can be lower than the number of 
rows updated, or `0`.
+
 
 === Camel SQL Starter
 
diff --git 
a/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/sql.json
 
b/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/sql.json
index 1c905fbc57cb..4641f8fd3aec 100644
--- 
a/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/sql.json
+++ 
b/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/sql.json
@@ -39,7 +39,7 @@
   },
   "headers": {
     "CamelSqlQuery": { "index": 0, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"String", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Query to execute. This header is ignored 
unless the endpoint enables allowQueryFromHeader=true (disabled by default); 
when enabled it takes precedence over the query specified in the endpoint URI. 
Note that query parameters in the header ar [...]
-    "CamelSqlUpdateCount": { "index": 1, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"Integer", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "important": true, "description": "The number of rows updated 
for update operations, returned as an Integer object. This header is not 
provided when using outputType=StreamList.", "constantName": 
"org.apache.camel.component.sql.SqlConstants#SQL_UPDA [...]
+    "CamelSqlUpdateCount": { "index": 1, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"Integer", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "important": true, "description": "The number of rows updated 
for update operations, returned as an Integer object. This header is not 
provided when using outputType=StreamList. With batch=true, the sum of the 
update counts of the batch items; an it [...]
     "CamelSqlRowCount": { "index": 2, "kind": "header", "displayName": "", 
"group": "producer", "label": "producer", "required": false, "javaType": 
"Integer", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "important": true, "description": "The number of rows returned 
for select operations, returned as an Integer object. This header is not 
provided when using outputType=StreamList.", "constantName": 
"org.apache.camel.component.sql.SqlConstants#SQL_ROW_COUNT" },
     "CamelSqlRetrieveGeneratedKeys": { "index": 3, "kind": "header", 
"displayName": "", "group": "producer", "label": "producer", "required": false, 
"javaType": "Boolean", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "defaultValue": "false", "description": "Set its value 
to true to retrieve generated keys", "constantName": 
"org.apache.camel.component.sql.SqlConstants#SQL_RETRIEVE_GENERATED_KEYS" },
     "CamelSqlGeneratedColumns": { "index": 4, "kind": "header", "displayName": 
"", "group": "producer", "label": "producer", "required": false, "javaType": 
"String[] or int[]", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "description": "Set it to specify the expected 
generated columns", "constantName": 
"org.apache.camel.component.sql.SqlConstants#SQL_GENERATED_COLUMNS" },
@@ -50,7 +50,7 @@
   },
   "bodyTypes": {
     "consumerOut": { "carriers": [ "map" ], "javaType": "java.util.Map<String, 
Object>", "dependsOn": [ "outputHeader", "useIterator", "outputType" ], 
"byOption": [ { "option": "useIterator", "value": "true", "carriers": [ "map" 
], "javaType": "java.util.Map<String, Object>", "javaTypeOption": "outputClass" 
}, { "option": "useIterator", "value": "false", "carriers": [ "list" ] }, { 
"option": "outputType", "value": "SelectList", "carriers": [ "map" ], 
"javaType": "java.util.Map<String, Ob [...]
-    "producerIn": { "carriers": [ "any" ], "description": "The parameters of 
the query: a Map for the named ones, a List or an array for the # ones; the 
query itself with useMessageBodyForSql" },
+    "producerIn": { "carriers": [ "any" ], "description": "The parameters of 
the query: a Map for the named ones, a List or an array for the # ones; the 
query itself with useMessageBodyForSql. With batch, a List or an Iterator of 
such parameters, one batch item each (a single Map is one batch item)" },
     "producerOut": { "carriers": [ "list", "unchanged" ], "dependsOn": [ 
"outputHeader", "noop" ], "byOption": [ { "option": "outputType", "value": 
"SelectOne", "carriers": [ "map", "value", "null" ], "javaTypeOption": 
"outputClass" }, { "option": "outputType", "value": "StreamList", "carriers": [ 
"iterator" ] } ], "description": "The rows of a select: a List of Maps of the 
column names and their values (objects of outputClass when set). A query that 
is not a select leaves the body uncha [...]
   },
   "properties": {
diff --git 
a/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/stored/sql-stored.json
 
b/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/stored/sql-stored.json
index 16606cd4dc34..a7abb1450ff7 100644
--- 
a/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/stored/sql-stored.json
+++ 
b/components/camel-sql/src/generated/resources/META-INF/org/apache/camel/component/sql/stored/sql-stored.json
@@ -33,11 +33,11 @@
   "headers": {
     "CamelSqlStoredTemplate": { "index": 0, "kind": "header", "displayName": 
"", "group": "producer", "label": "producer", "required": false, "javaType": 
"String", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "The stored-procedure template to execute. This 
header is ignored unless the endpoint enables allowTemplateFromHeader=true 
(disabled by default); when enabled it overrides the endpoint-configured 
template.", "constantName": "org.apa [...]
     "CamelSqlStoredParameters": { "index": 1, "kind": "header", "displayName": 
"", "group": "producer", "label": "producer", "required": false, "javaType": 
"Iterator", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "The parameters", "constantName": 
"org.apache.camel.component.sql.stored.SqlStoredConstants#SQL_STORED_PARAMETERS"
 },
-    "CamelSqlStoredUpdateCount": { "index": 2, "kind": "header", 
"displayName": "", "group": "producer", "label": "producer", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "important": true, "description": "The update count", 
"constantName": 
"org.apache.camel.component.sql.stored.SqlStoredConstants#SQL_STORED_UPDATE_COUNT"
 }
+    "CamelSqlStoredUpdateCount": { "index": 2, "kind": "header", 
"displayName": "", "group": "producer", "label": "producer", "required": false, 
"javaType": "Integer", "deprecated": false, "deprecationNote": "", "autowired": 
false, "secret": false, "important": true, "description": "The update count. 
With batch=true, the sum of the update counts of the batch items; an item the 
JDBC driver returns no count for (Statement.SUCCESS_NO_INFO) is not counted, so 
the value can be lower than the  [...]
   },
   "bodyTypes": {
-    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": "batch", 
"value": "true", "carriers": [ "list", "iterator" ] }, { "option": 
"useMessageBodyForTemplate", "value": "true", "carriers": [ "text" ] } ], 
"description": "What the expressions of the IN parameters read: a :#name 
parameter takes its value from a Map body, or else from the header. With batch, 
a List or an Iterator of such bodies, one batch item each (a single Map is not 
a batch); with useMessageBodyForTemplate, [...]
-    "producerOut": { "carriers": [ "map" ], "javaType": "java.util.Map<String, 
Object>", "dependsOn": [ "outputHeader" ], "byOption": [ { "option": "noop", 
"value": "true", "carriers": [ "unchanged" ] }, { "option": "batch", "value": 
"true", "carriers": [ "unchanged" ] } ], "description": "A Map of the names of 
the OUT and INOUT parameters and their values, with a result set the procedure 
returns under #result-set-1 (a List of Maps) and the update count under 
#update-count-1, which is al [...]
+    "producerIn": { "carriers": [ "any" ], "byOption": [ { "option": "batch", 
"value": "true", "carriers": [ "list", "iterator", "map" ] }, { "option": 
"useMessageBodyForTemplate", "value": "true", "carriers": [ "text" ] } ], 
"description": "What the expressions of the IN parameters read: a :#name 
parameter takes its value from a Map body, or else from the header. With batch, 
a List or an Iterator of such bodies, one batch item each (a single Map is one 
batch item); with useMessageBodyFo [...]
+    "producerOut": { "carriers": [ "map" ], "javaType": "java.util.Map<String, 
Object>", "dependsOn": [ "outputHeader" ], "byOption": [ { "option": "noop", 
"value": "true", "carriers": [ "unchanged" ] }, { "option": "batch", "value": 
"true", "carriers": [ "unchanged" ] } ], "description": "A Map of the names of 
the OUT and INOUT parameters and their values, with a result set the procedure 
returns under #result-set-1 (a List of Maps) and the update count under 
#update-count-1, which is al [...]
   },
   "properties": {
     "template": { "index": 0, "kind": "path", "displayName": "Template", 
"group": "producer", "label": "", "required": true, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "supportFileReference": true, 
"largeInput": true, "inputLanguage": "sql", "description": "Sets the stored 
procedure template to perform. You can externalize the template by using file: 
or classpath: as prefix and specify the location of [...]
diff --git a/components/camel-sql/src/main/docs/sql-component.adoc 
b/components/camel-sql/src/main/docs/sql-component.adoc
index f48fed0af171..225964f01b22 100644
--- a/components/camel-sql/src/main/docs/sql-component.adoc
+++ b/components/camel-sql/src/main/docs/sql-component.adoc
@@ -139,6 +139,10 @@ If `batch` is set to `true`, then the interpretation of 
the inbound
 message body changes slightly – instead of an iterator of parameters,
 the component expects an iterator that contains the parameter iterators;
 the size of the outer iterator determines the batch size.
+A single `Map` body (named parameters) is one batch item.
+The `CamelSqlUpdateCount` header holds the sum of the update counts of the 
batch items.
+A JDBC driver may not know the update count of an item and return 
`Statement.SUCCESS_NO_INFO` instead;
+such an item is not counted, so the header can be lower than the number of 
rows updated, or `0`.
 
 You can use the option `useMessageBodyForSql` that
 allows to use the message body as the SQL statement, and then the SQL
diff --git a/components/camel-sql/src/main/docs/sql-stored-component.adoc 
b/components/camel-sql/src/main/docs/sql-stored-component.adoc
index b41a11269a7c..6dd4404ca94a 100644
--- a/components/camel-sql/src/main/docs/sql-stored-component.adoc
+++ b/components/camel-sql/src/main/docs/sql-stored-component.adoc
@@ -221,6 +221,24 @@ You can configure query timeout (via 
`template.queryTimeout`) on statements used
 
 This will be overridden by the remaining transaction timeout when executing 
within a transaction that has a timeout specified at the transaction level.
 
+==== Batch
+
+With `batch=true` the stored procedure is called once for each batch item, and 
all calls are sent to the database as one JDBC batch.
+The batch items are taken from the message body (or from the 
`CamelSqlStoredParameters` header when `useMessageBodyForTemplate=true`):
+
+* a `List` or an `Iterator`, where each element is one batch item, typically a 
`Map` with the values of the IN parameters.
+* a single `Map`, which is one batch item.
+
+._XML-only: stored procedure call in batch mode_
+[source,xml]
+----
+<to uri="sql-stored:BATCHFN(INTEGER :#num)?batch=true"/>
+----
+
+The message body is not changed, and the `CamelSqlStoredUpdateCount` header 
holds the sum of the update counts of the batch items.
+A JDBC driver may not know the update count of an item and return 
`Statement.SUCCESS_NO_INFO` instead (HSQLDB does this for stored procedure 
calls).
+Such an item is not counted, so the header can be lower than the number of 
rows updated, or `0`.
+
 
 === Camel SQL Starter
 
diff --git 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlConstants.java
 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlConstants.java
index 2e961586aaa5..5d8a3fee31b9 100644
--- 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlConstants.java
+++ 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlConstants.java
@@ -32,7 +32,10 @@ public final class SqlConstants {
     public static final String SQL_QUERY = "CamelSqlQuery";
     @Metadata(label = "producer", description = "The number of rows updated 
for `update` operations, returned as an" +
                                                 " `Integer` object. This 
header is not provided when using" +
-                                                " outputType=StreamList.",
+                                                " outputType=StreamList. With 
batch=true, the sum of the update counts" +
+                                                " of the batch items; an item 
the JDBC driver returns no count for" +
+                                                " (Statement.SUCCESS_NO_INFO) 
is not counted, so the value can be lower" +
+                                                " than the number of rows 
updated, or 0.",
               javaType = "Integer", important = true)
     public static final String SQL_UPDATE_COUNT = "CamelSqlUpdateCount";
     @Metadata(label = "producer", description = "The number of rows returned 
for `select` operations, returned as an" +
diff --git 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlEndpoint.java
 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlEndpoint.java
index 465887263818..d8a7c6039768 100644
--- 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlEndpoint.java
+++ 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlEndpoint.java
@@ -54,7 +54,9 @@ import org.apache.camel.util.UnsafeUriCharactersEncoder;
                                                  + " object of outputClass; 
with useIterator=false the List of them"),
            producerIn = @BodyType(value = Carrier.ANY,
                                   description = "The parameters of the query: 
a Map for the named ones, a List or an array"
-                                                + " for the # ones; the query 
itself with useMessageBodyForSql"),
+                                                + " for the # ones; the query 
itself with useMessageBodyForSql. With batch,"
+                                                + " a List or an Iterator of 
such parameters, one batch item each (a single"
+                                                + " Map is one batch item)"),
            producerOut = @BodyType(value = { Carrier.LIST, Carrier.UNCHANGED 
}, dependsOn = { "outputHeader", "noop" },
                                    byOption = {
                                            @BodyType.When(option = 
"outputType", value = "SelectOne",
diff --git 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlProducer.java
 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlProducer.java
index b2e7b7d143bd..c85c6379b3ec 100644
--- 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlProducer.java
+++ 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/SqlProducer.java
@@ -25,6 +25,7 @@ import java.sql.Statement;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 
 import javax.sql.DataSource;
 
@@ -171,7 +172,11 @@ public class SqlProducer extends DefaultProducer {
                             int[] updateCounts = ps.executeBatch();
                             int total = 0;
                             for (int count : updateCounts) {
-                                total += count;
+                                // a driver may return a negative value such 
as Statement.SUCCESS_NO_INFO (-2)
+                                // when the count of an item is unknown, so 
only add the known counts
+                                if (count > 0) {
+                                    total += count;
+                                }
                             }
                             
exchange.getIn().setHeader(SqlConstants.SQL_UPDATE_COUNT, total);
                             if (!exchange.isTransacted() && manualCommit) {
@@ -302,11 +307,18 @@ public class SqlProducer extends DefaultProducer {
         if (alwaysPopulateStatement || expected > 0) {
             // transfer incoming message body data to prepared statement 
parameters, if necessary
             if (batch) {
-                Iterator<?> iterator;
+                Object items;
                 if (useMessageBodyForSql) {
-                    iterator = 
exchange.getIn().getHeader(SqlConstants.SQL_PARAMETERS, Iterator.class);
+                    items = 
exchange.getIn().getHeader(SqlConstants.SQL_PARAMETERS);
+                } else {
+                    items = exchange.getIn().getBody();
+                }
+                Iterator<?> iterator;
+                if (items instanceof Map) {
+                    // a single Map is one batch item (converting it to an 
Iterator would iterate its entries)
+                    iterator = Collections.singletonList(items).iterator();
                 } else {
-                    iterator = exchange.getIn().getBody(Iterator.class);
+                    iterator = 
exchange.getContext().getTypeConverter().convertTo(Iterator.class, exchange, 
items);
                 }
                 while (iterator != null && iterator.hasNext()) {
                     Object value = iterator.next();
diff --git 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/SqlStoredConstants.java
 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/SqlStoredConstants.java
index a8f91ff518da..576983f52e77 100644
--- 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/SqlStoredConstants.java
+++ 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/SqlStoredConstants.java
@@ -27,7 +27,11 @@ public final class SqlStoredConstants {
     public static final String SQL_STORED_TEMPLATE = "CamelSqlStoredTemplate";
     @Metadata(label = "producer", description = "The parameters", javaType = 
"Iterator")
     public static final String SQL_STORED_PARAMETERS = 
"CamelSqlStoredParameters";
-    @Metadata(label = "producer", description = "The update count", javaType = 
"Integer", important = true)
+    @Metadata(label = "producer",
+              description = "The update count. With batch=true, the sum of the 
update counts of the batch items; an item"
+                            + " the JDBC driver returns no count for 
(Statement.SUCCESS_NO_INFO) is not counted, so the"
+                            + " value can be lower than the number of rows 
updated, or 0.",
+              javaType = "Integer", important = true)
     public static final String SQL_STORED_UPDATE_COUNT = 
"CamelSqlStoredUpdateCount";
 
     private SqlStoredConstants() {
diff --git 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/SqlStoredEndpoint.java
 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/SqlStoredEndpoint.java
index d1628d12f490..fdc5414db542 100644
--- 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/SqlStoredEndpoint.java
+++ 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/SqlStoredEndpoint.java
@@ -50,13 +50,13 @@ import org.springframework.jdbc.core.JdbcTemplate;
 @BodyTypes(producerIn = @BodyType(value = Carrier.ANY,
                                   byOption = {
                                           @BodyType.When(option = "batch", 
value = "true",
-                                                         carriers = { 
Carrier.LIST, Carrier.ITERATOR }),
+                                                         carriers = { 
Carrier.LIST, Carrier.ITERATOR, Carrier.MAP }),
                                           @BodyType.When(option = 
"useMessageBodyForTemplate", value = "true",
                                                          carriers = 
Carrier.TEXT) },
                                   description = "What the expressions of the 
IN parameters read: a :#name parameter takes"
                                                 + " its value from a Map body, 
or else from the header. With batch, a List or"
-                                                + " an Iterator of such 
bodies, one batch item each (a single Map is not a"
-                                                + " batch); with 
useMessageBodyForTemplate, the template as text, with the"
+                                                + " an Iterator of such 
bodies, one batch item each (a single Map is one"
+                                                + " batch item); with 
useMessageBodyForTemplate, the template as text, with the"
                                                 + " parameters in the 
CamelSqlStoredParameters header"),
            producerOut = @BodyType(value = Carrier.MAP, javaType = 
"java.util.Map<String, Object>",
                                    dependsOn = "outputHeader",
@@ -68,7 +68,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
                                                  + " and the update count 
under #update-count-1, which is also in the"
                                                  + " CamelSqlStoredUpdateCount 
header. With outputHeader the Map is in that"
                                                  + " header and the body is 
unchanged; batch leaves the body unchanged, with"
-                                                 + " the total update count in 
the header"))
+                                                 + " the total of the known 
update counts in the header"))
 public class SqlStoredEndpoint extends DefaultEndpoint implements 
EndpointServiceLocation {
 
     private CallableStatementWrapperFactory wrapperFactory;
diff --git 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/SqlStoredProducer.java
 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/SqlStoredProducer.java
index 67b5404ed352..3946a76751d0 100644
--- 
a/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/SqlStoredProducer.java
+++ 
b/components/camel-sql/src/main/java/org/apache/camel/component/sql/stored/SqlStoredProducer.java
@@ -17,7 +17,9 @@
 package org.apache.camel.component.sql.stored;
 
 import java.sql.SQLException;
+import java.util.Collections;
 import java.util.Iterator;
+import java.util.Map;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.component.sql.SqlHelper;
@@ -46,11 +48,18 @@ public class SqlStoredProducer extends DefaultProducer {
             public void execute(StatementWrapper ps) throws SQLException, 
DataAccessException {
                 // transfer incoming message body data to prepared statement 
parameters, if necessary
                 if (getEndpoint().isBatch()) {
-                    Iterator<?> iterator;
+                    Object items;
                     if (getEndpoint().isUseMessageBodyForTemplate()) {
-                        iterator = 
exchange.getIn().getHeader(SqlStoredConstants.SQL_STORED_PARAMETERS, 
Iterator.class);
+                        items = 
exchange.getIn().getHeader(SqlStoredConstants.SQL_STORED_PARAMETERS);
                     } else {
-                        iterator = exchange.getIn().getBody(Iterator.class);
+                        items = exchange.getIn().getBody();
+                    }
+                    Iterator<?> iterator;
+                    if (items instanceof Map) {
+                        // a single Map is one batch item (converting it to an 
Iterator would iterate its entries)
+                        iterator = Collections.singletonList(items).iterator();
+                    } else {
+                        iterator = 
exchange.getContext().getTypeConverter().convertTo(Iterator.class, exchange, 
items);
                     }
 
                     if (iterator == null) {
@@ -75,7 +84,11 @@ public class SqlStoredProducer extends DefaultProducer {
                     int[] updateCounts = ps.executeBatch();
                     int total = 0;
                     for (int count : updateCounts) {
-                        total += count;
+                        // a driver may return a negative value such as 
Statement.SUCCESS_NO_INFO (-2)
+                        // when the count of an item is unknown, so only add 
the known counts
+                        if (count > 0) {
+                            total += count;
+                        }
                     }
                     
exchange.getIn().setHeader(SqlStoredConstants.SQL_STORED_UPDATE_COUNT, total);
                 } else {
diff --git 
a/components/camel-sql/src/test/java/org/apache/camel/component/sql/SqlProducerBatchTest.java
 
b/components/camel-sql/src/test/java/org/apache/camel/component/sql/SqlProducerBatchTest.java
new file mode 100644
index 000000000000..58d130f33090
--- /dev/null
+++ 
b/components/camel-sql/src/test/java/org/apache/camel/component/sql/SqlProducerBatchTest.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.sql;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DelegatingDataSource;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class SqlProducerBatchTest extends CamelTestSupport {
+
+    private EmbeddedDatabase db;
+    private JdbcTemplate jdbcTemplate;
+
+    @Override
+    public void doPreSetup() throws Exception {
+        db = new EmbeddedDatabaseBuilder()
+                .setName(getClass().getSimpleName())
+                .setType(EmbeddedDatabaseType.H2)
+                .addScript("sql/createAndPopulateDatabase.sql").build();
+
+        jdbcTemplate = new JdbcTemplate(db);
+    }
+
+    @Override
+    public void doPostTearDown() throws Exception {
+        if (db != null) {
+            db.shutdown();
+        }
+    }
+
+    @Test
+    public void testBatchUpdateCount() {
+        List<Map<String, Object>> data = Arrays.asList(project(4, "Donald"), 
project(5, "Goofy"));
+
+        Exchange out = template.request("direct:batch", e -> 
e.getIn().setBody(data));
+
+        assertEquals(2, 
out.getMessage().getHeader(SqlConstants.SQL_UPDATE_COUNT, Integer.class));
+        assertEquals(5, jdbcTemplate.queryForObject("select count(*) from 
projects", Integer.class));
+    }
+
+    @Test
+    public void testBatchUpdateCountWithSuccessNoInfo() {
+        List<Map<String, Object>> data = Arrays.asList(project(4, "Donald"), 
project(5, "Goofy"));
+
+        Exchange out = template.request("direct:noInfo", e -> 
e.getIn().setBody(data));
+
+        // the driver returns Statement.SUCCESS_NO_INFO (-2) for each item, 
which must not make the total negative
+        assertEquals(0, 
out.getMessage().getHeader(SqlConstants.SQL_UPDATE_COUNT, Integer.class));
+        assertEquals(5, jdbcTemplate.queryForObject("select count(*) from 
projects", Integer.class));
+    }
+
+    @Test
+    public void testBatchSingleMapBodyIsOneBatchItem() {
+        Exchange out = template.request("direct:batch", e -> 
e.getIn().setBody(project(4, "Donald")));
+
+        assertNull(out.getException());
+        assertEquals(1, 
out.getMessage().getHeader(SqlConstants.SQL_UPDATE_COUNT, Integer.class));
+        assertEquals("Donald", jdbcTemplate.queryForObject("select project 
from projects where id = 4", String.class));
+        assertEquals(4, jdbcTemplate.queryForObject("select count(*) from 
projects", Integer.class));
+    }
+
+    @Test
+    public void testBatchSingleMapHeaderIsOneBatchItem() {
+        Exchange out = template.request("direct:body", e -> {
+            e.getIn().setBody("insert into projects values (:?id, :?project, 
:?license)");
+            e.getIn().setHeader(SqlConstants.SQL_PARAMETERS, project(4, 
"Donald"));
+        });
+
+        assertNull(out.getException());
+        assertEquals(1, 
out.getMessage().getHeader(SqlConstants.SQL_UPDATE_COUNT, Integer.class));
+        assertEquals("Donald", jdbcTemplate.queryForObject("select project 
from projects where id = 4", String.class));
+        assertEquals(4, jdbcTemplate.queryForObject("select count(*) from 
projects", Integer.class));
+    }
+
+    private static Map<String, Object> project(int id, String project) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("id", id);
+        map.put("project", project);
+        map.put("license", "DIS");
+        return map;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                getContext().getComponent("sql", 
SqlComponent.class).setDataSource(db);
+                getContext().getRegistry().bind("noInfoDataSource", new 
SuccessNoInfoDataSource(db));
+
+                from("direct:batch")
+                        .to("sql:insert into projects values (:#id, :#project, 
:#license)?batch=true");
+
+                from("direct:noInfo")
+                        .to("sql:insert into projects values (:#id, :#project, 
:#license)?batch=true"
+                            + "&dataSource=#noInfoDataSource");
+
+                from("direct:body")
+                        .to("sql:query?batch=true&useMessageBodyForSql=true");
+            }
+        };
+    }
+
+    /**
+     * A data source whose prepared statements return 
Statement.SUCCESS_NO_INFO for each batch item, as some JDBC
+     * drivers do.
+     */
+    private static final class SuccessNoInfoDataSource extends 
DelegatingDataSource {
+
+        SuccessNoInfoDataSource(DataSource target) {
+            super(target);
+        }
+
+        @Override
+        public Connection getConnection() throws SQLException {
+            Connection con = super.getConnection();
+            return (Connection) 
Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { 
Connection.class },
+                    new Delegate(con) {
+                        @Override
+                        Object invoke(Method method, Object result) {
+                            if (result instanceof PreparedStatement ps) {
+                                return 
Proxy.newProxyInstance(getClass().getClassLoader(),
+                                        new Class<?>[] { 
PreparedStatement.class }, new Delegate(ps) {
+                                            @Override
+                                            Object invoke(Method method, 
Object result) {
+                                                if 
("executeBatch".equals(method.getName())) {
+                                                    int[] counts = new 
int[((int[]) result).length];
+                                                    Arrays.fill(counts, 
Statement.SUCCESS_NO_INFO);
+                                                    return counts;
+                                                }
+                                                return result;
+                                            }
+                                        });
+                            }
+                            return result;
+                        }
+                    });
+        }
+    }
+
+    private abstract static class Delegate implements InvocationHandler {
+
+        private final Object target;
+
+        Delegate(Object target) {
+            this.target = target;
+        }
+
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) 
throws Throwable {
+            try {
+                return invoke(method, method.invoke(target, args));
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
+        }
+
+        abstract Object invoke(Method method, Object result);
+    }
+}
diff --git 
a/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerBatchTest.java
 
b/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerBatchTest.java
index 6bb13ffa3136..89b507fc7f35 100644
--- 
a/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerBatchTest.java
+++ 
b/components/camel-sql/src/test/java/org/apache/camel/component/sql/stored/ProducerBatchTest.java
@@ -81,6 +81,47 @@ public class ProducerBatchTest extends CamelTestSupport {
 
         
assertNotNull(exchange.getIn().getHeader(SqlStoredConstants.SQL_STORED_UPDATE_COUNT));
         assertEquals(batchfnCallsBefore + 2, 
TestStoredProcedure.BATCHFN_CALL_COUNTER.get());
+        // HSQLDB returns Statement.SUCCESS_NO_INFO (-2) for each item, which 
must not make the total negative
+        assertEquals(0, 
exchange.getIn().getHeader(SqlStoredConstants.SQL_STORED_UPDATE_COUNT, 
Integer.class));
+    }
+
+    @Test
+    public void shouldExecuteSingleMapBodyAsOneBatchItem() throws 
InterruptedException {
+        MockEndpoint mock = getMockEndpoint("mock:query");
+        mock.expectedMessageCount(1);
+
+        Map<String, Object> batch1 = new HashMap<>();
+        batch1.put("num", "1");
+        batch1.put("other", "2");
+
+        final long batchfnCallsBefore = 
TestStoredProcedure.BATCHFN_CALL_COUNTER.get();
+
+        template.requestBody("direct:query", batch1);
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        Exchange exchange = mock.getExchanges().get(0);
+        assertEquals(batchfnCallsBefore + 1, 
TestStoredProcedure.BATCHFN_CALL_COUNTER.get());
+        assertEquals(0, 
exchange.getIn().getHeader(SqlStoredConstants.SQL_STORED_UPDATE_COUNT, 
Integer.class));
+    }
+
+    @Test
+    public void shouldExecuteSingleMapHeaderAsOneBatchItem() throws 
InterruptedException {
+        MockEndpoint mock = getMockEndpoint("mock:template");
+        mock.expectedMessageCount(1);
+
+        Map<String, Object> batch1 = new HashMap<>();
+        batch1.put("num", "1");
+        batch1.put("other", "2");
+
+        final long batchfnCallsBefore = 
TestStoredProcedure.BATCHFN_CALL_COUNTER.get();
+
+        template.requestBodyAndHeader("direct:template", "BATCHFN(INTEGER 
:#num)",
+                SqlStoredConstants.SQL_STORED_PARAMETERS, batch1);
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        assertEquals(batchfnCallsBefore + 1, 
TestStoredProcedure.BATCHFN_CALL_COUNTER.get());
     }
 
     @Override
@@ -92,6 +133,9 @@ public class ProducerBatchTest extends CamelTestSupport {
                 getContext().getComponent("sql-stored", 
SqlStoredComponent.class).setDataSource(db);
 
                 from("direct:query").to("sql-stored:BATCHFN(INTEGER 
:#num)?batch=true").to("mock:query");
+
+                
from("direct:template").to("sql-stored:template?batch=true&useMessageBodyForTemplate=true")
+                        .to("mock:template");
             }
         };
     }
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 41f6ca1f15c1..139a76288316 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -1717,6 +1717,18 @@ again. With `transacted=true` the SQL and MyBatis 
consumers break out of the bat
 `markRollbackOnlyLast()` reaches the consumer only when no `transacted()` EIP 
has consumed the flag, so the
 consumer's own transaction is the last one.
 
+=== camel-sql - sql and sql-stored with batch=true: the update count and a 
single Map body
+
+With `batch=true` the `CamelSqlUpdateCount` (sql) and 
`CamelSqlStoredUpdateCount` (sql-stored) headers no longer add
+the negative values a JDBC driver returns for an item whose update count it 
does not know
+(`Statement.SUCCESS_NO_INFO`, which HSQLDB returns for stored procedure 
calls). Such a batch reported a negative count
+before, for example `-4` for two items; it now reports the sum of the known 
counts, which can be `0`.
+
+A single `Map` body is now one batch item, and so is a single `Map` in the 
`CamelSqlParameters` header with
+`useMessageBodyForSql=true` (sql) or the `CamelSqlStoredParameters` header 
with `useMessageBodyForTemplate=true`
+(sql-stored). Before, its entries were used as the batch items and the call 
failed (sql: no value found for the named
+parameters, sql-stored: an `SQLException`).
+
 === camel-jta - an exchange marked rollback only last rolls back its 
transaction
 
 In a `transacted()` route that uses a JTA transaction manager, 
`markRollbackOnlyLast()` now rolls back the
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SqlEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SqlEndpointBuilderFactory.java
index 848f3d681a93..7f51de105a80 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SqlEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SqlEndpointBuilderFactory.java
@@ -2700,7 +2700,10 @@ public interface SqlEndpointBuilderFactory {
         /**
          * The number of rows updated for update operations, returned as an
          * Integer object. This header is not provided when using
-         * outputType=StreamList.
+         * outputType=StreamList. With batch=true, the sum of the update counts
+         * of the batch items; an item the JDBC driver returns no count for
+         * (Statement.SUCCESS_NO_INFO) is not counted, so the value can be 
lower
+         * than the number of rows updated, or 0.
          * 
          * The option is a: {@code Integer} type.
          * 
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SqlStoredEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SqlStoredEndpointBuilderFactory.java
index 5378273bcbea..98ae45a7a8b5 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SqlStoredEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/SqlStoredEndpointBuilderFactory.java
@@ -456,7 +456,10 @@ public interface SqlStoredEndpointBuilderFactory {
             return "CamelSqlStoredParameters";
         }
         /**
-         * The update count.
+         * The update count. With batch=true, the sum of the update counts of
+         * the batch items; an item the JDBC driver returns no count for
+         * (Statement.SUCCESS_NO_INFO) is not counted, so the value can be 
lower
+         * than the number of rows updated, or 0.
          * 
          * The option is a: {@code Integer} type.
          * 

Reply via email to