Skip to content

Commit cfedb8c

Browse files
committed
Fix Multi-Row Inserts in Spring
1 parent 6c2b614 commit cfedb8c

11 files changed

+217
-76
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Copyright 2016-2020 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.insert.render;
17+
18+
import java.util.function.Function;
19+
20+
import org.mybatis.dynamic.sql.SqlColumn;
21+
import org.mybatis.dynamic.sql.render.RenderingStrategy;
22+
import org.mybatis.dynamic.sql.util.ConstantMapping;
23+
import org.mybatis.dynamic.sql.util.MultiRowInsertMappingVisitor;
24+
import org.mybatis.dynamic.sql.util.NullMapping;
25+
import org.mybatis.dynamic.sql.util.PropertyMapping;
26+
import org.mybatis.dynamic.sql.util.StringConstantMapping;
27+
28+
public abstract class AbstractMultiRowValuePhraseVisitor extends MultiRowInsertMappingVisitor<FieldAndValue> {
29+
30+
protected RenderingStrategy renderingStrategy;
31+
protected String prefix;
32+
33+
public AbstractMultiRowValuePhraseVisitor(RenderingStrategy renderingStrategy, String prefix) {
34+
this.renderingStrategy = renderingStrategy;
35+
this.prefix = prefix;
36+
}
37+
38+
@Override
39+
public <T> FieldAndValue visit(NullMapping<T> mapping) {
40+
return FieldAndValue.withFieldName(mapping.mapColumn(SqlColumn::name))
41+
.withValuePhrase("null") //$NON-NLS-1$
42+
.build();
43+
}
44+
45+
@Override
46+
public <T> FieldAndValue visit(ConstantMapping<T> mapping) {
47+
return FieldAndValue.withFieldName(mapping.mapColumn(SqlColumn::name))
48+
.withValuePhrase(mapping.constant())
49+
.build();
50+
}
51+
52+
@Override
53+
public <T> FieldAndValue visit(StringConstantMapping<T> mapping) {
54+
return FieldAndValue.withFieldName(mapping.mapColumn(SqlColumn::name))
55+
.withValuePhrase("'" + mapping.constant() + "'") //$NON-NLS-1$ //$NON-NLS-2$
56+
.build();
57+
}
58+
59+
@Override
60+
public <T> FieldAndValue visit(PropertyMapping<T> mapping) {
61+
return FieldAndValue.withFieldName(mapping.mapColumn(SqlColumn::name))
62+
.withValuePhrase(mapping.mapColumn(toJdbcPlaceholder(mapping.property())))
63+
.build();
64+
}
65+
66+
abstract Function<SqlColumn<?>, String> toJdbcPlaceholder(String parameterName);
67+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ private BatchInsertRenderer(Builder<T> builder) {
3535
}
3636

3737
public BatchInsert<T> render() {
38-
MultiRowValuePhraseVisitor visitor = new MultiRowValuePhraseVisitor(renderingStrategy, "record"); //$NON-NLS-1$)
38+
BatchValuePhraseVisitor visitor = new BatchValuePhraseVisitor(renderingStrategy, "record"); //$NON-NLS-1$)
3939
List<FieldAndValue> fieldsAndValues = model
4040
.mapColumnMappings(MultiRowRenderingUtilities.toFieldAndValue(visitor))
4141
.collect(Collectors.toList());
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Copyright 2016-2020 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.insert.render;
17+
18+
import java.util.function.Function;
19+
20+
import org.mybatis.dynamic.sql.SqlColumn;
21+
import org.mybatis.dynamic.sql.render.RenderingStrategy;
22+
23+
public class BatchValuePhraseVisitor extends AbstractMultiRowValuePhraseVisitor {
24+
25+
public BatchValuePhraseVisitor(RenderingStrategy renderingStrategy, String prefix) {
26+
super(renderingStrategy, prefix);
27+
}
28+
29+
@Override
30+
Function<SqlColumn<?>, String> toJdbcPlaceholder(String parameterName) {
31+
return column -> column.renderingStrategy().orElse(renderingStrategy)
32+
.getFormattedJdbcPlaceholder(column, prefix, parameterName);
33+
}
34+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ public class MultiRowRenderingUtilities {
2626
private MultiRowRenderingUtilities() {}
2727

2828
public static Function<AbstractColumnMapping<?>, FieldAndValue> toFieldAndValue(
29-
MultiRowValuePhraseVisitor visitor) {
29+
AbstractMultiRowValuePhraseVisitor visitor) {
3030
return insertMapping -> MultiRowRenderingUtilities.toFieldAndValue(visitor, insertMapping);
3131
}
3232

33-
public static FieldAndValue toFieldAndValue(MultiRowValuePhraseVisitor visitor,
33+
public static FieldAndValue toFieldAndValue(AbstractMultiRowValuePhraseVisitor visitor,
3434
AbstractColumnMapping<?> insertMapping) {
3535
return insertMapping.accept(visitor);
3636
}

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

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,52 +19,16 @@
1919

2020
import org.mybatis.dynamic.sql.SqlColumn;
2121
import org.mybatis.dynamic.sql.render.RenderingStrategy;
22-
import org.mybatis.dynamic.sql.util.ConstantMapping;
23-
import org.mybatis.dynamic.sql.util.MultiRowInsertMappingVisitor;
24-
import org.mybatis.dynamic.sql.util.NullMapping;
25-
import org.mybatis.dynamic.sql.util.PropertyMapping;
26-
import org.mybatis.dynamic.sql.util.StringConstantMapping;
2722

28-
public class MultiRowValuePhraseVisitor extends MultiRowInsertMappingVisitor<FieldAndValue> {
29-
30-
private RenderingStrategy renderingStrategy;
31-
private String prefix;
23+
public class MultiRowValuePhraseVisitor extends AbstractMultiRowValuePhraseVisitor {
3224

3325
public MultiRowValuePhraseVisitor(RenderingStrategy renderingStrategy, String prefix) {
34-
this.renderingStrategy = renderingStrategy;
35-
this.prefix = prefix;
36-
}
37-
38-
@Override
39-
public <T> FieldAndValue visit(NullMapping<T> mapping) {
40-
return FieldAndValue.withFieldName(mapping.mapColumn(SqlColumn::name))
41-
.withValuePhrase("null") //$NON-NLS-1$
42-
.build();
26+
super(renderingStrategy, prefix);
4327
}
4428

4529
@Override
46-
public <T> FieldAndValue visit(ConstantMapping<T> mapping) {
47-
return FieldAndValue.withFieldName(mapping.mapColumn(SqlColumn::name))
48-
.withValuePhrase(mapping.constant())
49-
.build();
50-
}
51-
52-
@Override
53-
public <T> FieldAndValue visit(StringConstantMapping<T> mapping) {
54-
return FieldAndValue.withFieldName(mapping.mapColumn(SqlColumn::name))
55-
.withValuePhrase("'" + mapping.constant() + "'") //$NON-NLS-1$ //$NON-NLS-2$
56-
.build();
57-
}
58-
59-
@Override
60-
public <T> FieldAndValue visit(PropertyMapping<T> mapping) {
61-
return FieldAndValue.withFieldName(mapping.mapColumn(SqlColumn::name))
62-
.withValuePhrase(mapping.mapColumn(toJdbcPlaceholder(mapping.property())))
63-
.build();
64-
}
65-
66-
private Function<SqlColumn<?>, String> toJdbcPlaceholder(String parameterName) {
30+
Function<SqlColumn<?>, String> toJdbcPlaceholder(String parameterName) {
6731
return column -> column.renderingStrategy().orElse(renderingStrategy)
68-
.getFormattedJdbcPlaceholder(column, prefix, parameterName);
32+
.getMultiRowFormattedJdbcPlaceholder(column, prefix, parameterName);
6933
}
7034
}

src/main/java/org/mybatis/dynamic/sql/render/RenderingStrategy.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2016-2019 the original author or authors.
2+
* Copyright 2016-2020 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.
@@ -47,4 +47,8 @@ public static String formatParameterMapKey(AtomicInteger sequence) {
4747
public abstract String getFormattedJdbcPlaceholder(BindableColumn<?> column, String prefix, String parameterName);
4848

4949
public abstract String getFormattedJdbcPlaceholder(String prefix, String parameterName);
50+
51+
public String getMultiRowFormattedJdbcPlaceholder(BindableColumn<?> column, String prefix, String parameterName) {
52+
return getFormattedJdbcPlaceholder(column, prefix, parameterName);
53+
}
5054
}

src/main/java/org/mybatis/dynamic/sql/render/SpringNamedParameterRenderingStrategy.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2016-2019 the original author or authors.
2+
* Copyright 2016-2020 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.
@@ -28,4 +28,9 @@ public String getFormattedJdbcPlaceholder(BindableColumn<?> column, String prefi
2828
public String getFormattedJdbcPlaceholder(String prefix, String parameterName) {
2929
return ":" + parameterName; //$NON-NLS-1$
3030
}
31+
32+
@Override
33+
public String getMultiRowFormattedJdbcPlaceholder(BindableColumn<?> column, String prefix, String parameterName) {
34+
return ":" + prefix + "." + parameterName; //$NON-NLS-1$ //$NON-NLS-2$
35+
}
3136
}

src/main/java/org/mybatis/dynamic/sql/util/spring/NamedParameterJdbcTemplateExtensions.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider;
2424
import org.mybatis.dynamic.sql.insert.GeneralInsertModel;
2525
import org.mybatis.dynamic.sql.insert.InsertModel;
26+
import org.mybatis.dynamic.sql.insert.MultiRowInsertModel;
2627
import org.mybatis.dynamic.sql.insert.render.GeneralInsertStatementProvider;
2728
import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider;
29+
import org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider;
2830
import org.mybatis.dynamic.sql.select.SelectModel;
2931
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
3032
import org.mybatis.dynamic.sql.update.UpdateModel;
@@ -95,6 +97,24 @@ public <T> int insert(InsertStatementProvider<T> insertStatement, KeyHolder keyH
9597
new BeanPropertySqlParameterSource(insertStatement.getRecord()), keyHolder);
9698
}
9799

100+
public <T> int insertMultiple(Buildable<MultiRowInsertModel<T>> insertStatement) {
101+
return insertMultiple(SpringUtils.buildMultiRowInsert(insertStatement));
102+
}
103+
104+
public <T> int insertMultiple(MultiRowInsertStatementProvider<T> insertStatement) {
105+
return template.update(insertStatement.getInsertStatement(),
106+
new BeanPropertySqlParameterSource(insertStatement));
107+
}
108+
109+
public <T> int insertMultiple(Buildable<MultiRowInsertModel<T>> insertStatement, KeyHolder keyHolder) {
110+
return insertMultiple(SpringUtils.buildMultiRowInsert(insertStatement), keyHolder);
111+
}
112+
113+
public <T> int insertMultiple(MultiRowInsertStatementProvider<T> insertStatement, KeyHolder keyHolder) {
114+
return template.update(insertStatement.getInsertStatement(),
115+
new BeanPropertySqlParameterSource(insertStatement), keyHolder);
116+
}
117+
98118
public <T> List<T> selectList(Buildable<SelectModel> selectStatement, RowMapper<T> rowMapper) {
99119
return selectList(SpringUtils.buildSelect(selectStatement), rowMapper);
100120
}

src/main/java/org/mybatis/dynamic/sql/util/spring/SpringUtils.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider;
2020
import org.mybatis.dynamic.sql.insert.GeneralInsertModel;
2121
import org.mybatis.dynamic.sql.insert.InsertModel;
22+
import org.mybatis.dynamic.sql.insert.MultiRowInsertModel;
2223
import org.mybatis.dynamic.sql.insert.render.GeneralInsertStatementProvider;
2324
import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider;
25+
import org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider;
2426
import org.mybatis.dynamic.sql.render.RenderingStrategies;
2527
import org.mybatis.dynamic.sql.select.SelectModel;
2628
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
@@ -43,6 +45,10 @@ public static <T> InsertStatementProvider<T> buildInsert(Buildable<InsertModel<T
4345
return insertStatement.build().render(RenderingStrategies.SPRING_NAMED_PARAMETER);
4446
}
4547

48+
public static <T> MultiRowInsertStatementProvider<T> buildMultiRowInsert(Buildable<MultiRowInsertModel<T>> insertStatement) {
49+
return insertStatement.build().render(RenderingStrategies.SPRING_NAMED_PARAMETER);
50+
}
51+
4652
public static SelectStatementProvider buildSelect(Buildable<SelectModel> selectStatement) {
4753
return selectStatement.build().render(RenderingStrategies.SPRING_NAMED_PARAMETER);
4854
}

src/test/java/examples/generated/always/spring/SpringTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static examples.generated.always.spring.GeneratedAlwaysDynamicSqlSupport.*;
1919
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.assertj.core.api.Assertions.entry;
2021
import static org.mybatis.dynamic.sql.SqlBuilder.*;
2122

2223
import java.util.ArrayList;
@@ -27,6 +28,7 @@
2728
import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider;
2829
import org.mybatis.dynamic.sql.insert.GeneralInsertModel;
2930
import org.mybatis.dynamic.sql.insert.InsertModel;
31+
import org.mybatis.dynamic.sql.insert.MultiRowInsertModel;
3032
import org.mybatis.dynamic.sql.insert.render.BatchInsert;
3133
import org.mybatis.dynamic.sql.insert.render.GeneralInsertStatementProvider;
3234
import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider;
@@ -255,6 +257,36 @@ record = new GeneratedAlwaysRecord();
255257
assertThat(updateCounts[1]).isEqualTo(1);
256258
}
257259

260+
@Test
261+
void testMultiRowInsert() {
262+
NamedParameterJdbcTemplateExtensions extensions = new NamedParameterJdbcTemplateExtensions(template);
263+
KeyHolder keyHolder = new GeneratedKeyHolder();
264+
265+
List<GeneratedAlwaysRecord> records = new ArrayList<>();
266+
GeneratedAlwaysRecord record = new GeneratedAlwaysRecord();
267+
record.setId(100);
268+
record.setFirstName("Bob");
269+
record.setLastName("Jones");
270+
records.add(record);
271+
272+
record = new GeneratedAlwaysRecord();
273+
record.setId(101);
274+
record.setFirstName("Jim");
275+
record.setLastName("Smith");
276+
records.add(record);
277+
278+
Buildable<MultiRowInsertModel<GeneratedAlwaysRecord>> insertStatement = insertMultiple(records).into(generatedAlways)
279+
.map(id).toProperty("id")
280+
.map(firstName).toProperty("firstName")
281+
.map(lastName).toProperty("lastName");
282+
283+
int rows = extensions.insertMultiple(insertStatement, keyHolder);
284+
285+
assertThat(rows).isEqualTo(2);
286+
assertThat(keyHolder.getKeyList().get(0)).contains(entry("FULL_NAME", "Bob Jones"));
287+
assertThat(keyHolder.getKeyList().get(1)).contains(entry("FULL_NAME", "Jim Smith"));
288+
}
289+
258290
@Test
259291
void testUpdate() {
260292
UpdateStatementProvider updateStatement = update(generatedAlways)

0 commit comments

Comments
 (0)