From 95f3fa98546719e047ac6c6117cfa415e9eb73ab Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 25 Jul 2025 16:54:38 -0400 Subject: [PATCH 1/6] Support "mappedColumn" on insert statements We added a "javaProperty" to SqlColumn so properties can be centrally configured, and then used on generated insert statements. This will make the mappers generated from MyBatis Generator less verbose. --- .../org/mybatis/dynamic/sql/SqlColumn.java | 20 +++++- .../mybatis/dynamic/sql/insert/InsertDSL.java | 12 ++++ .../dynamic/sql/insert/MultiRowInsertDSL.java | 6 ++ .../render/MultiRowValuePhraseVisitor.java | 19 ++++++ .../sql/insert/render/ValuePhraseVisitor.java | 29 +++++++++ .../sql/util/ColumnMappingVisitor.java | 4 ++ .../sql/util/GeneralInsertMappingVisitor.java | 10 +++ .../dynamic/sql/util/InternalError.java | 7 +- .../dynamic/sql/util/MappedColumnMapping.java | 34 ++++++++++ .../util/MappedColumnWhenPresentMapping.java | 43 +++++++++++++ .../util/MultiRowInsertMappingVisitor.java | 5 ++ .../sql/util/UpdateMappingVisitor.java | 10 +++ .../dynamic/sql/util/messages.properties | 1 + .../simple/PersonDynamicSqlSupport.java | 22 +++++-- .../java/examples/simple/PersonMapper.java | 42 ++++++------ .../examples/spring/PersonTemplateTest.java | 5 +- .../sql/insert/render/InsertVisitorsTest.java | 60 +++++++++++++++++ .../sql/util/ColumnMappingVisitorTest.java | 64 +++++++++++++++++++ 18 files changed, 360 insertions(+), 33 deletions(-) create mode 100644 src/main/java/org/mybatis/dynamic/sql/util/MappedColumnMapping.java create mode 100644 src/main/java/org/mybatis/dynamic/sql/util/MappedColumnWhenPresentMapping.java create mode 100644 src/test/java/org/mybatis/dynamic/sql/insert/render/InsertVisitorsTest.java diff --git a/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java b/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java index 4c841d86b..0d3a9ec44 100644 --- a/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java +++ b/src/main/java/org/mybatis/dynamic/sql/SqlColumn.java @@ -37,6 +37,7 @@ 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) { name = Objects.requireNonNull(builder.name); @@ -49,6 +50,7 @@ private SqlColumn(Builder builder) { parameterTypeConverter = Objects.requireNonNull(builder.parameterTypeConverter); tableQualifier = builder.tableQualifier; javaType = builder.javaType; + javaProperty = builder.javaProperty; } public String name() { @@ -79,6 +81,10 @@ 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); @@ -164,6 +170,11 @@ public SqlColumn withJavaType(Class javaType) { return b.withJavaType(javaType).build(); } + public SqlColumn withJavaProperty(String javaProperty) { + Builder b = copy(); + return b.withJavaProperty(javaProperty).build(); + } + /** * 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 @@ -185,7 +196,8 @@ private Builder copy() { .withRenderingStrategy(this.renderingStrategy) .withParameterTypeConverter((ParameterTypeConverter) this.parameterTypeConverter) .withTableQualifier(this.tableQualifier) - .withJavaType((Class) this.javaType); + .withJavaType((Class) this.javaType) + .withJavaProperty(this.javaProperty); } public static SqlColumn of(String name, SqlTable table) { @@ -212,6 +224,7 @@ 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) { this.name = name; @@ -263,6 +276,11 @@ public Builder withJavaType(@Nullable Class javaType) { return this; } + public Builder withJavaProperty(@Nullable String javaProperty) { + this.javaProperty = javaProperty; + return this; + } + public SqlColumn build() { return new SqlColumn<>(this); } 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/MultiRowValuePhraseVisitor.java b/src/main/java/org/mybatis/dynamic/sql/insert/render/MultiRowValuePhraseVisitor.java index ba2d2ffa5..49f362750 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 @@ -16,8 +16,11 @@ package org.mybatis.dynamic.sql.insert.render; import org.mybatis.dynamic.sql.SqlColumn; +import org.mybatis.dynamic.sql.exception.InvalidSqlException; 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.Messages; import org.mybatis.dynamic.sql.util.MultiRowInsertMappingVisitor; import org.mybatis.dynamic.sql.util.NullMapping; import org.mybatis.dynamic.sql.util.PropertyMapping; @@ -69,6 +72,22 @@ public FieldAndValueAndParameters visit(RowMapping mapping) { .build(); } + @Override + public FieldAndValueAndParameters visit(MappedColumnMapping mapping) { + return FieldAndValueAndParameters.withFieldName(mapping.columnName()) + .withValuePhrase(calculateJdbcPlaceholder( + mapping.column(), + getMappedPropertyName(mapping.column())) + ) + .build(); + } + + private String getMappedPropertyName(SqlColumn column) { + return column.javaProperty().orElseThrow(() -> + new InvalidSqlException(Messages + .getString("ERROR.50", column.name()))); //$NON-NLS-1$ + } + 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..a784f6e3b 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 @@ -18,9 +18,13 @@ import java.util.Optional; import org.mybatis.dynamic.sql.SqlColumn; +import org.mybatis.dynamic.sql.exception.InvalidSqlException; 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.Messages; import org.mybatis.dynamic.sql.util.NullMapping; import org.mybatis.dynamic.sql.util.PropertyMapping; import org.mybatis.dynamic.sql.util.PropertyWhenPresentMapping; @@ -80,6 +84,31 @@ public Optional visit(RowMapping mapping) { .buildOptional(); } + @Override + public Optional visit(MappedColumnMapping mapping) { + return FieldAndValueAndParameters.withFieldName(mapping.columnName()) + .withValuePhrase(calculateJdbcPlaceholder( + mapping.column(), + getMappedPropertyName(mapping.column())) + ) + .buildOptional(); + } + + @Override + public Optional visit(MappedColumnWhenPresentMapping mapping) { + if (mapping.shouldRender()) { + return visit((MappedColumnMapping) mapping); + } else { + return Optional.empty(); + } + } + + private String getMappedPropertyName(SqlColumn column) { + return column.javaProperty().orElseThrow(() -> + new InvalidSqlException(Messages + .getString("ERROR.50", column.name()))); //$NON-NLS-1$ + } + 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..2e0729ed0 --- /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 org.mybatis.dynamic.sql.SqlColumn; + +import java.util.Objects; +import java.util.function.Supplier; + +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/resources/org/mybatis/dynamic/sql/util/messages.properties b/src/main/resources/org/mybatis/dynamic/sql/util/messages.properties index 3cf560a78..89ea08e70 100644 --- a/src/main/resources/org/mybatis/dynamic/sql/util/messages.properties +++ b/src/main/resources/org/mybatis/dynamic/sql/util/messages.properties @@ -67,4 +67,5 @@ ERROR.47=A Kotlin case statement must specify a "then" clause for every "when" c ERROR.48=You cannot call more than one of "forUpdate", "forNoKeyUpdate", "forShare", or "forKeyShare" in a select \ statement ERROR.49=You cannot call more than one of "skipLocked", or "nowait" in a select statement +ERROR.50=Mapped column {0} does not have a javaProperty configured INTERNAL.ERROR=Internal Error {0} diff --git a/src/test/java/examples/simple/PersonDynamicSqlSupport.java b/src/test/java/examples/simple/PersonDynamicSqlSupport.java index 215f8a909..ba31eea5d 100644 --- a/src/test/java/examples/simple/PersonDynamicSqlSupport.java +++ b/src/test/java/examples/simple/PersonDynamicSqlSupport.java @@ -32,13 +32,21 @@ public final class PersonDynamicSqlSupport { public static final SqlColumn addressId = person.addressId; public static final class Person extends SqlTable { - public final SqlColumn id = column("id", JDBCType.INTEGER); - public final SqlColumn firstName = column("first_name", JDBCType.VARCHAR); - public final SqlColumn lastName = column("last_name", JDBCType.VARCHAR, "examples.simple.LastNameTypeHandler"); - public final SqlColumn birthDate = column("birth_date", JDBCType.DATE); - public final SqlColumn employed = column("employed", JDBCType.VARCHAR, "examples.simple.YesNoTypeHandler"); - public final SqlColumn occupation = column("occupation", JDBCType.VARCHAR); - public final SqlColumn addressId = column("address_id", JDBCType.INTEGER); + public final SqlColumn id = column("id", JDBCType.INTEGER).withJavaProperty("id"); + public final SqlColumn firstName = column("first_name", JDBCType.VARCHAR) + .withJavaProperty("firstName"); + public final SqlColumn lastName = + column("last_name", JDBCType.VARCHAR, "examples.simple.LastNameTypeHandler") + .withJavaProperty("lastName"); + public final SqlColumn birthDate = column("birth_date", JDBCType.DATE) + .withJavaProperty("birthDate"); + public final SqlColumn employed = + column("employed", JDBCType.VARCHAR, "examples.simple.YesNoTypeHandler") + .withJavaProperty("employed"); + public final SqlColumn occupation = column("occupation", JDBCType.VARCHAR) + .withJavaProperty("occupation"); + public final SqlColumn addressId = column("address_id", JDBCType.INTEGER) + .withJavaProperty("addressId"); public Person() { super("Person"); diff --git a/src/test/java/examples/simple/PersonMapper.java b/src/test/java/examples/simple/PersonMapper.java index 819b72c3b..371caaf0b 100644 --- a/src/test/java/examples/simple/PersonMapper.java +++ b/src/test/java/examples/simple/PersonMapper.java @@ -106,13 +106,13 @@ default int generalInsert(UnaryOperator completer) { default int insert(PersonRecord row) { return MyBatis3Utils.insert(this::insert, row, person, c -> - c.map(id).toProperty("id") - .map(firstName).toProperty("firstName") - .map(lastName).toProperty("lastName") - .map(birthDate).toProperty("birthDate") - .map(employed).toProperty("employed") - .map(occupation).toProperty("occupation") - .map(addressId).toProperty("addressId") + c.withMappedColumn(id) + .withMappedColumn(firstName) + .withMappedColumn(lastName) + .withMappedColumn(birthDate) + .withMappedColumn(employed) + .withMappedColumn(occupation) + .withMappedColumn(addressId) ); } @@ -122,25 +122,25 @@ default int insertMultiple(PersonRecord...records) { default int insertMultiple(Collection records) { return MyBatis3Utils.insertMultiple(this::insertMultiple, records, person, c -> - c.map(id).toProperty("id") - .map(firstName).toProperty("firstName") - .map(lastName).toProperty("lastName") - .map(birthDate).toProperty("birthDate") - .map(employed).toProperty("employed") - .map(occupation).toProperty("occupation") - .map(addressId).toProperty("addressId") + c.withMappedColumn(id) + .withMappedColumn(firstName) + .withMappedColumn(lastName) + .withMappedColumn(birthDate) + .withMappedColumn(employed) + .withMappedColumn(occupation) + .withMappedColumn(addressId) ); } default int insertSelective(PersonRecord row) { return MyBatis3Utils.insert(this::insert, row, person, c -> - c.map(id).toPropertyWhenPresent("id", row::id) - .map(firstName).toPropertyWhenPresent("firstName", row::firstName) - .map(lastName).toPropertyWhenPresent("lastName", row::lastName) - .map(birthDate).toPropertyWhenPresent("birthDate", row::birthDate) - .map(employed).toPropertyWhenPresent("employed", row::employed) - .map(occupation).toPropertyWhenPresent("occupation", row::occupation) - .map(addressId).toPropertyWhenPresent("addressId", row::addressId) + c.withMappedColumnWhenPresent(id, row::id) + .withMappedColumnWhenPresent(firstName, row::firstName) + .withMappedColumnWhenPresent(lastName, row::lastName) + .withMappedColumnWhenPresent(birthDate, row::birthDate) + .withMappedColumnWhenPresent(employed, row::employed) + .withMappedColumnWhenPresent(occupation, row::occupation) + .withMappedColumnWhenPresent(addressId, row::addressId) ); } diff --git a/src/test/java/examples/spring/PersonTemplateTest.java b/src/test/java/examples/spring/PersonTemplateTest.java index b92c03a50..9e8de9813 100644 --- a/src/test/java/examples/spring/PersonTemplateTest.java +++ b/src/test/java/examples/spring/PersonTemplateTest.java @@ -201,9 +201,8 @@ void testFirstNameIn() { List rows = template.selectList(selectStatement, personRowMapper); - assertThat(rows).hasSize(2); - - assertThat(rows).satisfiesExactly( + assertThat(rows).hasSize(2) + .satisfiesExactly( person1 -> assertThat(person1).isNotNull() .extracting("lastName").isNotNull() .extracting("name").isEqualTo("Flintstone"), diff --git a/src/test/java/org/mybatis/dynamic/sql/insert/render/InsertVisitorsTest.java b/src/test/java/org/mybatis/dynamic/sql/insert/render/InsertVisitorsTest.java new file mode 100644 index 000000000..f44b0930c --- /dev/null +++ b/src/test/java/org/mybatis/dynamic/sql/insert/render/InsertVisitorsTest.java @@ -0,0 +1,60 @@ +/* + * 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.insert.render; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import org.junit.jupiter.api.Test; +import org.mybatis.dynamic.sql.SqlColumn; +import org.mybatis.dynamic.sql.SqlTable; +import org.mybatis.dynamic.sql.exception.InvalidSqlException; +import org.mybatis.dynamic.sql.render.RenderingStrategies; +import org.mybatis.dynamic.sql.util.MappedColumnMapping; +import org.mybatis.dynamic.sql.util.Messages; + +class InsertVisitorsTest { + @Test + void testThatMultiRowInsertVisitorErrorsForMappedColumnWhenPropertyIsMissing() { + TestTable table = new TestTable(); + MultiRowValuePhraseVisitor tv = new MultiRowValuePhraseVisitor(RenderingStrategies.MYBATIS3, "prefix"); + MappedColumnMapping mapping = MappedColumnMapping.of(table.id); + + assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(() -> tv.visit(mapping)) + .withMessage(Messages.getString("ERROR.50", table.id.name())); + } + + @Test + void testThatValuePhraseVisitorErrorsForMappedColumnWhenPropertyIsMissing() { + TestTable table = new TestTable(); + ValuePhraseVisitor tv = new ValuePhraseVisitor(RenderingStrategies.MYBATIS3); + MappedColumnMapping mapping = MappedColumnMapping.of(table.id); + + assertThatExceptionOfType(InvalidSqlException.class).isThrownBy(() -> tv.visit(mapping)) + .withMessage(Messages.getString("ERROR.50", table.id.name())); + } + + private static class TestTable extends SqlTable { + public final SqlColumn id; + public final SqlColumn description; + + public TestTable() { + super("Test"); + + id = column("id"); + description = column("description"); + } + } +} diff --git a/src/test/java/org/mybatis/dynamic/sql/util/ColumnMappingVisitorTest.java b/src/test/java/org/mybatis/dynamic/sql/util/ColumnMappingVisitorTest.java index 2f17910b2..9369a35ce 100644 --- a/src/test/java/org/mybatis/dynamic/sql/util/ColumnMappingVisitorTest.java +++ b/src/test/java/org/mybatis/dynamic/sql/util/ColumnMappingVisitorTest.java @@ -204,6 +204,56 @@ void testThatUpdateVisitorErrorsForRowMapping() { .withMessage("Internal Error 15"); } + @Test + void testThatUpdateVisitorErrorsForMappedColumnMapping() { + TestTable table = new TestTable(); + UpdateVisitor tv = new UpdateVisitor(); + MappedColumnMapping mapping = MappedColumnMapping.of(table.id); + + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> tv.visit(mapping)) + .withMessage("Internal Error 19"); + } + + @Test + void testThatUpdateVisitorErrorsForMappedWhenPresentColumnMapping() { + TestTable table = new TestTable(); + UpdateVisitor tv = new UpdateVisitor(); + MappedColumnWhenPresentMapping mapping = MappedColumnWhenPresentMapping.of(table.id, () -> 1); + + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> tv.visit(mapping)) + .withMessage("Internal Error 20"); + } + + @Test + void testThatGeneralInsertVisitorErrorsForMappedColumnMapping() { + TestTable table = new TestTable(); + GeneralInsertVisitor tv = new GeneralInsertVisitor(); + MappedColumnMapping mapping = MappedColumnMapping.of(table.id); + + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> tv.visit(mapping)) + .withMessage("Internal Error 16"); + } + + @Test + void testThatGeneralInsertVisitorErrorsForMappedWhenPresentColumnMapping() { + TestTable table = new TestTable(); + GeneralInsertVisitor tv = new GeneralInsertVisitor(); + MappedColumnWhenPresentMapping mapping = MappedColumnWhenPresentMapping.of(table.id, () -> 1); + + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> tv.visit(mapping)) + .withMessage("Internal Error 17"); + } + + @Test + void testThatMultiRowInsertVisitorErrorsForMappedColumnWhenPresentMapping() { + TestTable table = new TestTable(); + MultiRowInsertVisitor tv = new MultiRowInsertVisitor(); + MappedColumnWhenPresentMapping mapping = MappedColumnWhenPresentMapping.of(table.id, () -> 1); + + assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> tv.visit(mapping)) + .withMessage("Internal Error 18"); + } + private static class TestTable extends SqlTable { public final SqlColumn id; public final SqlColumn description; @@ -278,6 +328,16 @@ public String visit(PropertyWhenPresentMapping mapping) { public String visit(RowMapping mapping) { return "Row Mapping"; } + + @Override + public String visit(MappedColumnMapping mapping) { + return "Mapped Column Mapping"; + } + + @Override + public String visit(MappedColumnWhenPresentMapping mapping) { + return "Mapped Column When Present Mapping"; + } } private static class UpdateVisitor extends UpdateMappingVisitor { @@ -349,5 +409,9 @@ public String visit(RowMapping mapping) { return "Row Mapping"; } + @Override + public String visit(MappedColumnMapping mapping) { + return "Mapped Column Mapping"; + } } } From 3897fbae98776b337b6bea67592a58bb33c42ae4 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 25 Jul 2025 16:58:48 -0400 Subject: [PATCH 2/6] checkstyle --- .../dynamic/sql/util/MappedColumnWhenPresentMapping.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/mybatis/dynamic/sql/util/MappedColumnWhenPresentMapping.java b/src/main/java/org/mybatis/dynamic/sql/util/MappedColumnWhenPresentMapping.java index 2e0729ed0..268cae396 100644 --- a/src/main/java/org/mybatis/dynamic/sql/util/MappedColumnWhenPresentMapping.java +++ b/src/main/java/org/mybatis/dynamic/sql/util/MappedColumnWhenPresentMapping.java @@ -15,11 +15,11 @@ */ package org.mybatis.dynamic.sql.util; -import org.mybatis.dynamic.sql.SqlColumn; - import java.util.Objects; import java.util.function.Supplier; +import org.mybatis.dynamic.sql.SqlColumn; + public class MappedColumnWhenPresentMapping extends MappedColumnMapping { private final Supplier valueSupplier; From ac2e6dce4b7c431942902caf6bbd649a84ba22fc Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Fri, 25 Jul 2025 17:21:19 -0400 Subject: [PATCH 3/6] Add Kotlin Support for MappedColumns with inserts --- .../util/kotlin/KotlinBatchInsertBuilder.kt | 5 ++ .../sql/util/kotlin/KotlinInsertBuilder.kt | 10 ++++ .../kotlin/KotlinMultiRowInsertBuilder.kt | 5 ++ .../kotlin/elements/SqlTableExtensions.kt | 4 +- .../canonical/PersonDynamicSqlSupport.kt | 16 +++--- .../canonical/PersonMapperExtensions.kt | 56 +++++++++---------- 6 files changed, 60 insertions(+), 36 deletions(-) diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBatchInsertBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBatchInsertBuilder.kt index aa2dd3703..fed6bf1d6 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBatchInsertBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinBatchInsertBuilder.kt @@ -21,6 +21,7 @@ import org.mybatis.dynamic.sql.insert.BatchInsertDSL import org.mybatis.dynamic.sql.insert.BatchInsertModel import org.mybatis.dynamic.sql.util.AbstractColumnMapping import org.mybatis.dynamic.sql.util.Buildable +import org.mybatis.dynamic.sql.util.MappedColumnMapping typealias KotlinBatchInsertCompleter = KotlinBatchInsertBuilder.() -> Unit @@ -37,6 +38,10 @@ class KotlinBatchInsertBuilder (private val rows: Collection): Build columnMappings.add(it) } + fun withMappedColumn(column: SqlColumn) { + columnMappings.add(MappedColumnMapping.of(column)) + } + override fun build(): BatchInsertModel { assertNotNull(table, "ERROR.23") //$NON-NLS-1$ return with(BatchInsertDSL.Builder()) { diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertBuilder.kt index 8b76231f7..6b2784fed 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinInsertBuilder.kt @@ -21,6 +21,8 @@ import org.mybatis.dynamic.sql.insert.InsertDSL import org.mybatis.dynamic.sql.insert.InsertModel import org.mybatis.dynamic.sql.util.AbstractColumnMapping import org.mybatis.dynamic.sql.util.Buildable +import org.mybatis.dynamic.sql.util.MappedColumnMapping +import org.mybatis.dynamic.sql.util.MappedColumnWhenPresentMapping typealias KotlinInsertCompleter = KotlinInsertBuilder.() -> Unit @@ -37,6 +39,14 @@ class KotlinInsertBuilder (private val row: T): Buildable withMappedColumn(column: SqlColumn) { + columnMappings.add(MappedColumnMapping.of(column)) + } + + fun withMappedColumnWhenPresent(column: SqlColumn, valueSupplier: () -> Any?) { + columnMappings.add(MappedColumnWhenPresentMapping.of(column, valueSupplier)) + } + override fun build(): InsertModel { assertNotNull(table, "ERROR.25") //$NON-NLS-1$ return with(InsertDSL.Builder()) { diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiRowInsertBuilder.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiRowInsertBuilder.kt index b00a62aef..6b3d72d7c 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiRowInsertBuilder.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/KotlinMultiRowInsertBuilder.kt @@ -21,6 +21,7 @@ import org.mybatis.dynamic.sql.insert.MultiRowInsertDSL import org.mybatis.dynamic.sql.insert.MultiRowInsertModel import org.mybatis.dynamic.sql.util.AbstractColumnMapping import org.mybatis.dynamic.sql.util.Buildable +import org.mybatis.dynamic.sql.util.MappedColumnMapping typealias KotlinMultiRowInsertCompleter = KotlinMultiRowInsertBuilder.() -> Unit @@ -37,6 +38,10 @@ class KotlinMultiRowInsertBuilder (private val rows: Collection): Bu columnMappings.add(it) } + fun withMappedColumn(column: SqlColumn) { + columnMappings.add(MappedColumnMapping.of(column)) + } + override fun build(): MultiRowInsertModel { assertNotNull(table, "ERROR.26") //$NON-NLS-1$ return with(MultiRowInsertDSL.Builder()) { diff --git a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlTableExtensions.kt b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlTableExtensions.kt index bbd79dcd7..24962ef01 100644 --- a/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlTableExtensions.kt +++ b/src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/elements/SqlTableExtensions.kt @@ -34,7 +34,8 @@ fun SqlTable.column( typeHandler: String? = null, renderingStrategy: RenderingStrategy? = null, parameterTypeConverter: ((T?) -> Any?) = { it }, - javaType: KClass? = null + javaType: KClass? = null, + javaProperty: String? = null, ): SqlColumn = SqlColumn.Builder().run { withTable(this@column) withName(name) @@ -43,5 +44,6 @@ fun SqlTable.column( withRenderingStrategy(renderingStrategy) withParameterTypeConverter(parameterTypeConverter) withJavaType(javaType?.java) + withJavaProperty(javaProperty) build() } diff --git a/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonDynamicSqlSupport.kt b/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonDynamicSqlSupport.kt index 94e60161d..dbfbbecba 100644 --- a/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonDynamicSqlSupport.kt +++ b/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonDynamicSqlSupport.kt @@ -31,20 +31,22 @@ object PersonDynamicSqlSupport { val addressId = person.addressId class Person : SqlTable("Person") { - val id = column(name = "id", jdbcType = JDBCType.INTEGER) - val firstName = column(name = "first_name", jdbcType = JDBCType.VARCHAR) + val id = column(name = "id", jdbcType = JDBCType.INTEGER, javaProperty = "id") + val firstName = column(name = "first_name", jdbcType = JDBCType.VARCHAR, javaProperty = "firstName") val lastName = column( name = "last_name", jdbcType = JDBCType.VARCHAR, - typeHandler = "examples.kotlin.mybatis3.canonical.LastNameTypeHandler" + typeHandler = "examples.kotlin.mybatis3.canonical.LastNameTypeHandler", + javaProperty = "lastName" ) - val birthDate = column(name = "birth_date", jdbcType = JDBCType.DATE) + val birthDate = column(name = "birth_date", jdbcType = JDBCType.DATE, javaProperty = "birthDate") val employed = column( name = "employed", JDBCType.VARCHAR, - typeHandler = "examples.kotlin.mybatis3.canonical.YesNoTypeHandler" + typeHandler = "examples.kotlin.mybatis3.canonical.YesNoTypeHandler", + javaProperty = "employed" ) - val occupation = column(name = "occupation", jdbcType = JDBCType.VARCHAR) - val addressId = column(name = "address_id", jdbcType = JDBCType.INTEGER) + val occupation = column(name = "occupation", jdbcType = JDBCType.VARCHAR, javaProperty = "occupation") + val addressId = column(name = "address_id", jdbcType = JDBCType.INTEGER, javaProperty = "addressId") } } diff --git a/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonMapperExtensions.kt b/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonMapperExtensions.kt index 237b5ebd7..184805bae 100644 --- a/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonMapperExtensions.kt +++ b/src/test/kotlin/examples/kotlin/mybatis3/canonical/PersonMapperExtensions.kt @@ -65,13 +65,13 @@ fun PersonMapper.deleteByPrimaryKey(id_: Int) = fun PersonMapper.insert(record: PersonRecord) = insert(this::insert, record, person) { - map(id) toProperty "id" - map(firstName) toProperty "firstName" - map(lastName) toProperty "lastName" - map(birthDate) toProperty "birthDate" - map(employed) toProperty "employed" - map(occupation) toProperty "occupation" - map(addressId) toProperty "addressId" + withMappedColumn(id) + withMappedColumn(firstName) + withMappedColumn(lastName) + withMappedColumn(birthDate) + withMappedColumn(employed) + withMappedColumn(occupation) + withMappedColumn(addressId) } fun PersonMapper.generalInsert(completer: GeneralInsertCompleter) = @@ -85,13 +85,13 @@ fun PersonMapper.insertBatch(vararg records: PersonRecord): List = fun PersonMapper.insertBatch(records: Collection): List = insertBatch(this::insert, records, person) { - map(id) toProperty "id" - map(firstName) toProperty "firstName" - map(lastName) toProperty "lastName" - map(birthDate) toProperty "birthDate" - map(employed) toProperty "employed" - map(occupation) toProperty "occupation" - map(addressId) toProperty "addressId" + withMappedColumn(id) + withMappedColumn(firstName) + withMappedColumn(lastName) + withMappedColumn(birthDate) + withMappedColumn(employed) + withMappedColumn(occupation) + withMappedColumn(addressId) } fun PersonMapper.insertMultiple(vararg records: PersonRecord) = @@ -99,24 +99,24 @@ fun PersonMapper.insertMultiple(vararg records: PersonRecord) = fun PersonMapper.insertMultiple(records: Collection) = insertMultiple(this::insertMultiple, records, person) { - map(id) toProperty "id" - map(firstName) toProperty "firstName" - map(lastName) toProperty "lastName" - map(birthDate) toProperty "birthDate" - map(employed) toProperty "employed" - map(occupation) toProperty "occupation" - map(addressId) toProperty "addressId" + withMappedColumn(id) + withMappedColumn(firstName) + withMappedColumn(lastName) + withMappedColumn(birthDate) + withMappedColumn(employed) + withMappedColumn(occupation) + withMappedColumn(addressId) } fun PersonMapper.insertSelective(record: PersonRecord) = insert(this::insert, record, person) { - map(id).toPropertyWhenPresent("id", record::id) - map(firstName).toPropertyWhenPresent("firstName", record::firstName) - map(lastName).toPropertyWhenPresent("lastName", record::lastName) - map(birthDate).toPropertyWhenPresent("birthDate", record::birthDate) - map(employed).toPropertyWhenPresent("employed", record::employed) - map(occupation).toPropertyWhenPresent("occupation", record::occupation) - map(addressId).toPropertyWhenPresent("addressId", record::addressId) + withMappedColumnWhenPresent(id, record::id) + withMappedColumnWhenPresent(firstName, record::firstName) + withMappedColumnWhenPresent(lastName, record::lastName) + withMappedColumnWhenPresent(birthDate, record::birthDate) + withMappedColumnWhenPresent(employed, record::employed) + withMappedColumnWhenPresent(occupation, record::occupation) + withMappedColumnWhenPresent(addressId, record::addressId) } private val columnList = listOf(id `as` "A_ID", firstName, lastName, birthDate, employed, occupation, addressId) From 0b8c93e924cdfe50f01eaf421f6e8f3645a73a28 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Mon, 28 Jul 2025 09:50:49 -0400 Subject: [PATCH 4/6] Add MappedColumn support for batch inserts in Java --- .../org/mybatis/dynamic/sql/insert/BatchInsertDSL.java | 6 ++++++ .../always/spring/GeneratedAlwaysDynamicSqlSupport.java | 8 ++++---- .../java/examples/generated/always/spring/SpringTest.java | 6 +++--- 3 files changed, 13 insertions(+), 7 deletions(-) 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/test/java/examples/generated/always/spring/GeneratedAlwaysDynamicSqlSupport.java b/src/test/java/examples/generated/always/spring/GeneratedAlwaysDynamicSqlSupport.java index 9a96f8bc2..cc0af0058 100644 --- a/src/test/java/examples/generated/always/spring/GeneratedAlwaysDynamicSqlSupport.java +++ b/src/test/java/examples/generated/always/spring/GeneratedAlwaysDynamicSqlSupport.java @@ -26,10 +26,10 @@ public final class GeneratedAlwaysDynamicSqlSupport { public static final SqlColumn fullName = generatedAlways.fullName; public static final class GeneratedAlways extends SqlTable { - public final SqlColumn id = column("id"); - public final SqlColumn firstName = column("first_name"); - public final SqlColumn lastName = column("last_name"); - public final SqlColumn fullName = column("full_name"); + public final SqlColumn id = column("id").withJavaProperty("id"); + public final SqlColumn firstName = column("first_name").withJavaProperty("firstName"); + public final SqlColumn lastName = column("last_name").withJavaProperty("lastName"); + public final SqlColumn fullName = column("full_name").withJavaProperty("fullName"); public GeneratedAlways() { super("GeneratedAlways"); diff --git a/src/test/java/examples/generated/always/spring/SpringTest.java b/src/test/java/examples/generated/always/spring/SpringTest.java index 59a241368..5fd9b59d1 100644 --- a/src/test/java/examples/generated/always/spring/SpringTest.java +++ b/src/test/java/examples/generated/always/spring/SpringTest.java @@ -241,9 +241,9 @@ void testInsertBatch() { BatchInsert batchInsert = insertBatch(records) .into(generatedAlways) - .map(id).toProperty("id") - .map(firstName).toProperty("firstName") - .map(lastName).toProperty("lastName") + .withMappedColumn(id) + .withMappedColumn(firstName) + .withMappedColumn(lastName) .build() .render(RenderingStrategies.SPRING_NAMED_PARAMETER); From bc175524be9c2175c826cdde71e65b33617fdbb6 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Tue, 29 Jul 2025 14:48:38 -0400 Subject: [PATCH 5/6] Documentation --- CHANGELOG.md | 2 + src/site/markdown/docs/insert.md | 60 +++++++++++++++++++ .../examples/simple/PersonMapperTest.java | 22 +++++++ 3 files changed, 84 insertions(+) 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/src/site/markdown/docs/insert.md b/src/site/markdown/docs/insert.md index b7fe11a70..6ac2e0302 100644 --- a/src/site/markdown/docs/insert.md +++ b/src/site/markdown/docs/insert.md @@ -44,6 +44,66 @@ Notice the `map` method. It is used to map a database column to an attribute of 5. `map(column).toPropertyWhenPresent(property, Supplier valueSupplier)` will insert a value from the record into a column if the value is non-null. The value of the property will be bound to the SQL statement as a prepared statement parameter. This is used to generate a "selective" insert as defined in MyBatis Generator. 6. `map(column).toRow()` will insert the record itself into a column. This is appropriate when the "record" is a simple class like Integer or String. +### Mapped Columns +Starting in version 2.0.0 there are two new methods: + +1. `withMappedColumn(SqlColumn)` that will map a database column to a Java property based on a property name that can + be configured in an `SQLColumn`. +2. `withMappedColumnWhenPresent(SqlColumn, Supplier)` that will map a database column to a Java property based on a + property name that can be configured in an `SQLColumn`. The insert statement will only contain the mapped column when + the Supplier returns a non-null value (this method is for single record inserts only). + +This will allow you to configure mappings in a single place (the `SqlColumn`) and reuse them in multiple insert +statements. For example: + +```java +public final class PersonDynamicSqlSupport { + public static final Person person = new Person(); + public static final SqlColumn id = person.id; + public static final SqlColumn firstName = person.firstName; + public static final SqlColumn lastName = person.lastName; + + public static final class Person extends SqlTable { + public final SqlColumn id = column("id", JDBCType.INTEGER).withJavaProperty("id"); + public final SqlColumn firstName = column("first_name", JDBCType.VARCHAR) + .withJavaProperty("firstName"); + public final SqlColumn lastName = + column("last_name", JDBCType.VARCHAR).withJavaProperty("lastName"); + + public Person() { + super("Person"); + } + } +} +``` + +In this support class, each `SqlColumn` has a configured Java property. This property can be accessed in record based +inserts in the following way: + +```java + @Test + void testRawInsert() { + try (SqlSession session = sqlSessionFactory.openSession()) { + PersonMapper mapper = session.getMapper(PersonMapper.class); + PersonRecord row = new PersonRecord(100, "Joe", "Jones"); + + InsertStatementProvider insertStatement = insert(row).into(person) + .withMappedColumn(id) + .withMappedColumn(firstName) + .withMappedColumn(lastName) + .build().render(RenderingStrategies.MYBATIS3); + + int rows = mapper.insert(insertStatement); + assertThat(rows).isEqualTo(1); + } + } +``` + +In this test, the mapping between a column and the property of a record is calculated by reading the configured Java +property for each column. + +These new methods are available for the record based insert statements (`insert`, `insertMultiple`, `insertBatch`). + ### Annotated Mapper for Single Row Insert Statements The InsertStatementProvider object can be used as a parameter to a MyBatis mapper method directly. If you are using an annotated mapper, the insert method should look like this (with @Options added for generated values if necessary): diff --git a/src/test/java/examples/simple/PersonMapperTest.java b/src/test/java/examples/simple/PersonMapperTest.java index b02d52c4c..ca1e90792 100644 --- a/src/test/java/examples/simple/PersonMapperTest.java +++ b/src/test/java/examples/simple/PersonMapperTest.java @@ -53,6 +53,7 @@ import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider; import org.mybatis.dynamic.sql.exception.NonRenderingWhereClauseException; import org.mybatis.dynamic.sql.insert.render.GeneralInsertStatementProvider; +import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider; import org.mybatis.dynamic.sql.render.RenderingStrategies; import org.mybatis.dynamic.sql.select.CountDSLCompleter; import org.mybatis.dynamic.sql.select.SelectDSLCompleter; @@ -338,6 +339,27 @@ void testInsert() { } } + @Test + void testRawInsert() { + try (SqlSession session = sqlSessionFactory.openSession()) { + PersonMapper mapper = session.getMapper(PersonMapper.class); + PersonRecord row = new PersonRecord(100, "Joe", new LastName("Jones"), new Date(), true, "Developer", 1); + + InsertStatementProvider insertStatement = insert(row).into(person) + .withMappedColumn(id) + .withMappedColumn(firstName) + .withMappedColumn(lastName) + .withMappedColumn(birthDate) + .withMappedColumn(employed) + .withMappedColumn(occupation) + .withMappedColumn(addressId) + .build().render(RenderingStrategies.MYBATIS3); + + int rows = mapper.insert(insertStatement); + assertThat(rows).isEqualTo(1); + } + } + @Test void testGeneralInsert() { try (SqlSession session = sqlSessionFactory.openSession()) { From 997a11b1c93ab69ff894cd749600e0ed1ba96256 Mon Sep 17 00:00:00 2001 From: Jeff Butler Date: Mon, 11 Aug 2025 11:08:57 -0400 Subject: [PATCH 6/6] Remove copy/paste code --- .../sql/insert/render/InsertRenderingUtilities.java | 9 +++++++++ .../sql/insert/render/MultiRowValuePhraseVisitor.java | 10 +--------- .../dynamic/sql/insert/render/ValuePhraseVisitor.java | 10 +--------- 3 files changed, 11 insertions(+), 18 deletions(-) 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 49f362750..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 @@ -16,11 +16,9 @@ package org.mybatis.dynamic.sql.insert.render; import org.mybatis.dynamic.sql.SqlColumn; -import org.mybatis.dynamic.sql.exception.InvalidSqlException; 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.Messages; import org.mybatis.dynamic.sql.util.MultiRowInsertMappingVisitor; import org.mybatis.dynamic.sql.util.NullMapping; import org.mybatis.dynamic.sql.util.PropertyMapping; @@ -77,17 +75,11 @@ public FieldAndValueAndParameters visit(MappedColumnMapping mapping) { return FieldAndValueAndParameters.withFieldName(mapping.columnName()) .withValuePhrase(calculateJdbcPlaceholder( mapping.column(), - getMappedPropertyName(mapping.column())) + InsertRenderingUtilities.getMappedPropertyName(mapping.column())) ) .build(); } - private String getMappedPropertyName(SqlColumn column) { - return column.javaProperty().orElseThrow(() -> - new InvalidSqlException(Messages - .getString("ERROR.50", column.name()))); //$NON-NLS-1$ - } - 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 a784f6e3b..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 @@ -18,13 +18,11 @@ import java.util.Optional; import org.mybatis.dynamic.sql.SqlColumn; -import org.mybatis.dynamic.sql.exception.InvalidSqlException; 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.Messages; import org.mybatis.dynamic.sql.util.NullMapping; import org.mybatis.dynamic.sql.util.PropertyMapping; import org.mybatis.dynamic.sql.util.PropertyWhenPresentMapping; @@ -89,7 +87,7 @@ public Optional visit(MappedColumnMapping mapping) { return FieldAndValueAndParameters.withFieldName(mapping.columnName()) .withValuePhrase(calculateJdbcPlaceholder( mapping.column(), - getMappedPropertyName(mapping.column())) + InsertRenderingUtilities.getMappedPropertyName(mapping.column())) ) .buildOptional(); } @@ -103,12 +101,6 @@ public Optional visit(MappedColumnWhenPresentMapping } } - private String getMappedPropertyName(SqlColumn column) { - return column.javaProperty().orElseThrow(() -> - new InvalidSqlException(Messages - .getString("ERROR.50", column.name()))); //$NON-NLS-1$ - } - private String calculateJdbcPlaceholder(SqlColumn column) { return column.renderingStrategy().orElse(renderingStrategy) .getRecordBasedInsertBinding(column, "row"); //$NON-NLS-1$