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

davsclaus pushed a commit to branch CAMEL-26145-mybatis-inout-body
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 8a844e3a1334db0d891d363a5af480ef6a7b2d93
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 17:19:27 2026 +0100

    CAMEL-26145: camel-mybatis - Keep the body with InOut, and fail an Insert 
without a body when the statement has parameters
    
    With an InOut exchange the producer copied the in body to the out message 
only
    when outputHeader was set, so Insert, Update and Delete (and their List
    variants), which put the result in a header, left the body null. The body is
    now always preserved, and a SELECT still replaces it with the result.
    
    An Insert with a null body ran the statement without a parameter, so a
    statement with parameters inserted a row of NULLs without an error. It now
    fails with an IllegalArgumentException; a statement without parameters still
    runs.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../camel/catalog/docs/mybatis-component.adoc      |   5 +
 .../src/main/docs/mybatis-component.adoc           |   5 +
 .../camel/component/mybatis/MyBatisProducer.java   |  21 +++-
 .../mybatis/MyBatisInOutKeepsBodyTest.java         | 127 +++++++++++++++++++++
 .../mybatis/MyBatisInsertNullBodyTest.java         |  63 ++++++++++
 .../mybatis/MyBatisInsertWithRollbackTest.java     |   8 +-
 .../org/apache/camel/component/mybatis/Account.xml |   6 +
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  10 ++
 8 files changed, 239 insertions(+), 6 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mybatis-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mybatis-component.adoc
index d84d40a70f92..434feb36377a 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mybatis-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/mybatis-component.adoc
@@ -63,6 +63,11 @@ Camel will not replace the body. This allows you to continue 
routing and
 keep the original body. The response from MyBatis is stored in
 the header with the key `CamelMyBatisResult`, or in the header that the
 `outputHeader` option names, which leaves the body unchanged for a `SELECT` as 
well.
+This is the same for an `InOnly` and an `InOut` exchange.
+
+When the body (or the header that the `inputHeader` option names) is `null`, 
an `Insert` runs the statement
+without a parameter. If the statement has parameters, such as `#{id}`, the 
producer fails with an
+`IllegalArgumentException` instead of inserting a row where every parameter is 
`NULL`.
 
 == Examples
 
diff --git a/components/camel-mybatis/src/main/docs/mybatis-component.adoc 
b/components/camel-mybatis/src/main/docs/mybatis-component.adoc
index d84d40a70f92..434feb36377a 100644
--- a/components/camel-mybatis/src/main/docs/mybatis-component.adoc
+++ b/components/camel-mybatis/src/main/docs/mybatis-component.adoc
@@ -63,6 +63,11 @@ Camel will not replace the body. This allows you to continue 
routing and
 keep the original body. The response from MyBatis is stored in
 the header with the key `CamelMyBatisResult`, or in the header that the
 `outputHeader` option names, which leaves the body unchanged for a `SELECT` as 
well.
+This is the same for an `InOnly` and an `InOut` exchange.
+
+When the body (or the header that the `inputHeader` option names) is `null`, 
an `Insert` runs the statement
+without a parameter. If the statement has parameters, such as `#{id}`, the 
producer fails with an
+`IllegalArgumentException` instead of inserting a row where every parameter is 
`NULL`.
 
 == Examples
 
diff --git 
a/components/camel-mybatis/src/main/java/org/apache/camel/component/mybatis/MyBatisProducer.java
 
