diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 66bba3618..da63dfb28 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -18,9 +18,9 @@ jobs:
name: Test JDK ${{ matrix.java }}, ${{ matrix.os }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Set up JDK ${{ matrix.java }} ${{ matrix.distribution }}
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
java-version: ${{ matrix.java }}
distribution: ${{ matrix.distribution }}
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 8e5c9c4b6..0016ded97 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -25,25 +25,25 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Setup Java
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
cache: maven
distribution: 'temurin'
java-version: 21
- name: Initialize CodeQL
- uses: github/codeql-action/init@v3
+ uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
- uses: github/codeql-action/autobuild@v3
+ uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v3
+ uses: github/codeql-action/analyze@v4
with:
category: "/language:${{ matrix.language }}"
diff --git a/.github/workflows/coveralls.yaml b/.github/workflows/coveralls.yaml
index b4aaaa991..de596cfd9 100644
--- a/.github/workflows/coveralls.yaml
+++ b/.github/workflows/coveralls.yaml
@@ -9,9 +9,9 @@ jobs:
if: github.repository_owner == 'mybatis'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Set up JDK
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
cache: maven
distribution: temurin
diff --git a/.github/workflows/site.yaml b/.github/workflows/site.yaml
index de1babe41..30e5957be 100644
--- a/.github/workflows/site.yaml
+++ b/.github/workflows/site.yaml
@@ -13,9 +13,9 @@ jobs:
if: github.repository_owner == 'mybatis' && ! contains(toJSON(github.event.head_commit.message), '[maven-release-plugin]')
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Set up JDK
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
cache: maven
distribution: temurin
diff --git a/.github/workflows/sonar.yaml b/.github/workflows/sonar.yaml
index 33c70609c..ed0d72515 100644
--- a/.github/workflows/sonar.yaml
+++ b/.github/workflows/sonar.yaml
@@ -12,12 +12,12 @@ jobs:
if: github.repository_owner == 'mybatis'
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
with:
# Disabling shallow clone is recommended for improving relevancy of reporting
fetch-depth: 0
- name: Set up JDK
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
cache: maven
distribution: temurin
diff --git a/.github/workflows/sonatype.yaml b/.github/workflows/sonatype.yaml
index c922f382c..57788412f 100644
--- a/.github/workflows/sonatype.yaml
+++ b/.github/workflows/sonatype.yaml
@@ -12,9 +12,9 @@ jobs:
if: github.repository_owner == 'mybatis' && ! contains(toJSON(github.event.head_commit.message), '[maven-release-plugin]')
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v6
- name: Set up JDK
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
cache: maven
distribution: temurin
diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java
index bdf0ddfa6..7e9c1e3c9 100644
--- a/.mvn/wrapper/MavenWrapperDownloader.java
+++ b/.mvn/wrapper/MavenWrapperDownloader.java
@@ -7,7 +7,7 @@
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * 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
@@ -30,7 +30,7 @@
import java.util.concurrent.ThreadLocalRandom;
public final class MavenWrapperDownloader {
- private static final String WRAPPER_VERSION = "3.3.2";
+ private static final String WRAPPER_VERSION = "3.3.4";
private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE"));
@@ -45,8 +45,11 @@ public static void main(String[] args) {
try {
log(" - Downloader started");
final URL wrapperUrl = URI.create(args[0]).toURL();
- final String jarPath = args[1].replace("..", ""); // Sanitize path
- final Path wrapperJarPath = Paths.get(jarPath).toAbsolutePath().normalize();
+ final Path baseDir = Paths.get(".").toAbsolutePath().normalize();
+ final Path wrapperJarPath = baseDir.resolve(args[1]).normalize();
+ if (!wrapperJarPath.startsWith(baseDir)) {
+ throw new IOException("Invalid path: outside of allowed directory");
+ }
downloadFileFromURL(wrapperUrl, wrapperJarPath);
log("Done");
} catch (IOException e) {
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index 0d35f6dbb..3b6c5d496 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1,20 +1,4 @@
-# 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
-#
-# https://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.
-wrapperVersion=3.3.2
+wrapperVersion=3.3.4
distributionType=source
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.10/apache-maven-3.9.10-bin.zip
-wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c65f2c09c..9375a78e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -107,6 +107,8 @@ Runtime behavior changes:
are not supported by the library out of the box. The statement renderers now call methods `renderCondition` and
`renderLeftColumn` that you can override to implement any rendering you need. In addition, we've made `filter` and
`map` support optional if you implement custom conditions
+- Added support for configuring a Java property name to be associated with an `SqlColumn`. This property name can be
+ used with the record based insert methods to reduce the boilerplate code for mapping columns to Java properties.
## Release 1.5.2 - June 3, 2024
diff --git a/mvnw b/mvnw
index 668388825..1ddd97b9e 100755
--- a/mvnw
+++ b/mvnw
@@ -8,7 +8,7 @@
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
-# https://www.apache.org/licenses/LICENSE-2.0
+# 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
@@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Apache Maven Wrapper startup batch script, version 3.3.2
+# Apache Maven Wrapper startup batch script, version 3.3.4
#
# Required ENV vars:
# ------------------
@@ -201,6 +201,14 @@ MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
export MAVEN_PROJECTBASEDIR
log "$MAVEN_PROJECTBASEDIR"
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
@@ -212,15 +220,13 @@ else
log "Couldn't find $wrapperJarPath, downloading it ..."
if [ -n "$MVNW_REPOURL" ]; then
- wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar"
else
- wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar"
fi
while IFS="=" read -r key value; do
- # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
- safeValue=$(echo "$value" | tr -d '\r')
case "$key" in wrapperUrl)
- wrapperUrl="$safeValue"
+ wrapperUrl=$(trim "${value-}")
break
;;
esac
@@ -235,17 +241,17 @@ else
log "Found wget ... using wget"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ wget ${QUIET:+"$QUIET"} "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
- wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ wget ${QUIET:+"$QUIET"} --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl >/dev/null; then
log "Found curl ... using curl"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ curl ${QUIET:+"$QUIET"} -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
else
- curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ curl ${QUIET:+"$QUIET"} --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
fi
else
log "Falling back to using Java to download"
@@ -276,7 +282,7 @@ fi
wrapperSha256Sum=""
while IFS="=" read -r key value; do
case "$key" in wrapperSha256Sum)
- wrapperSha256Sum=$value
+ wrapperSha256Sum=$(trim "${value-}")
break
;;
esac
@@ -284,7 +290,7 @@ done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
if [ -n "$wrapperSha256Sum" ]; then
wrapperSha256Result=false
if command -v sha256sum >/dev/null; then
- if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c >/dev/null 2>&1; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c - >/dev/null 2>&1; then
wrapperSha256Result=true
fi
elif command -v shasum >/dev/null; then
diff --git a/mvnw.cmd b/mvnw.cmd
index da4fe4dd9..8366e2170 100644
--- a/mvnw.cmd
+++ b/mvnw.cmd
@@ -7,7 +7,7 @@
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
-@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@@ -18,7 +18,7 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
-@REM Apache Maven Wrapper startup batch script, version 3.3.2
+@REM Apache Maven Wrapper startup batch script, version 3.3.4
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@@ -119,7 +119,7 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
@@ -133,7 +133,7 @@ if exist %WRAPPER_JAR% (
)
) else (
if not "%MVNW_REPOURL%" == "" (
- SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
diff --git a/pom.xml b/pom.xml
index 9e41feb33..b3c3b4a6b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
org.mybatismybatis-parent
- 50
+ 51org.mybatis.dynamic-sql
@@ -60,8 +60,8 @@
171717
- 5.13.3
- 5.2.2
+ 6.0.1
+ 6.0.0checkstyle-override.xml
@@ -69,7 +69,7 @@
org.mybatis.dynamic.sql
- 2.1.21
+ 2.3.0172.02.0
@@ -80,7 +80,7 @@
http://localhost:9000official
- 1.21.3
+ 2.0.3org.mybatis.dynamic.sql.*;version=${project.version};-noimport:=true
@@ -95,7 +95,7 @@
org.jetbrains.kotlin
- kotlin-stdlib-jdk8
+ kotlin-stdlib${kotlin.version}providedtrue
@@ -103,7 +103,7 @@
org.springframeworkspring-jdbc
- 6.2.8
+ 7.0.2providedtrue
@@ -136,13 +136,13 @@
org.assertjassertj-core
- 3.27.3
+ 3.27.6testorg.mybatismybatis-spring
- 3.0.5
+ 4.0.0test
@@ -172,49 +172,49 @@
ch.qos.logbacklogback-classic
- 1.5.18
+ 1.5.22testorg.testcontainers
- junit-jupiter
+ testcontainers-junit-jupiter${test.containers.version}testorg.testcontainers
- postgresql
+ testcontainers-postgresql${test.containers.version}testorg.postgresqlpostgresql
- 42.7.7
+ 42.7.8testorg.testcontainers
- mariadb
+ testcontainers-mariadb${test.containers.version}testorg.mariadb.jdbcmariadb-java-client
- 3.5.4
+ 3.5.7testorg.testcontainers
- mysql
+ testcontainers-mysql${test.containers.version}testcom.mysqlmysql-connector-j
- 9.3.0
+ 9.5.0test
diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java
index e178c6bf3..41c6a56e2 100644
--- a/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java
+++ b/src/main/java/org/mybatis/dynamic/sql/AbstractListValueCondition.java
@@ -23,7 +23,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import org.jspecify.annotations.NonNull;
import org.mybatis.dynamic.sql.render.RenderedParameterInfo;
import org.mybatis.dynamic.sql.render.RenderingContext;
import org.mybatis.dynamic.sql.util.FragmentAndParameters;
@@ -114,7 +113,7 @@ public interface Filterable {
* @return this condition if renderable and the value matches the predicate, otherwise a condition
* that will not render.
*/
- AbstractListValueCondition filter(Predicate super @NonNull T> predicate);
+ AbstractListValueCondition filter(Predicate super T> predicate);
}
/**
@@ -139,6 +138,6 @@ public interface Mappable {
* @return a new condition with the result of applying the mapper to the value of this condition,
* if renderable, otherwise a condition that will not render.
*/
- AbstractListValueCondition map(Function super @NonNull T, ? extends R> mapper);
+ AbstractListValueCondition map(Function super T, ? extends R> mapper);
}
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java
index eb56eef39..c16dbf08c 100644
--- a/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java
+++ b/src/main/java/org/mybatis/dynamic/sql/AbstractSingleValueCondition.java
@@ -21,7 +21,6 @@
import java.util.function.Predicate;
import java.util.function.Supplier;
-import org.jspecify.annotations.NonNull;
import org.mybatis.dynamic.sql.render.RenderedParameterInfo;
import org.mybatis.dynamic.sql.render.RenderingContext;
import org.mybatis.dynamic.sql.util.FragmentAndParameters;
@@ -89,7 +88,7 @@ public interface Filterable {
* @return this condition if renderable and the value matches the predicate, otherwise a condition
* that will not render.
*/
- AbstractSingleValueCondition filter(Predicate super @NonNull T> predicate);
+ AbstractSingleValueCondition filter(Predicate super T> predicate);
}
/**
@@ -114,6 +113,6 @@ public interface Mappable {
* @return a new condition with the result of applying the mapper to the value of this condition,
* if renderable, otherwise a condition that will not render.
*/
- AbstractSingleValueCondition map(Function super @NonNull T, ? extends R> mapper);
+ AbstractSingleValueCondition map(Function super T, ? extends R> mapper);
}
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java b/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java
index d409ffbb8..6cceff16e 100644
--- a/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java
+++ b/src/main/java/org/mybatis/dynamic/sql/AbstractTwoValueCondition.java
@@ -23,7 +23,6 @@
import java.util.function.Predicate;
import java.util.function.Supplier;
-import org.jspecify.annotations.NonNull;
import org.mybatis.dynamic.sql.render.RenderedParameterInfo;
import org.mybatis.dynamic.sql.render.RenderingContext;
import org.mybatis.dynamic.sql.util.FragmentAndParameters;
@@ -111,7 +110,7 @@ public interface Filterable {
* @return this condition if renderable and the values match the predicate, otherwise a condition
* that will not render.
*/
- AbstractTwoValueCondition filter(BiPredicate super @NonNull T, ? super @NonNull T> predicate);
+ AbstractTwoValueCondition filter(BiPredicate super T, ? super T> predicate);
/**
* If renderable and both values match the predicate, returns this condition. Else returns a condition
@@ -122,7 +121,7 @@ public interface Filterable {
* @return this condition if renderable and the values match the predicate, otherwise a condition
* that will not render.
*/
- AbstractTwoValueCondition filter(Predicate super @NonNull T> predicate);
+ AbstractTwoValueCondition filter(Predicate super T> predicate);
}
/**
@@ -148,8 +147,8 @@ public interface Mappable {
* @return a new condition with the result of applying the mappers to the values of this condition,
* if renderable, otherwise a condition that will not render.
*/
- AbstractTwoValueCondition map(Function super @NonNull T, ? extends R> mapper1,
- Function super @NonNull T, ? extends R> mapper2);
+ AbstractTwoValueCondition map(Function super T, ? extends R> mapper1,
+ Function super T, ? extends R> mapper2);
/**
* If renderable, apply the mapping to both values and return a new condition with the new values. Else return a
@@ -160,6 +159,6 @@ AbstractTwoValueCondition map(Function super @NonNull T, ? extends R> m
* @return a new condition with the result of applying the mappers to the values of this condition,
* if renderable, otherwise a condition that will not render.
*/
- AbstractTwoValueCondition map(Function super @NonNull T, ? extends R> mapper);
+ AbstractTwoValueCondition map(Function super T, ? extends R> mapper);
}
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java
index c8fe5c3ba..2a8243999 100644
--- a/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java
+++ b/src/main/java/org/mybatis/dynamic/sql/SqlBuilder.java
@@ -21,7 +21,6 @@
import java.util.Objects;
import java.util.function.Supplier;
-import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.mybatis.dynamic.sql.delete.DeleteDSL;
import org.mybatis.dynamic.sql.delete.DeleteModel;
@@ -782,11 +781,11 @@ static IsLessThanOrEqualToWhenPresent isLessThanOrEqualToWhenPresent(Supp
}
@SafeVarargs
- static IsIn isIn(@NonNull T... values) {
+ static IsIn isIn(T... values) {
return IsIn.of(values);
}
- static IsIn isIn(Collection<@NonNull T> values) {
+ static IsIn isIn(Collection values) {
return IsIn.of(values);
}
@@ -804,11 +803,11 @@ static IsInWhenPresent isInWhenPresent(@Nullable Collection<@Nullable T>
}
@SafeVarargs
- static IsNotIn isNotIn(@NonNull T... values) {
+ static IsNotIn isNotIn(T... values) {
return IsNotIn.of(values);
}
- static IsNotIn isNotIn(Collection<@NonNull T> values) {
+ static IsNotIn isNotIn(Collection values) {
return IsNotIn.of(values);
}
@@ -829,7 +828,7 @@ static IsBetween.Builder isBetween(T value1) {
return IsBetween.isBetween(value1);
}
- static IsBetween.Builder isBetween(Supplier<@NonNull T> valueSupplier1) {
+ static IsBetween.Builder isBetween(Supplier valueSupplier1) {
return isBetween(valueSupplier1.get());
}
@@ -845,7 +844,7 @@ static IsNotBetween.Builder isNotBetween(T value1) {
return IsNotBetween.isNotBetween(value1);
}
- static IsNotBetween.Builder isNotBetween(Supplier<@NonNull T> valueSupplier1) {
+ static IsNotBetween.Builder isNotBetween(Supplier valueSupplier1) {
return isNotBetween(valueSupplier1.get());
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java b/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java
index 4c841d86b..d8f9dc56e 100644
--- a/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java
+++ b/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java
@@ -25,6 +25,77 @@
import org.mybatis.dynamic.sql.util.FragmentAndParameters;
import org.mybatis.dynamic.sql.util.StringUtilities;
+/**
+ * This class represents the definition of a column in a table.
+ *
+ *
The class contains many attributes that are helpful for use in MyBatis and Spring runtime
+ * environments, but the only required attributes are the name of the column and a reference to
+ * the {@link SqlTable} the column is a part of.
+ *
+ *
The class can be extended if you wish to associate additional attributes with a column for your
+ * own purposes. Extending the class is a bit more challenging than you might expect because you may need to
+ * handle the covariant types for many methods in {@code SqlColumn}. Additionally, many methods in {@code SqlColumn}
+ * create new instances of the class in keeping with the library's primary strategy of immutability. You will also
+ * need to ensure that these methods create instances of your extended class, rather than the base {@code SqlColumn}
+ * class. We have worked to keep this process as simple as possible.
+ *
+ *
Extending the class involves the following activities:
+ *
+ *
Create a class that extends {@link SqlColumn}
+ *
In your extended class, create a static builder class that extends {@link SqlColumn.AbstractBuilder}
+ *
Add your desired attributes to the class and the builder
+ *
You MUST override the {@link SqlColumn#copyBuilder()} method and return a new instance of
+ * your builder with all attributes set. In the overridden method you should call the superclass
+ * {@link SqlColumn#populateBaseBuilder(AbstractBuilder)} method
+ * to set the attributes from the base {@code SqlColumn}, then populate your extended attributes. During normal
+ * usage, the library may create additional instances of your class. If you do not override the
+ * {@link SqlColumn#copyBuilder()} method properly, then your extended attributes will be lost.
+ *
+ *
You MAY override the following methods. These methods are used with regular operations in the library and
+ * create new instances of the class. However, these methods are not typically chained, so losing the specific
+ * type may not be a problem. If you want to preserve the type, then you can override these methods
+ * to specify the covariant return type. See below for usage of the {@link SqlColumn#cast(SqlColumn)} method
+ * to make it easier to override these methods.
+ *
+ *
{@link SqlColumn#as(String)}
+ *
{@link SqlColumn#asCamelCase()}
+ *
{@link SqlColumn#descending()}
+ *
{@link SqlColumn#qualifiedWith(String)}
+ *
+ *
+ *
You SHOULD override the following methods. These methods can be used to add additional attributes to a
+ * column by creating a new instance with a specified attribute set. These methods are used during the
+ * construction of columns. If you do not override these methods, and a user calls them, then the specific type
+ * will be lost. If you want to preserve the type, then you can override these methods
+ * to specify the covariant return type. See below for usage of the {@link SqlColumn#cast(SqlColumn)} method
+ * to make it easier to override these methods.
+ *
For all overridden methods except {@code copyBuilder()}, the process is to call the superclass
+ * method and cast the result properly. We provide a {@link SqlColumn#cast(SqlColumn)} method to aid with this
+ * process. For example, overriding the {@code descending} method could look like this:
+ *
+ *
The test code for this library contains an example of a fully executed extension of this class.
+ *
+ * @param the Java type associated with the column
+ */
public class SqlColumn implements BindableColumn, SortSpecification {
protected final String name;
@@ -37,8 +108,9 @@ public class SqlColumn implements BindableColumn, SortSpecification {
protected final ParameterTypeConverter parameterTypeConverter;
protected final @Nullable String tableQualifier;
protected final @Nullable Class javaType;
+ protected final @Nullable String javaProperty;
- private SqlColumn(Builder builder) {
+ protected SqlColumn(AbstractBuilder builder) {
name = Objects.requireNonNull(builder.name);
table = Objects.requireNonNull(builder.table);
jdbcType = builder.jdbcType;
@@ -49,6 +121,7 @@ private SqlColumn(Builder builder) {
parameterTypeConverter = Objects.requireNonNull(builder.parameterTypeConverter);
tableQualifier = builder.tableQualifier;
javaType = builder.javaType;
+ javaProperty = builder.javaProperty;
}
public String name() {
@@ -79,21 +152,36 @@ public Optional> javaType() {
return Optional.ofNullable(javaType);
}
+ public Optional javaProperty() {
+ return Optional.ofNullable(javaProperty);
+ }
+
@Override
public @Nullable Object convertParameterType(@Nullable T value) {
return value == null ? null : parameterTypeConverter.convert(value);
}
+ /**
+ * Create a new column instance that will render as descending when used in an order by phrase.
+ *
+ * @return a new column instance that will render as descending when used in an order by phrase
+ */
@Override
- public SortSpecification descending() {
- Builder b = copy();
- return b.withDescendingPhrase(" DESC").build(); //$NON-NLS-1$
+ public SqlColumn descending() {
+ return copyBuilder().withDescendingPhrase(" DESC").build(); //$NON-NLS-1$
}
+ /**
+ * Create a new column instance with the specified alias that will render as "as alias" in a column list.
+ *
+ * @param alias
+ * the column alias to set
+ *
+ * @return a new column instance with the specified alias
+ */
@Override
public SqlColumn as(String alias) {
- Builder b = copy();
- return b.withAlias(alias).build();
+ return copyBuilder().withAlias(alias).build();
}
/**
@@ -104,25 +192,24 @@ public SqlColumn as(String alias) {
* @return a new column that will be rendered with the specified table qualifier
*/
public SqlColumn qualifiedWith(String tableQualifier) {
- Builder b = copy();
- b.withTableQualifier(tableQualifier);
- return b.build();
+ return copyBuilder().withTableQualifier(tableQualifier).build();
}
/**
- * Set an alias with a camel cased string based on the column name. This can be useful for queries using
+ * Set an alias with a camel-cased string based on the column name. This can be useful for queries using
* the {@link org.mybatis.dynamic.sql.util.mybatis3.CommonSelectMapper} where the columns are placed into
* a map based on the column name returned from the database.
*
- *
A camel case string is mixed case, and most databases do not support unquoted mixed case strings
+ *
A camel case string is a mixed case string, and most databases do not support unquoted mixed case strings
* as identifiers. Therefore, the generated alias will be surrounded by double quotes thereby making it a
* quoted identifier. Most databases will respect quoted mixed case identifiers.
*
* @return a new column aliased with a camel case version of the column name
*/
public SqlColumn asCamelCase() {
- Builder b = copy();
- return b.withAlias("\"" + StringUtilities.toCamelCase(name) + "\"").build(); //$NON-NLS-1$ //$NON-NLS-2$
+ return copyBuilder()
+ .withAlias("\"" + StringUtilities.toCamelCase(name) + "\"") //$NON-NLS-1$ //$NON-NLS-2$
+ .build();
}
@Override
@@ -144,38 +231,126 @@ public Optional renderingStrategy() {
return Optional.ofNullable(renderingStrategy);
}
+ /**
+ * Create a new column instance with the specified type handler.
+ *
+ *
This method uses a different type (S). This allows it to be chained with the other
+ * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain.
+ * Without this different type (for example, if we used T), the compiler would erase the type after the call
+ * and method chaining would not work. This is a workaround for Java's lack of reification.
+ *
+ * @param typeHandler the type handler to set
+ * @param the type of the new column (will be the same as T)
+ * @return a new column instance with the specified type handler
+ */
public SqlColumn withTypeHandler(String typeHandler) {
- Builder b = copy();
- return b.withTypeHandler(typeHandler).build();
+ return cast(copyBuilder().withTypeHandler(typeHandler).build());
}
+ /**
+ * Create a new column instance with the specified rendering strategy.
+ *
+ *
This method uses a different type (S). This allows it to be chained with the other
+ * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain.
+ * Without this different type (for example, if we used T), the compiler would erase the type after the call
+ * and method chaining would not work. This is a workaround for Java's lack of reification.
+ *
+ * @param renderingStrategy the rendering strategy to set
+ * @param the type of the new column (will be the same as T)
+ * @return a new column instance with the specified type handler
+ */
public SqlColumn withRenderingStrategy(RenderingStrategy renderingStrategy) {
- Builder b = copy();
- return b.withRenderingStrategy(renderingStrategy).build();
+ return cast(copyBuilder().withRenderingStrategy(renderingStrategy).build());
}
+ /**
+ * Create a new column instance with the specified parameter type converter.
+ *
+ *
Parameter type converters are useful with Spring JDBC. Typically, they are not needed for MyBatis.
+ *
+ *
This method uses a different type (S). This allows it to be chained with the other
+ * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain.
+ * Without this different type (for example, if we used T), the compiler would erase the type after the call
+ * and method chaining would not work. This is a workaround for Java's lack of reification.
+ *
+ * @param parameterTypeConverter the parameter type converter to set
+ * @param the type of the new column (will be the same as T)
+ * @return a new column instance with the specified type handler
+ */
+ @SuppressWarnings("unchecked")
public SqlColumn withParameterTypeConverter(ParameterTypeConverter parameterTypeConverter) {
- Builder b = copy();
- return b.withParameterTypeConverter(parameterTypeConverter).build();
+ return cast(copyBuilder().withParameterTypeConverter((ParameterTypeConverter) parameterTypeConverter)
+ .build());
}
+ /**
+ * Create a new column instance with the specified Java type.
+ *
+ *
Specifying a Java type will force rendering of the Java type for MyBatis parameters. This can be useful
+ * with some MyBatis type handlers.
+ *
+ *
This method uses a different type (S). This allows it to be chained with the other
+ * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain.
+ * Without this different type (for example, if we used T), the compiler would erase the type after the call
+ * and method chaining would not work. This is a workaround for Java's lack of reification.
+ *
+ * @param javaType the Java type to set
+ * @param the type of the new column (will be the same as T)
+ * @return a new column instance with the specified type handler
+ */
+ @SuppressWarnings("unchecked")
public SqlColumn withJavaType(Class javaType) {
- Builder b = copy();
- return b.withJavaType(javaType).build();
+ return cast(copyBuilder().withJavaType((Class) javaType).build());
+ }
+
+ /**
+ * Create a new column instance with the specified Java property.
+ *
+ *
Specifying a Java property in the column will allow usage of the column as a "mapped column" in record-based
+ * insert statements.
+ *
+ *
This method uses a different type (S). This allows it to be chained with the other
+ * with* methods. Using new types forces the compiler to delay type inference until the end of a call chain.
+ * Without this different type (for example, if we used T), the compiler would erase the type after the call
+ * and method chaining would not work. This is a workaround for Java's lack of reification.
+ *
+ * @param javaProperty the Java property to set
+ * @param the type of the new column (will be the same as T)
+ * @return a new column instance with the specified type handler
+ */
+ public SqlColumn withJavaProperty(String javaProperty) {
+ return cast(copyBuilder().withJavaProperty(javaProperty).build());
+ }
+
+ /**
+ * Create a new Builder, then populate all attributes in the builder with current values.
+ *
+ *
This method is used to create copies of the class during normal operations (e.g. when calling the
+ * {@link SqlColumn#as(String)} method). Any subclass of {@code SqlColumn} MUST override this method.
+ *
+ * @return a new Builder instance with all current values populated
+ */
+ protected AbstractBuilder copyBuilder() {
+ return populateBaseBuilder(new Builder<>());
+ }
+
+ @SuppressWarnings("unchecked")
+ protected > S cast(SqlColumn> column) {
+ return (S) column;
}
/**
- * This method helps us tell a bit of fiction to the Java compiler. Java, for better or worse,
- * does not carry generic type information through chained methods. We want to enable method
- * chaining in the "with" methods. With this bit of fiction, we force the compiler to delay type
- * inference to the last method in the chain.
+ * This method will add all current attributes to the specified builder. It is useful when creating
+ * new class instances that only change one attribute - we set all current attributes, then
+ * change the one attribute. This utility can be used with the with* methods and other methods that
+ * create new instances.
*
- * @param the type. Will be the same as T for this usage.
- * @return a new SqlColumn of type S (S is the same as T)
+ * @param the concrete builder type
+ * @return the populated builder
*/
@SuppressWarnings("unchecked")
- private Builder copy() {
- return new Builder()
+ protected > B populateBaseBuilder(B builder) {
+ return (B) builder
.withName(this.name)
.withTable(this.table)
.withJdbcType(this.jdbcType)
@@ -183,9 +358,10 @@ private Builder copy() {
.withAlias(this.alias)
.withTypeHandler(this.typeHandler)
.withRenderingStrategy(this.renderingStrategy)
- .withParameterTypeConverter((ParameterTypeConverter) this.parameterTypeConverter)
+ .withParameterTypeConverter(this.parameterTypeConverter)
.withTableQualifier(this.tableQualifier)
- .withJavaType((Class) this.javaType);
+ .withJavaType(this.javaType)
+ .withJavaProperty(this.javaProperty);
}
public static SqlColumn of(String name, SqlTable table) {
@@ -201,7 +377,7 @@ public static SqlColumn of(String name, SqlTable table, JDBCType jdbcType
.build();
}
- public static class Builder {
+ public abstract static class AbstractBuilder, B extends AbstractBuilder> {
protected @Nullable String name;
protected @Nullable SqlTable table;
protected @Nullable JDBCType jdbcType;
@@ -212,59 +388,77 @@ public static class Builder {
protected ParameterTypeConverter parameterTypeConverter = v -> v;
protected @Nullable String tableQualifier;
protected @Nullable Class javaType;
+ protected @Nullable String javaProperty;
- public Builder withName(String name) {
+ public B withName(String name) {
this.name = name;
- return this;
+ return getThis();
}
- public Builder withTable(SqlTable table) {
+ public B withTable(SqlTable table) {
this.table = table;
- return this;
+ return getThis();
}
- public Builder withJdbcType(@Nullable JDBCType jdbcType) {
+ public B withJdbcType(@Nullable JDBCType jdbcType) {
this.jdbcType = jdbcType;
- return this;
+ return getThis();
}
- public Builder withDescendingPhrase(String descendingPhrase) {
+ public B withDescendingPhrase(String descendingPhrase) {
this.descendingPhrase = descendingPhrase;
- return this;
+ return getThis();
}
- public Builder withAlias(@Nullable String alias) {
+ public B withAlias(@Nullable String alias) {
this.alias = alias;
- return this;
+ return getThis();
}
- public Builder withTypeHandler(@Nullable String typeHandler) {
+ public B withTypeHandler(@Nullable String typeHandler) {
this.typeHandler = typeHandler;
- return this;
+ return getThis();
}
- public Builder withRenderingStrategy(@Nullable RenderingStrategy renderingStrategy) {
+ public B withRenderingStrategy(@Nullable RenderingStrategy renderingStrategy) {
this.renderingStrategy = renderingStrategy;
- return this;
+ return getThis();
}
- public Builder withParameterTypeConverter(ParameterTypeConverter parameterTypeConverter) {
+ public B withParameterTypeConverter(ParameterTypeConverter parameterTypeConverter) {
this.parameterTypeConverter = parameterTypeConverter;
- return this;
+ return getThis();
}
- private Builder withTableQualifier(@Nullable String tableQualifier) {
+ public B withTableQualifier(@Nullable String tableQualifier) {
this.tableQualifier = tableQualifier;
- return this;
+ return getThis();
}
- public Builder withJavaType(@Nullable Class javaType) {
+ public B withJavaType(@Nullable Class javaType) {
this.javaType = javaType;
- return this;
+ return getThis();
+ }
+
+ public B withJavaProperty(@Nullable String javaProperty) {
+ this.javaProperty = javaProperty;
+ return getThis();
}
+ protected abstract B getThis();
+
+ public abstract C build();
+ }
+
+ public static class Builder extends AbstractBuilder, Builder> {
+ @Override
public SqlColumn build() {
return new SqlColumn<>(this);
}
+
+ @Override
+ protected Builder getThis() {
+ return this;
+ }
}
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/BatchInsertDSL.java b/src/main/java/org/mybatis/dynamic/sql/insert/BatchInsertDSL.java
index 71bc350d6..c2938e9c1 100644
--- a/src/main/java/org/mybatis/dynamic/sql/insert/BatchInsertDSL.java
+++ b/src/main/java/org/mybatis/dynamic/sql/insert/BatchInsertDSL.java
@@ -27,6 +27,7 @@
import org.mybatis.dynamic.sql.util.AbstractColumnMapping;
import org.mybatis.dynamic.sql.util.Buildable;
import org.mybatis.dynamic.sql.util.ConstantMapping;
+import org.mybatis.dynamic.sql.util.MappedColumnMapping;
import org.mybatis.dynamic.sql.util.NullMapping;
import org.mybatis.dynamic.sql.util.PropertyMapping;
import org.mybatis.dynamic.sql.util.RowMapping;
@@ -48,6 +49,11 @@ public ColumnMappingFinisher map(SqlColumn column) {
return new ColumnMappingFinisher<>(column);
}
+ public BatchInsertDSL withMappedColumn(SqlColumn column) {
+ columnMappings.add(MappedColumnMapping.of(column));
+ return this;
+ }
+
@Override
public BatchInsertModel build() {
return BatchInsertModel.withRecords(records)
diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/InsertDSL.java b/src/main/java/org/mybatis/dynamic/sql/insert/InsertDSL.java
index d622ced8c..8b95a9c0f 100644
--- a/src/main/java/org/mybatis/dynamic/sql/insert/InsertDSL.java
+++ b/src/main/java/org/mybatis/dynamic/sql/insert/InsertDSL.java
@@ -27,6 +27,8 @@
import org.mybatis.dynamic.sql.util.AbstractColumnMapping;
import org.mybatis.dynamic.sql.util.Buildable;
import org.mybatis.dynamic.sql.util.ConstantMapping;
+import org.mybatis.dynamic.sql.util.MappedColumnMapping;
+import org.mybatis.dynamic.sql.util.MappedColumnWhenPresentMapping;
import org.mybatis.dynamic.sql.util.NullMapping;
import org.mybatis.dynamic.sql.util.PropertyMapping;
import org.mybatis.dynamic.sql.util.PropertyWhenPresentMapping;
@@ -49,6 +51,16 @@ public ColumnMappingFinisher map(SqlColumn column) {
return new ColumnMappingFinisher<>(column);
}
+ public InsertDSL withMappedColumn(SqlColumn column) {
+ columnMappings.add(MappedColumnMapping.of(column));
+ return this;
+ }
+
+ public InsertDSL withMappedColumnWhenPresent(SqlColumn column, Supplier> valueSupplier) {
+ columnMappings.add(MappedColumnWhenPresentMapping.of(column, valueSupplier));
+ return this;
+ }
+
@Override
public InsertModel build() {
return InsertModel.withRow(row)
diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/MultiRowInsertDSL.java b/src/main/java/org/mybatis/dynamic/sql/insert/MultiRowInsertDSL.java
index 8e5f30e49..87705c415 100644
--- a/src/main/java/org/mybatis/dynamic/sql/insert/MultiRowInsertDSL.java
+++ b/src/main/java/org/mybatis/dynamic/sql/insert/MultiRowInsertDSL.java
@@ -25,6 +25,7 @@
import org.mybatis.dynamic.sql.util.AbstractColumnMapping;
import org.mybatis.dynamic.sql.util.Buildable;
import org.mybatis.dynamic.sql.util.ConstantMapping;
+import org.mybatis.dynamic.sql.util.MappedColumnMapping;
import org.mybatis.dynamic.sql.util.NullMapping;
import org.mybatis.dynamic.sql.util.PropertyMapping;
import org.mybatis.dynamic.sql.util.RowMapping;
@@ -46,6 +47,11 @@ public ColumnMappingFinisher map(SqlColumn column) {
return new ColumnMappingFinisher<>(column);
}
+ public MultiRowInsertDSL withMappedColumn(SqlColumn column) {
+ columnMappings.add(MappedColumnMapping.of(column));
+ return this;
+ }
+
@Override
public MultiRowInsertModel build() {
return MultiRowInsertModel.withRecords(records)
diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderingUtilities.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderingUtilities.java
index de529a2b4..a65b56b85 100644
--- a/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderingUtilities.java
+++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/InsertRenderingUtilities.java
@@ -17,7 +17,10 @@
import static org.mybatis.dynamic.sql.util.StringUtilities.spaceBefore;
+import org.mybatis.dynamic.sql.SqlColumn;
import org.mybatis.dynamic.sql.SqlTable;
+import org.mybatis.dynamic.sql.exception.InvalidSqlException;
+import org.mybatis.dynamic.sql.util.Messages;
public class InsertRenderingUtilities {
private InsertRenderingUtilities() {}
@@ -33,4 +36,10 @@ public static String calculateInsertStatement(SqlTable table, FieldAndValueColle
public static String calculateInsertStatementStart(SqlTable table) {
return "insert into " + table.tableName(); //$NON-NLS-1$
}
+
+ public static String getMappedPropertyName(SqlColumn> column) {
+ return column.javaProperty().orElseThrow(() ->
+ new InvalidSqlException(Messages
+ .getString("ERROR.50", column.name()))); //$NON-NLS-1$
+ }
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowValuePhraseVisitor.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowValuePhraseVisitor.java
index ba2d2ffa5..216fdfcbf 100644
--- a/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowValuePhraseVisitor.java
+++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowValuePhraseVisitor.java
@@ -18,6 +18,7 @@
import org.mybatis.dynamic.sql.SqlColumn;
import org.mybatis.dynamic.sql.render.RenderingStrategy;
import org.mybatis.dynamic.sql.util.ConstantMapping;
+import org.mybatis.dynamic.sql.util.MappedColumnMapping;
import org.mybatis.dynamic.sql.util.MultiRowInsertMappingVisitor;
import org.mybatis.dynamic.sql.util.NullMapping;
import org.mybatis.dynamic.sql.util.PropertyMapping;
@@ -69,6 +70,16 @@ public FieldAndValueAndParameters visit(RowMapping mapping) {
.build();
}
+ @Override
+ public FieldAndValueAndParameters visit(MappedColumnMapping mapping) {
+ return FieldAndValueAndParameters.withFieldName(mapping.columnName())
+ .withValuePhrase(calculateJdbcPlaceholder(
+ mapping.column(),
+ InsertRenderingUtilities.getMappedPropertyName(mapping.column()))
+ )
+ .build();
+ }
+
private String calculateJdbcPlaceholder(SqlColumn> column) {
return column.renderingStrategy().orElse(renderingStrategy).getRecordBasedInsertBinding(column, prefix);
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/insert/render/ValuePhraseVisitor.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/ValuePhraseVisitor.java
index dccf0f14a..d628c77ef 100644
--- a/src/main/java/org/mybatis/dynamic/sql/insert/render/ValuePhraseVisitor.java
+++ b/src/main/java/org/mybatis/dynamic/sql/insert/render/ValuePhraseVisitor.java
@@ -21,6 +21,8 @@
import org.mybatis.dynamic.sql.render.RenderingStrategy;
import org.mybatis.dynamic.sql.util.ConstantMapping;
import org.mybatis.dynamic.sql.util.InsertMappingVisitor;
+import org.mybatis.dynamic.sql.util.MappedColumnMapping;
+import org.mybatis.dynamic.sql.util.MappedColumnWhenPresentMapping;
import org.mybatis.dynamic.sql.util.NullMapping;
import org.mybatis.dynamic.sql.util.PropertyMapping;
import org.mybatis.dynamic.sql.util.PropertyWhenPresentMapping;
@@ -80,6 +82,25 @@ public Optional visit(RowMapping mapping) {
.buildOptional();
}
+ @Override
+ public Optional visit(MappedColumnMapping mapping) {
+ return FieldAndValueAndParameters.withFieldName(mapping.columnName())
+ .withValuePhrase(calculateJdbcPlaceholder(
+ mapping.column(),
+ InsertRenderingUtilities.getMappedPropertyName(mapping.column()))
+ )
+ .buildOptional();
+ }
+
+ @Override
+ public Optional visit(MappedColumnWhenPresentMapping mapping) {
+ if (mapping.shouldRender()) {
+ return visit((MappedColumnMapping) mapping);
+ } else {
+ return Optional.empty();
+ }
+ }
+
private String calculateJdbcPlaceholder(SqlColumn> column) {
return column.renderingStrategy().orElse(renderingStrategy)
.getRecordBasedInsertBinding(column, "row"); //$NON-NLS-1$
diff --git a/src/main/java/org/mybatis/dynamic/sql/util/ColumnMappingVisitor.java b/src/main/java/org/mybatis/dynamic/sql/util/ColumnMappingVisitor.java
index 19f949f09..34516105b 100644
--- a/src/main/java/org/mybatis/dynamic/sql/util/ColumnMappingVisitor.java
+++ b/src/main/java/org/mybatis/dynamic/sql/util/ColumnMappingVisitor.java
@@ -52,4 +52,8 @@ public interface ColumnMappingVisitor {
R visit(ColumnToColumnMapping mapping);
R visit(RowMapping mapping);
+
+ R visit(MappedColumnMapping mapping);
+
+ R visit(MappedColumnWhenPresentMapping mapping);
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/util/GeneralInsertMappingVisitor.java b/src/main/java/org/mybatis/dynamic/sql/util/GeneralInsertMappingVisitor.java
index e55e3d100..21303d49b 100644
--- a/src/main/java/org/mybatis/dynamic/sql/util/GeneralInsertMappingVisitor.java
+++ b/src/main/java/org/mybatis/dynamic/sql/util/GeneralInsertMappingVisitor.java
@@ -40,4 +40,14 @@ public final R visit(ColumnToColumnMapping columnMapping) {
public final R visit(RowMapping mapping) {
throw new UnsupportedOperationException(Messages.getInternalErrorString(InternalError.INTERNAL_ERROR_14));
}
+
+ @Override
+ public R visit(MappedColumnMapping mapping) {
+ throw new UnsupportedOperationException(Messages.getInternalErrorString(InternalError.INTERNAL_ERROR_16));
+ }
+
+ @Override
+ public R visit(MappedColumnWhenPresentMapping mapping) {
+ throw new UnsupportedOperationException(Messages.getInternalErrorString(InternalError.INTERNAL_ERROR_17));
+ }
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/util/InternalError.java b/src/main/java/org/mybatis/dynamic/sql/util/InternalError.java
index ed6b0a3cc..e973dad55 100644
--- a/src/main/java/org/mybatis/dynamic/sql/util/InternalError.java
+++ b/src/main/java/org/mybatis/dynamic/sql/util/InternalError.java
@@ -33,7 +33,12 @@ public enum InternalError {
INTERNAL_ERROR_12(12),
INTERNAL_ERROR_13(13),
INTERNAL_ERROR_14(14),
- INTERNAL_ERROR_15(15);
+ INTERNAL_ERROR_15(15),
+ INTERNAL_ERROR_16(16),
+ INTERNAL_ERROR_17(17),
+ INTERNAL_ERROR_18(18),
+ INTERNAL_ERROR_19(19),
+ INTERNAL_ERROR_20(20);
private final int number;
diff --git a/src/main/java/org/mybatis/dynamic/sql/util/MappedColumnMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/MappedColumnMapping.java
new file mode 100644
index 000000000..bef0a57e0
--- /dev/null
+++ b/src/main/java/org/mybatis/dynamic/sql/util/MappedColumnMapping.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016-2025 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.mybatis.dynamic.sql.util;
+
+import org.mybatis.dynamic.sql.SqlColumn;
+
+public class MappedColumnMapping extends AbstractColumnMapping {
+
+ protected MappedColumnMapping(SqlColumn> column) {
+ super(column);
+ }
+
+ @Override
+ public R accept(ColumnMappingVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ public static MappedColumnMapping of(SqlColumn> column) {
+ return new MappedColumnMapping(column);
+ }
+}
diff --git a/src/main/java/org/mybatis/dynamic/sql/util/MappedColumnWhenPresentMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/MappedColumnWhenPresentMapping.java
new file mode 100644
index 000000000..268cae396
--- /dev/null
+++ b/src/main/java/org/mybatis/dynamic/sql/util/MappedColumnWhenPresentMapping.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016-2025 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://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.mybatis.dynamic.sql.util;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import org.mybatis.dynamic.sql.SqlColumn;
+
+public class MappedColumnWhenPresentMapping extends MappedColumnMapping {
+ private final Supplier> valueSupplier;
+
+ private MappedColumnWhenPresentMapping(SqlColumn> column, Supplier> valueSupplier) {
+ super(column);
+ this.valueSupplier = Objects.requireNonNull(valueSupplier);
+ }
+
+ public boolean shouldRender() {
+ return valueSupplier.get() != null;
+ }
+
+ @Override
+ public R accept(ColumnMappingVisitor visitor) {
+ return visitor.visit(this);
+ }
+
+ public static MappedColumnWhenPresentMapping of(SqlColumn> column, Supplier> valueSupplier) {
+ return new MappedColumnWhenPresentMapping(column, valueSupplier);
+ }
+}
diff --git a/src/main/java/org/mybatis/dynamic/sql/util/MultiRowInsertMappingVisitor.java b/src/main/java/org/mybatis/dynamic/sql/util/MultiRowInsertMappingVisitor.java
index 18a75221d..668d064e7 100644
--- a/src/main/java/org/mybatis/dynamic/sql/util/MultiRowInsertMappingVisitor.java
+++ b/src/main/java/org/mybatis/dynamic/sql/util/MultiRowInsertMappingVisitor.java
@@ -20,4 +20,9 @@ public abstract class MultiRowInsertMappingVisitor extends InsertMappingVisit
public final R visit(PropertyWhenPresentMapping mapping) {
throw new UnsupportedOperationException(Messages.getInternalErrorString(InternalError.INTERNAL_ERROR_12));
}
+
+ @Override
+ public R visit(MappedColumnWhenPresentMapping mapping) {
+ throw new UnsupportedOperationException(Messages.getInternalErrorString(InternalError.INTERNAL_ERROR_18));
+ }
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/util/UpdateMappingVisitor.java b/src/main/java/org/mybatis/dynamic/sql/util/UpdateMappingVisitor.java
index 8d06585ab..b6597685c 100644
--- a/src/main/java/org/mybatis/dynamic/sql/util/UpdateMappingVisitor.java
+++ b/src/main/java/org/mybatis/dynamic/sql/util/UpdateMappingVisitor.java
@@ -30,4 +30,14 @@ public final R visit(PropertyWhenPresentMapping mapping) {
public final R visit(RowMapping mapping) {
throw new UnsupportedOperationException(Messages.getInternalErrorString(InternalError.INTERNAL_ERROR_15));
}
+
+ @Override
+ public R visit(MappedColumnMapping mapping) {
+ throw new UnsupportedOperationException(Messages.getInternalErrorString(InternalError.INTERNAL_ERROR_19));
+ }
+
+ @Override
+ public R visit(MappedColumnWhenPresentMapping mapping) {
+ throw new UnsupportedOperationException(Messages.getInternalErrorString(InternalError.INTERNAL_ERROR_20));
+ }
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/util/Utilities.java b/src/main/java/org/mybatis/dynamic/sql/util/Utilities.java
index 0c3bd188f..b0c27081e 100644
--- a/src/main/java/org/mybatis/dynamic/sql/util/Utilities.java
+++ b/src/main/java/org/mybatis/dynamic/sql/util/Utilities.java
@@ -15,10 +15,19 @@
*/
package org.mybatis.dynamic.sql.util;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.stream.Stream;
+
import org.jspecify.annotations.Nullable;
public interface Utilities {
static long safelyUnbox(@Nullable Long l) {
return l == null ? 0 : l;
}
+
+ static Stream filterNulls(Collection<@Nullable T> values) {
+ // this method helps IntelliJ understand intended nullability
+ return values.stream().filter(Objects::nonNull);
+ }
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/AndGatherer.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/AndGatherer.java
index c9514f3fa..8a587262a 100644
--- a/src/main/java/org/mybatis/dynamic/sql/where/condition/AndGatherer.java
+++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/AndGatherer.java
@@ -17,8 +17,6 @@
import java.util.function.Supplier;
-import org.jspecify.annotations.NonNull;
-
/**
* Utility class supporting the "and" part of a between condition. This class supports builders, so it is mutable.
*
@@ -40,7 +38,7 @@ public R and(T value2) {
return build(value2);
}
- public R and(Supplier<@NonNull T> valueSupplier2) {
+ public R and(Supplier valueSupplier2) {
return and(valueSupplier2.get());
}
diff --git a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java
index 0f7fcd66a..ffc801508 100644
--- a/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java
+++ b/src/main/java/org/mybatis/dynamic/sql/where/condition/IsBetween.java
@@ -20,10 +20,9 @@
import java.util.function.Function;
import java.util.function.Predicate;
-import org.jspecify.annotations.NonNull;
import org.mybatis.dynamic.sql.AbstractTwoValueCondition;
-public class IsBetween extends AbstractTwoValueCondition<@NonNull T>
+public class IsBetween extends AbstractTwoValueCondition
implements AbstractTwoValueCondition.Filterable, AbstractTwoValueCondition.Mappable {
private static final IsBetween> EMPTY = new IsBetween