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 97460eb125c3 CAMEL-25508: camel-mybatis - Use a String or byte[] body
as one parameter for Insert, Update and Delete
97460eb125c3 is described below
commit 97460eb125c36effe44495332d88065930c3f173
Author: allthingssecurity <[email protected]>
AuthorDate: Sat Oct 10 13:06:23 2026 +0530
CAMEL-25508: camel-mybatis - Use a String or byte[] body as one parameter
for Insert, Update and Delete
MyBatisProducer ran Insert, Update and Delete statements once per element
of ObjectHelper.createIterator(body), with only a Map passed as-is.
createIterator also splits a String at every comma and iterates a byte[]
byte by byte, so inserting "Doe, Jr." inserted "Doe" and " Jr.", and a
byte[] ran the statement once per byte.
A Map, a String (also a blank one, which did not run the statement
before) and a byte[] are now passed as one parameter; MyBatis wraps an
array parameter into a parameter map, so the statement refers to the
byte[] as #{array}. Collections, iterators, streams and all other arrays,
including int[] and long[], still run the statement once per element.
This is a breaking change for a route that relied on the String being
split, as described in the 4.23 upgrade guide.
Closes #27641
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../camel/component/mybatis/MyBatisProducer.java | 37 ++++-----
.../mybatis/MyBatisStringParameterTest.java | 89 ++++++++++++++++++++++
.../org/apache/camel/component/mybatis/Account.xml | 24 ++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 9 +++
4 files changed, 138 insertions(+), 21 deletions(-)
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 4e54d2689a26..be3320002846 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
@@ -127,13 +127,7 @@ public class MyBatisProducer extends DefaultProducer {
Object result;
Object in = getInput(exchange);
if (in != null) {
- Iterator<?> iter;
- if (in instanceof Map) {
- // we want the map as-is
- iter = Collections.singletonList(in).iterator();
- } else {
- iter = ObjectHelper.createIterator(in);
- }
+ Iterator<?> iter = createIterator(in);
while (iter.hasNext()) {
Object value = iter.next();
LOG.trace("Inserting: {} using statement: {}", value,
statement);
@@ -166,13 +160,7 @@ public class MyBatisProducer extends DefaultProducer {
Object result;
Object in = getInput(exchange);
if (in != null) {
- Iterator<?> iter;
- if (in instanceof Map) {
- // we want the map as-is
- iter = Collections.singletonList(in).iterator();
- } else {
- iter = ObjectHelper.createIterator(in);
- }
+ Iterator<?> iter = createIterator(in);
while (iter.hasNext()) {
Object value = iter.next();
LOG.trace("Updating: {} using statement: {}", value,
statement);
@@ -205,13 +193,7 @@ public class MyBatisProducer extends DefaultProducer {
Object result;
Object in = getInput(exchange);
if (in != null) {
- Iterator<?> iter;
- if (in instanceof Map) {
- // we want the map as-is
- iter = Collections.singletonList(in).iterator();
- } else {
- iter = ObjectHelper.createIterator(in);
- }
+ Iterator<?> iter = createIterator(in);
while (iter.hasNext()) {
Object value = iter.next();
LOG.trace("Deleting: {} using statement: {}", value,
statement);
@@ -297,6 +279,19 @@ public class MyBatisProducer extends DefaultProducer {
return (MyBatisEndpoint) super.getEndpoint();
}
+ /**
+ * Iterates the input to run the statement once per element: a collection,
an iterator, a stream or an array
+ * (including an array of a primitive type such as a long[] of ids). A
Map, a String (which may contain commas) and
+ * a byte[] are one parameter as-is; MyBatis wraps an array parameter into
a map, so a statement refers to a byte[]
+ * as #{array}.
+ */
+ private static Iterator<?> createIterator(Object in) {
+ if (in instanceof Map || in instanceof String || in instanceof byte[])
{
+ return Collections.singletonList(in).iterator();
+ }
+ return ObjectHelper.createIterator(in);
+ }
+
private Object getInput(final Exchange exchange) {
final String inputHeader = getEndpoint().getInputHeader();
if (inputHeader != null) {
diff --git
a/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisStringParameterTest.java
b/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisStringParameterTest.java
new file mode 100644
index 000000000000..dfa34f482284
--- /dev/null
+++
b/components/camel-mybatis/src/test/java/org/apache/camel/component/mybatis/MyBatisStringParameterTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.nio.charset.StandardCharsets;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * A String or byte[] body is one parameter, also when the String contains a
comma or is blank; collections, iterators
+ * and other arrays run the statement once per element.
+ */
+public class MyBatisStringParameterTest extends MyBatisTestSupport {
+
+ @Test
+ public void testInsertStringWithComma() {
+
template.sendBody("mybatis:insertAccountWithLastName?statementType=Insert",
"Doe, Jr.");
+
+ assertEquals(3, rowCount(), "There should be 3 rows");
+ Account account =
template.requestBody("mybatis:selectAccountById?statementType=SelectOne", 789,
Account.class);
+ assertEquals("Doe, Jr.", account.getLastName());
+ }
+
+ @Test
+ public void testUpdateStringWithComma() {
+
template.sendBody("mybatis:updateLastNameOfAccount123?statementType=Update",
"Strachan, Jr.");
+
+ Account account =
template.requestBody("mybatis:selectAccountById?statementType=SelectOne", 123,
Account.class);
+ assertEquals("Strachan, Jr.", account.getLastName());
+ }
+
+ @Test
+ public void testDeleteStringWithComma() {
+
template.sendBody("mybatis:updateLastNameOfAccount123?statementType=Update",
"Ibsen, Strachan");
+
+ // deletes account 123 only, not the account 456 with the last name
Ibsen
+
template.sendBody("mybatis:deleteAccountByLastName?statementType=Delete",
"Ibsen, Strachan");
+
+ assertEquals(1, rowCount(), "There should be 1 row");
+ Account account =
template.requestBody("mybatis:selectAccountById?statementType=SelectOne", 456,
Account.class);
+ assertEquals("Ibsen", account.getLastName());
+ }
+
+ @Test
+ public void testInsertBlankString() {
+
template.sendBody("mybatis:insertAccountWithLastName?statementType=Insert", "");
+
+ assertEquals(3, rowCount(), "There should be 3 rows");
+ Account account =
template.requestBody("mybatis:selectAccountById?statementType=SelectOne", 789,
Account.class);
+ assertEquals("", account.getLastName());
+ }
+
+ @Test
+ public void testInsertByteArray() {
+
template.sendBody("mybatis:insertAccountWithLastNameBytes?statementType=Insert",
+ "Doe".getBytes(StandardCharsets.UTF_8));
+
+ assertEquals(3, rowCount(), "There should be 3 rows");
+ Account account =
template.requestBody("mybatis:selectAccountById?statementType=SelectOne", 789,
Account.class);
+ assertEquals("Doe", account.getLastName());
+ }
+
+ @Test
+ public void testDeleteLongArrayRunsOncePerElement() {
+
template.sendBody("mybatis:deleteAccountByLongId?statementType=Delete", new
long[] { 123L, 456L });
+
+ assertEquals(0, rowCount(), "There should be 0 rows");
+ }
+
+ private int rowCount() {
+ return template.requestBody("mybatis:count?statementType=SelectOne",
null, Integer.class);
+ }
+}
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 207064d951cc..789547a47fe3 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
@@ -115,6 +115,30 @@ aliases to match the properties of the target result
class. -->
</foreach>
</delete>
+ <!-- Insert, update and delete examples, using a String as the parameter
class -->
+ <insert id="insertAccountWithLastName" parameterType="String">
+ insert into ACCOUNT (ACC_ID, ACC_FIRST_NAME, ACC_LAST_NAME, ACC_EMAIL)
+ values (789, 'Jane', #{lastName}, '[email protected]')
+ </insert>
+
+ <update id="updateLastNameOfAccount123" parameterType="String">
+ update ACCOUNT set ACC_LAST_NAME = #{lastName} where ACC_ID = 123
+ </update>
+
+ <delete id="deleteAccountByLastName" parameterType="String">
+ delete from ACCOUNT where ACC_LAST_NAME = #{lastName}
+ </delete>
+
+ <!-- A byte[] is one parameter, which MyBatis wraps into a map with the
key array -->
+ <insert id="insertAccountWithLastNameBytes">
+ insert into ACCOUNT (ACC_ID, ACC_FIRST_NAME, ACC_LAST_NAME, ACC_EMAIL)
+ values (789, 'Jane', UTF8TOSTRING(#{array}), '[email protected]')
+ </insert>
+
+ <delete id="deleteAccountByLongId" parameterType="long">
+ delete from ACCOUNT where ACC_ID = #{id}
+ </delete>
+
<!-- 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_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 5a2073eb349b..7b50fb025307 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -854,6 +854,15 @@ and most services, which were not registered at this level
already). Prior to Ca
every component. Use `RoutesOnly` (or `Default`) to keep the component MBeans.
The CamelContext, health check and route
controller MBeans are still registered.
+=== camel-mybatis - a String or byte[] body is one parameter (Breaking change)
+
+With `statementType=Insert`, `Update` or `Delete`, a `String` body (or input
header) is now passed to the statement as
+one parameter, also when it contains commas or is blank, and so is a `byte[]`.
MyBatis wraps an array parameter into a
+parameter map, so the statement refers to the `byte[]` as `+#{array}+`. Prior
to Camel 4.23 the String was split at each
+comma and the statement ran once for each part (once for each byte of a
`byte[]`, and not at all for a blank String).
+Collections, iterators and other arrays (including `int[]`, `long[]` and other
primitive arrays) still run the statement
+once per element; send a `List` to run it once per value.
+
=== camel-groovy
A `GroovyShellFactory` is now looked up in the registry once per
`CamelContext`, when the first groovy expression is