Skip to content

Commit cc81895

Browse files
authored
Merge pull request #343 from jeffgbutler/value-or-null-mapping
Add Mapping for General Insert and Update that Renders null Values More Simply
2 parents b1fa3a6 + d84743e commit cc81895

File tree

10 files changed

+223
-11
lines changed

10 files changed

+223
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ Kotlin DSL.
9797
- Added support for the "exists" and "not exists" operator in where clauses ([#296](https://github.com/mybatis/mybatis-dynamic-sql/pull/296))
9898
- Refactored the built-in conditions ([#331](https://github.com/mybatis/mybatis-dynamic-sql/pull/331)) ([#336](https://github.com/mybatis/mybatis-dynamic-sql/pull/336))
9999
- Added composition functions for WhereApplier ([#335](https://github.com/mybatis/mybatis-dynamic-sql/pull/335))
100+
- Added a mapping for general insert and update statements that will render null values as "null" in the SQL ([#343](https://github.com/mybatis/mybatis-dynamic-sql/pull/343))
100101

101102
## Release 1.2.1 - September 29, 2020
102103

src/main/java/org/mybatis/dynamic/sql/insert/GeneralInsertDSL.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 the original author or authors.
2+
* Copyright 2016-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
2929
import org.mybatis.dynamic.sql.util.NullMapping;
3030
import org.mybatis.dynamic.sql.util.StringConstantMapping;
3131
import org.mybatis.dynamic.sql.util.ValueMapping;
32+
import org.mybatis.dynamic.sql.util.ValueOrNullMapping;
3233
import org.mybatis.dynamic.sql.util.ValueWhenPresentMapping;
3334

3435
public class GeneralInsertDSL implements Buildable<GeneralInsertModel> {
@@ -88,6 +89,15 @@ public GeneralInsertDSL toValue(Supplier<T> valueSupplier) {
8889
return GeneralInsertDSL.this;
8990
}
9091

92+
public GeneralInsertDSL toValueOrNull(T value) {
93+
return toValueOrNull(() -> value);
94+
}
95+
96+
public GeneralInsertDSL toValueOrNull(Supplier<T> valueSupplier) {
97+
insertMappings.add(ValueOrNullMapping.of(column, valueSupplier));
98+
return GeneralInsertDSL.this;
99+
}
100+
91101
public GeneralInsertDSL toValueWhenPresent(T value) {
92102
return toValueWhenPresent(() -> value);
93103
}

src/main/java/org/mybatis/dynamic/sql/insert/render/GeneralInsertValuePhraseVisitor.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 the original author or authors.
2+
* Copyright 2016-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import org.mybatis.dynamic.sql.util.NullMapping;
2727
import org.mybatis.dynamic.sql.util.StringConstantMapping;
2828
import org.mybatis.dynamic.sql.util.ValueMapping;
29+
import org.mybatis.dynamic.sql.util.ValueOrNullMapping;
2930
import org.mybatis.dynamic.sql.util.ValueWhenPresentMapping;
3031

3132
public class GeneralInsertValuePhraseVisitor extends GeneralInsertMappingVisitor<Optional<FieldAndValueAndParameters>> {
@@ -39,9 +40,7 @@ public GeneralInsertValuePhraseVisitor(RenderingStrategy renderingStrategy) {
3940

4041
@Override
4142
public Optional<FieldAndValueAndParameters> visit(NullMapping mapping) {
42-
return FieldAndValueAndParameters.withFieldName(mapping.columnName())
43-
.withValuePhrase("null") //$NON-NLS-1$
44-
.buildOptional();
43+
return buildNullFragment(mapping);
4544
}
4645

4746
@Override
@@ -63,6 +62,12 @@ public <T> Optional<FieldAndValueAndParameters> visit(ValueMapping<T> mapping) {
6362
return buildValueFragment(mapping, mapping.value());
6463
}
6564

65+
@Override
66+
public <T> Optional<FieldAndValueAndParameters> visit(ValueOrNullMapping<T> mapping) {
67+
return mapping.value().map(v -> buildValueFragment(mapping, v))
68+
.orElseGet(() -> buildNullFragment(mapping));
69+
}
70+
6671
@Override
6772
public <T> Optional<FieldAndValueAndParameters> visit(ValueWhenPresentMapping<T> mapping) {
6873
return mapping.value().flatMap(v -> buildValueFragment(mapping, v));
@@ -73,6 +78,12 @@ private Optional<FieldAndValueAndParameters> buildValueFragment(AbstractColumnMa
7378
return buildFragment(mapping, value);
7479
}
7580

81+
private Optional<FieldAndValueAndParameters> buildNullFragment(AbstractColumnMapping mapping) {
82+
return FieldAndValueAndParameters.withFieldName(mapping.columnName())
83+
.withValuePhrase("null") //$NON-NLS-1$
84+
.buildOptional();
85+
}
86+
7687
private Optional<FieldAndValueAndParameters> buildFragment(AbstractColumnMapping mapping, Object value) {
7788
String mapKey = RenderingStrategy.formatParameterMapKey(sequence);
7889

src/main/java/org/mybatis/dynamic/sql/update/UpdateDSL.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 the original author or authors.
2+
* Copyright 2016-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@
3434
import org.mybatis.dynamic.sql.util.SelectMapping;
3535
import org.mybatis.dynamic.sql.util.StringConstantMapping;
3636
import org.mybatis.dynamic.sql.util.ValueMapping;
37+
import org.mybatis.dynamic.sql.util.ValueOrNullMapping;
3738
import org.mybatis.dynamic.sql.util.ValueWhenPresentMapping;
3839
import org.mybatis.dynamic.sql.where.AbstractWhereDSL;
3940
import org.mybatis.dynamic.sql.where.AbstractWhereSupport;
@@ -126,6 +127,15 @@ public UpdateDSL<R> equalTo(BasicColumn rightColumn) {
126127
return UpdateDSL.this;
127128
}
128129

130+
public UpdateDSL<R> equalToOrNull(T value) {
131+
return equalToOrNull(() -> value);
132+
}
133+
134+
public UpdateDSL<R> equalToOrNull(Supplier<T> valueSupplier) {
135+
columnMappings.add(ValueOrNullMapping.of(column, valueSupplier));
136+
return UpdateDSL.this;
137+
}
138+
129139
public UpdateDSL<R> equalToWhenPresent(T value) {
130140
return equalToWhenPresent(() -> value);
131141
}

src/main/java/org/mybatis/dynamic/sql/update/render/SetPhraseVisitor.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 the original author or authors.
2+
* Copyright 2016-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,6 +33,7 @@
3333
import org.mybatis.dynamic.sql.util.StringConstantMapping;
3434
import org.mybatis.dynamic.sql.util.UpdateMappingVisitor;
3535
import org.mybatis.dynamic.sql.util.ValueMapping;
36+
import org.mybatis.dynamic.sql.util.ValueOrNullMapping;
3637
import org.mybatis.dynamic.sql.util.ValueWhenPresentMapping;
3738

3839
public class SetPhraseVisitor extends UpdateMappingVisitor<Optional<FragmentAndParameters>> {
@@ -74,6 +75,16 @@ public <T> Optional<FragmentAndParameters> visit(ValueMapping<T> mapping) {
7475
return buildFragment(mapping, mapping.value());
7576
}
7677

78+
@Override
79+
public <T> Optional<FragmentAndParameters> visit(ValueOrNullMapping<T> mapping) {
80+
return mapping.value()
81+
.map(v -> buildFragment(mapping, v))
82+
.orElseGet(() -> FragmentAndParameters
83+
.withFragment(mapping.columnName() + " = null") //$NON-NLS-1$
84+
.buildOptional()
85+
);
86+
}
87+
7788
@Override
7889
public <T> Optional<FragmentAndParameters> visit(ValueWhenPresentMapping<T> mapping) {
7990
return mapping.value().flatMap(v -> buildFragment(mapping, v));

src/main/java/org/mybatis/dynamic/sql/util/ColumnMappingVisitor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 the original author or authors.
2+
* Copyright 2016-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -38,6 +38,8 @@ public interface ColumnMappingVisitor<R> {
3838

3939
<T> R visit(ValueMapping<T> mapping);
4040

41+
<T> R visit(ValueOrNullMapping<T> mapping);
42+
4143
<T> R visit(ValueWhenPresentMapping<T> mapping);
4244

4345
R visit(SelectMapping mapping);

src/main/java/org/mybatis/dynamic/sql/util/InsertMappingVisitor.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 the original author or authors.
2+
* Copyright 2016-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,6 +21,11 @@ public final <T> R visit(ValueMapping<T> mapping) {
2121
throw new UnsupportedOperationException();
2222
}
2323

24+
@Override
25+
public final <T> R visit(ValueOrNullMapping<T> mapping) {
26+
throw new UnsupportedOperationException();
27+
}
28+
2429
@Override
2530
public final <T> R visit(ValueWhenPresentMapping<T> mapping) {
2631
throw new UnsupportedOperationException();
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2016-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.mybatis.dynamic.sql.util;
17+
18+
import java.util.Objects;
19+
import java.util.Optional;
20+
import java.util.function.Supplier;
21+
22+
import org.mybatis.dynamic.sql.SqlColumn;
23+
24+
public class ValueOrNullMapping<T> extends AbstractColumnMapping {
25+
26+
private final Supplier<T> valueSupplier;
27+
// keep a reference to the column so we don't lose the type
28+
private final SqlColumn<T> localColumn;
29+
30+
private ValueOrNullMapping(SqlColumn<T> column, Supplier<T> valueSupplier) {
31+
super(column);
32+
this.valueSupplier = Objects.requireNonNull(valueSupplier);
33+
localColumn = Objects.requireNonNull(column);
34+
}
35+
36+
public Optional<Object> value() {
37+
return Optional.ofNullable(localColumn.convertParameterType(valueSupplier.get()));
38+
}
39+
40+
@Override
41+
public <R> R accept(ColumnMappingVisitor<R> visitor) {
42+
return visitor.visit(this);
43+
}
44+
45+
public static <T> ValueOrNullMapping<T> of(SqlColumn<T> column, Supplier<T> valueSupplier) {
46+
return new ValueOrNullMapping<>(column, valueSupplier);
47+
}
48+
}

src/test/java/examples/animal/data/AnimalDataTest.java

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,6 +1406,46 @@ void testUpdate() {
14061406
}
14071407
}
14081408

1409+
@Test
1410+
void testUpdateValueOrNullWithValue() {
1411+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
1412+
AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class);
1413+
AnimalData record = new AnimalData();
1414+
record.setBodyWeight(2.6);
1415+
1416+
UpdateStatementProvider updateStatement = update(animalData)
1417+
.set(animalName).equalToOrNull("fred")
1418+
.where(id, isEqualTo(1))
1419+
.build()
1420+
.render(RenderingStrategies.MYBATIS3);
1421+
1422+
assertThat(updateStatement.getUpdateStatement()).isEqualTo(
1423+
"update AnimalData set animal_name = #{parameters.p1,jdbcType=VARCHAR} where id = #{parameters.p2,jdbcType=INTEGER}");
1424+
int rows = mapper.update(updateStatement);
1425+
assertThat(rows).isEqualTo(1);
1426+
}
1427+
}
1428+
1429+
@Test
1430+
void testUpdateValueOrNullWithNull() {
1431+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
1432+
AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class);
1433+
AnimalData record = new AnimalData();
1434+
record.setBodyWeight(2.6);
1435+
1436+
UpdateStatementProvider updateStatement = update(animalData)
1437+
.set(animalName).equalToOrNull((String) null)
1438+
.where(id, isEqualTo(1))
1439+
.build()
1440+
.render(RenderingStrategies.MYBATIS3);
1441+
1442+
assertThat(updateStatement.getUpdateStatement()).isEqualTo(
1443+
"update AnimalData set animal_name = null where id = #{parameters.p1,jdbcType=INTEGER}");
1444+
int rows = mapper.update(updateStatement);
1445+
assertThat(rows).isEqualTo(1);
1446+
}
1447+
}
1448+
14091449
@Test
14101450
void testInsert() {
14111451
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
@@ -1994,6 +2034,61 @@ void testGeneralInsert() {
19942034
}
19952035
}
19962036

2037+
@Test
2038+
void testGeneralInsertValueOrNullWithValue() {
2039+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
2040+
AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class);
2041+
2042+
GeneralInsertStatementProvider insertStatement = insertInto(animalData)
2043+
.set(id).toValue(101)
2044+
.set(animalName).toValueOrNull("Fred")
2045+
.set(brainWeight).toConstant("2.2")
2046+
.set(bodyWeight).toValue(4.5)
2047+
.build()
2048+
.render(RenderingStrategies.MYBATIS3);
2049+
2050+
String expected = "insert into AnimalData (id, animal_name, brain_weight, body_weight) "
2051+
+ "values (#{parameters.p1,jdbcType=INTEGER}, #{parameters.p2,jdbcType=VARCHAR}, 2.2, "
2052+
+ "#{parameters.p3,jdbcType=DOUBLE})";
2053+
2054+
assertThat(insertStatement.getInsertStatement()).isEqualTo(expected);
2055+
assertThat(insertStatement.getParameters()).hasSize(3);
2056+
assertThat(insertStatement.getParameters()).containsEntry("p1", 101);
2057+
assertThat(insertStatement.getParameters()).containsEntry("p2", "Fred");
2058+
assertThat(insertStatement.getParameters()).containsEntry("p3", 4.5);
2059+
2060+
int rows = mapper.generalInsert(insertStatement);
2061+
assertThat(rows).isEqualTo(1);
2062+
}
2063+
}
2064+
2065+
@Test
2066+
void testGeneralInsertValueOrNullWithNull() {
2067+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
2068+
AnimalDataMapper mapper = sqlSession.getMapper(AnimalDataMapper.class);
2069+
2070+
GeneralInsertStatementProvider insertStatement = insertInto(animalData)
2071+
.set(id).toValue(101)
2072+
.set(animalName).toValueOrNull((String) null)
2073+
.set(brainWeight).toConstant("2.2")
2074+
.set(bodyWeight).toValue(4.5)
2075+
.build()
2076+
.render(RenderingStrategies.MYBATIS3);
2077+
2078+
String expected = "insert into AnimalData (id, animal_name, brain_weight, body_weight) "
2079+
+ "values (#{parameters.p1,jdbcType=INTEGER}, null, 2.2, "
2080+
+ "#{parameters.p2,jdbcType=DOUBLE})";
2081+
2082+
assertThat(insertStatement.getInsertStatement()).isEqualTo(expected);
2083+
assertThat(insertStatement.getParameters()).hasSize(2);
2084+
assertThat(insertStatement.getParameters()).containsEntry("p1", 101);
2085+
assertThat(insertStatement.getParameters()).containsEntry("p2", 4.5);
2086+
2087+
int rows = mapper.generalInsert(insertStatement);
2088+
assertThat(rows).isEqualTo(1);
2089+
}
2090+
}
2091+
19972092
@Test
19982093
void testUpdateWithSelect() {
19992094
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {

0 commit comments

Comments
 (0)