b/components/camel-mybatis/src/main/java/org/apache/camel/component/mybatis/MyBatisProducer.java
index be3320002846..6b39e71c0206 100644
--- 
a/components/camel-mybatis/src/main/java/org/apache/camel/component/mybatis/MyBatisProducer.java
+++ 
b/components/camel-mybatis/src/main/java/org/apache/camel/component/mybatis/MyBatisProducer.java
@@ -135,6 +135,7 @@ public class MyBatisProducer extends DefaultProducer {
                 doProcessResult(exchange, result, session);
             }
         } else {
+            checkNoParameters(session);
             LOG.trace("Inserting using statement: {}", statement);
             result = session.insert(statement);
             doProcessResult(exchange, result, session);
@@ -230,10 +231,8 @@ public class MyBatisProducer extends DefaultProducer {
             answer = exchange.getOut();
             // preserve headers
             answer.getHeaders().putAll(exchange.getIn().getHeaders());
-            if (outputHeader != null) {
-                //if we put the MyBatis result into a header we should 
preserve the body as well
-                answer.setBody(exchange.getIn().getBody());
-            }
+            // preserve the body, which a SELECT replaces with the result 
below, unless the result goes into a header
+            answer.setBody(exchange.getIn().getBody());
         }
         if (endpoint.getStatementType() == StatementType.SelectList || 
endpoint.getStatementType() == StatementType.SelectOne) {
             // we should not set the body if its a stored procedure as the 
result is already in its OUT parameter
@@ -274,6 +273,20 @@ public class MyBatisProducer extends DefaultProducer {
         answer.setHeader(MyBatisConstants.MYBATIS_STATEMENT_NAME, statement);
     }
 
+    /**
+     * Fails when there is no input, but the statement has parameters, as 
MyBatis would otherwise run the statement with
+     * NULL for every parameter, such as inserting a row of nulls.
+     */
+    private void checkNoParameters(SqlSession session) {
+        MappedStatement ms = 
session.getConfiguration().getMappedStatement(statement);
+        if (!ms.getBoundSql(null).getParameterMappings().isEmpty()) {
+            final String inputHeader = getEndpoint().getInputHeader();
+            String input = inputHeader != null ? "header " + inputHeader : 
"message body";
+            throw new IllegalArgumentException(
+                    "The " + input + " is null, but the MyBatis statement " + 
statement + " has parameters");
+        }
+    }
+
     @Override
     public MyBatisEndpoint getEndpoint() {
         return (MyBatisEndpoint) super.getEndpoint();
diff --git 
a/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisInOutKeepsBodyTest.java
 
b/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisInOutKeepsBodyTest.java
new file mode 100644
index 000000000000..be8e388777c9
--- /dev/null
+++ 
b/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisInOutKeepsBodyTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.mybatis;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.ExchangePattern;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * Insert, Update and Delete store the result in a header, so the body is 
kept, with InOut as well as with InOnly.
+ */
+public class MyBatisInOutKeepsBodyTest extends MyBatisTestSupport {
+
+    @Test
+    public void testInsertKeepsBody() {
+        Account account = createAccount(444, "Willem");
+
+        Exchange out = request("mybatis:insertAccount?statementType=Insert", 
account);
+
+        assertSame(account, out.getMessage().getBody());
+        assertEquals(1, 
out.getMessage().getHeader(MyBatisConstants.MYBATIS_RESULT));
+        assertEquals(3, countRows());
+    }
+
+    @Test
+    public void testInsertListKeepsBody() {
+        List<Account> accounts = Arrays.asList(createAccount(444, "Willem"), 
createAccount(555, "Donald"));
+
+        Exchange out = 
request("mybatis:batchInsertAccount?statementType=InsertList", accounts);
+
+        assertSame(accounts, out.getMessage().getBody());
+        assertEquals(2, 
out.getMessage().getHeader(MyBatisConstants.MYBATIS_RESULT));
+        assertEquals(4, countRows());
+    }
+
+    @Test
+    public void testUpdateKeepsBody() {
+        Account account = createAccount(123, "Jim");
+
+        Exchange out = request("mybatis:updateAccount?statementType=Update", 
account);
+
+        assertSame(account, out.getMessage().getBody());
+        assertEquals(1, 
out.getMessage().getHeader(MyBatisConstants.MYBATIS_RESULT));
+    }
+
+    @Test
+    public void testUpdateListKeepsBody() {
+        Map<String, Object> params = new HashMap<>();
+        params.put("list", Arrays.asList(createAccount(123, "James"), 
createAccount(456, "Claus")));
+        params.put("emailAddress", "[email protected]");
+
+        Exchange out = 
request("mybatis:batchUpdateAccount?statementType=UpdateList", params);
+
+        assertSame(params, out.getMessage().getBody());
+        assertEquals(2, 
out.getMessage().getHeader(MyBatisConstants.MYBATIS_RESULT));
+    }
+
+    @Test
+    public void testDeleteKeepsBody() {
+        Exchange out = 
request("mybatis:deleteAccountById?statementType=Delete", 456);
+
+        assertEquals(456, out.getMessage().getBody());
+        assertEquals(1, 
out.getMessage().getHeader(MyBatisConstants.MYBATIS_RESULT));
+        assertEquals(1, countRows());
+    }
+
+    @Test
+    public void testDeleteListKeepsBody() {
+        List<Integer> ids = Arrays.asList(123, 456);
+
+        Exchange out = 
request("mybatis:batchDeleteAccountById?statementType=DeleteList", ids);
+
+        assertSame(ids, out.getMessage().getBody());
+        assertEquals(2, 
out.getMessage().getHeader(MyBatisConstants.MYBATIS_RESULT));
+        assertEquals(0, countRows());
+    }
+
+    @Test
+    public void testSelectOneSetsResultAsBody() {
+        Exchange out = 
request("mybatis:selectAccountById?statementType=SelectOne", 456);
+
+        Account account = out.getMessage().getBody(Account.class);
+        assertEquals("Claus", account.getFirstName());
+    }
+
+    private Exchange request(String uri, Object body) {
+        Exchange out = template.send(uri, ExchangePattern.InOut, e -> 
e.getIn().setBody(body));
+        assertNull(out.getException());
+        return out;
+    }
+
+    private int countRows() {
+        return template.requestBody("mybatis:count?statementType=SelectOne", 
null, Integer.class);
+    }
+
+    private static Account createAccount(int id, String firstName) {
+        Account account = new Account();
+        account.setId(id);
+        account.setFirstName(firstName);
+        account.setLastName("Last");
+        account.setEmailAddress(firstName + "@example.com");
+        return account;
+    }
+}
diff --git 
a/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisInsertNullBodyTest.java
 
b/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisInsertNullBodyTest.java
new file mode 100644
index 000000000000..2c5e327cbc3c
--- /dev/null
+++ 
b/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisInsertNullBodyTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.mybatis;
+
+import org.apache.camel.CamelExecutionException;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class MyBatisInsertNullBodyTest extends MyBatisTestSupport {
+
+    @Test
+    public void testInsertNullBodyFailsWhenStatementHasParameters() {
+        CamelExecutionException e = assertThrows(CamelExecutionException.class,
+                () -> 
template.sendBody("mybatis:insertAccount?statementType=Insert", null));
+
+        IllegalArgumentException cause = 
assertInstanceOf(IllegalArgumentException.class, e.getCause());
+        assertEquals("The message body is null, but the MyBatis statement 
insertAccount has parameters",
+                cause.getMessage());
+        // no row of nulls is inserted
+        assertEquals(2, countRows());
+    }
+
+    @Test
+    public void testInsertMissingInputHeaderFailsWhenStatementHasParameters() {
+        CamelExecutionException e = assertThrows(CamelExecutionException.class,
+                () -> 
template.sendBody("mybatis:insertAccount?statementType=Insert&inputHeader=account",
 "ignored"));
+
+        IllegalArgumentException cause = 
assertInstanceOf(IllegalArgumentException.class, e.getCause());
+        assertEquals("The header account is null, but the MyBatis statement 
insertAccount has parameters",
+                cause.getMessage());
+        assertEquals(2, countRows());
+    }
+
+    @Test
+    public void testInsertNullBodyWithoutParameters() {
+        template.sendBody("mybatis:insertDefaultAccount?statementType=Insert", 
null);
+
+        assertEquals(3, countRows());
+        Account account = 
template.requestBody("mybatis:selectAccountById?statementType=SelectOne", 999, 
Account.class);
+        assertEquals("Default", account.getFirstName());
+    }
+
+    private int countRows() {
+        return template.requestBody("mybatis:count?statementType=SelectOne", 
null, Integer.class);
+    }
+}
diff --git 
a/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisInsertWithRollbackTest.java
 
b/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisInsertWithRollbackTest.java
index 498ea8f8908e..120d2b57478e 100644
--- 
a/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisInsertWithRollbackTest.java
+++ 
b/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisInsertWithRollbackTest.java
@@ -41,12 +41,16 @@ public class MyBatisInsertWithRollbackTest extends 
MyBatisTestSupport {
 
     @Test
     public void testInsert() throws Exception {
+        // an account without names violates the NOT NULL constraints
+        Account account = new Account();
+        account.setId(444);
+
         getMockEndpoint("mock:commit").expectedMessageCount(0);
         getMockEndpoint("mock:rollback").expectedMessageCount(1);
-        getMockEndpoint("mock:rollback").message(0).body().isEqualTo(null);
+        getMockEndpoint("mock:rollback").message(0).body().isEqualTo(account);
         
getMockEndpoint("mock:rollback").message(0).header(Exchange.EXCEPTION_CAUGHT).isInstanceOf(PersistenceException.class);
 
-        template.sendBody("direct:start", null);
+        template.sendBody("direct:start", account);
 
         MockEndpoint.assertIsSatisfied(context);
 
diff --git 
a/components/camel-mybatis/src/test/resources/org/apache/camel/component/mybatis/Account.xml
 
b/components/camel-mybatis/src/test/resources/org/apache/camel/component/mybatis/Account.xml
index 789547a47fe3..36743a0c18d0 100644
--- 
a/components/camel-mybatis/src/test/resources/org/apache/camel/component/mybatis/Account.xml
+++ 
b/components/camel-mybatis/src/test/resources/org/apache/camel/component/mybatis/Account.xml
@@ -139,6 +139,12 @@ aliases to match the properties of the target result 
class. -->
         delete from ACCOUNT where ACC_ID = #{id}
     </delete>
 
+    <!-- An insert without parameters -->
+    <insert id="insertDefaultAccount">
+        insert into ACCOUNT (ACC_ID, ACC_FIRST_NAME, ACC_LAST_NAME, ACC_EMAIL)
+        values (999, 'Default', 'Account', '[email protected]')
+    </insert>
+
     <!-- START SNIPPET: e1 -->
     <select id="selectUnprocessedAccounts" resultMap="AccountResult">
         select * from ACCOUNT where PROCESSED = false
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 1bc188419d61..eee7a3bbdd9a 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
@@ -2487,6 +2487,16 @@ handshake; an IP address must be listed as a subject 
alternative name of the cer
 host name (such as `mllp://8888`) cannot pass the check. Use a certificate 
that matches the host of the endpoint, or set
 the new `hostnameVerification` option to `false` to keep the previous 
behaviour.
 
+=== camel-mybatis - the body is kept with InOut, and an Insert without a body 
fails when the statement has parameters
+
+With an `InOut` exchange, the `Insert`, `InsertList`, `Update`, `UpdateList`, 
`Delete` and `DeleteList` statement
+types now keep the message body, as they already did with `InOnly`. Before, 
the body was `null` after the call unless
+the `outputHeader` option was set. The result is still in the 
`CamelMyBatisResult` header (or the `outputHeader`).
+
+An `Insert` with a `null` body (or a missing `inputHeader`) now fails with an 
`IllegalArgumentException` when the
+statement has parameters. Before, MyBatis inserted a row where every parameter 
was `NULL`. A statement without
+parameters still runs with a `null` body.
+
 === camel-aws2-s3 - streaming upload with the random or timestamp naming 
strategy names the first file too
 
 With `streamingUploadMode=true` and `namingStrategy=random` (or `timestamp`), 
the first file uploaded by a producer was

Reply via email to