Skip to content

Commit a65451d

Browse files
committed
Implement better method for handling multi-row inserts with generated keys
1 parent bd253cb commit a65451d

File tree

6 files changed

+75
-34
lines changed

6 files changed

+75
-34
lines changed

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

Lines changed: 14 additions & 3 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.
@@ -51,16 +51,27 @@ public String insertMultiple(MultiRowInsertStatementProvider<?> insertStatement)
5151
return insertStatement.getInsertStatement();
5252
}
5353

54+
/**
55+
* This adapter method is intended for use with MyBatis' @InsertProvider annotation when there are generated
56+
* values expected from executing the insert statement.
57+
*
58+
* @param parameterMap The parameter map is automatically created by MyBatis when there are multiple
59+
* parameters in the insert method.
60+
* @return the SQL statement contained in the parameter map. This is assumed to be the one
61+
* and only map entry of type String.
62+
*/
5463
public String insertMultipleWithGeneratedKeys(Map<String, Object> parameterMap) {
5564
List<String> entries = parameterMap.entrySet().stream()
5665
.filter(e -> e.getKey().startsWith("param")) //$NON-NLS-1$
57-
.filter(e -> e.getValue() instanceof String)
66+
.filter(e -> String.class.isAssignableFrom(e.getValue().getClass()))
5867
.map(e -> (String) e.getValue())
5968
.collect(Collectors.toList());
69+
6070
if (entries.size() == 1) {
6171
return entries.get(0);
6272
} else {
63-
throw new RuntimeException("The parameters for insertMultipleWithGeneratedKeys must contain exactly one parameter of type String");
73+
throw new IllegalArgumentException("The parameters for insertMultipleWithGeneratedKeys" + //$NON-NLS-1$
74+
" must contain exactly one parameter of type String"); //$NON-NLS-1$
6475
}
6576
}
6677

src/main/java/org/mybatis/dynamic/sql/util/mybatis3/MyBatis3Utils.java

Lines changed: 8 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.
@@ -18,6 +18,7 @@
1818
import java.util.Collection;
1919
import java.util.List;
2020
import java.util.function.Function;
21+
import java.util.function.ToIntBiFunction;
2122
import java.util.function.ToIntFunction;
2223
import java.util.function.ToLongFunction;
2324
import java.util.function.UnaryOperator;
@@ -138,6 +139,12 @@ public static <R> int insertMultiple(ToIntFunction<MultiRowInsertStatementProvid
138139
return mapper.applyAsInt(insertMultiple(records, table, completer));
139140
}
140141

142+
public static <R> int insertMultipleWithGeneratedKeys(ToIntBiFunction<String, List<R>> mapper,
143+
Collection<R> records, SqlTable table, UnaryOperator<MultiRowInsertDSL<R>> completer) {
144+
MultiRowInsertStatementProvider<R> provider = insertMultiple(records, table, completer);
145+
return mapper.applyAsInt(provider.getInsertStatement(), provider.getRecords());
146+
}
147+
141148
public static SelectStatementProvider select(BasicColumn[] selectList, SqlTable table,
142149
SelectDSLCompleter completer) {
143150
return select(SqlBuilder.select(selectList).from(table), completer);

src/main/kotlin/org/mybatis/dynamic/sql/util/kotlin/mybatis3/MapperSupportFunctions.kt

Lines changed: 2 additions & 2 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.
@@ -73,7 +73,7 @@ fun <T> insertMultipleWithGeneratedKeys(
7373
table: SqlTable,
7474
completer: MultiRowInsertCompleter<T>
7575
): Int =
76-
with(insertMultiple(records).into(table, completer)) {
76+
with(SqlBuilder.insertMultiple(records).into(table, completer)) {
7777
mapper(insertStatement, this.records)
7878
}
7979

src/test/java/examples/generated/always/mybatis/GeneratedAlwaysMapper.java

Lines changed: 15 additions & 27 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.
@@ -23,7 +23,6 @@
2323
import java.util.List;
2424
import java.util.Optional;
2525

26-
import org.apache.ibatis.annotations.Insert;
2726
import org.apache.ibatis.annotations.InsertProvider;
2827
import org.apache.ibatis.annotations.Options;
2928
import org.apache.ibatis.annotations.Param;
@@ -33,7 +32,6 @@
3332
import org.apache.ibatis.annotations.SelectProvider;
3433
import org.mybatis.dynamic.sql.BasicColumn;
3534
import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider;
36-
import org.mybatis.dynamic.sql.insert.render.MultiRowInsertStatementProvider;
3735
import org.mybatis.dynamic.sql.select.SelectDSLCompleter;
3836
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
3937
import org.mybatis.dynamic.sql.update.UpdateDSL;
@@ -42,11 +40,10 @@
4240
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;
4341

4442
import examples.generated.always.GeneratedAlwaysRecord;
45-
import org.mybatis.dynamic.sql.util.mybatis3.CommonInsertMapper;
4643
import org.mybatis.dynamic.sql.util.mybatis3.CommonUpdateMapper;
4744
import org.mybatis.dynamic.sql.util.mybatis3.MyBatis3Utils;
4845

49-
public interface GeneratedAlwaysMapper extends CommonInsertMapper<GeneratedAlwaysRecord>, CommonUpdateMapper {
46+
public interface GeneratedAlwaysMapper extends CommonUpdateMapper {
5047
@SelectProvider(type=SqlProviderAdapter.class, method="select")
5148
@Results(id="gaResults", value={
5249
@Result(property="id", column="id", id=true),
@@ -60,23 +57,14 @@ public interface GeneratedAlwaysMapper extends CommonInsertMapper<GeneratedAlway
6057
@ResultMap("gaResults")
6158
Optional<GeneratedAlwaysRecord> selectOne(SelectStatementProvider selectStatement);
6259

63-
@Override
6460
@InsertProvider(type=SqlProviderAdapter.class, method="insert")
6561
@Options(useGeneratedKeys=true, keyProperty="record.fullName")
6662
int insert(InsertStatementProvider<GeneratedAlwaysRecord> insertStatement);
6763

68-
// This is kludgy. Currently MyBatis does not support nested lists in parameter objects
69-
// when returning generated keys.
70-
// So we need to do this silliness and decompose the multi row insert into its component parts
71-
// for the actual MyBatis call
72-
@Insert("${insertStatement}")
64+
@InsertProvider(type=SqlProviderAdapter.class, method="insertMultipleWithGeneratedKeys")
7365
@Options(useGeneratedKeys=true, keyProperty="records.fullName")
7466
int insertMultiple(@Param("insertStatement") String statement, @Param("records") List<GeneratedAlwaysRecord> records);
7567

76-
default int insertMultiple(MultiRowInsertStatementProvider<GeneratedAlwaysRecord> multiInsert) {
77-
return insertMultiple(multiInsert.getInsertStatement(), multiInsert.getRecords());
78-
}
79-
8068
BasicColumn[] selectList =
8169
BasicColumn.columnList(id, firstName, lastName, fullName);
8270

@@ -100,6 +88,18 @@ default int insert(GeneratedAlwaysRecord record) {
10088
);
10189
}
10290

91+
default int insertMultiple(GeneratedAlwaysRecord...records) {
92+
return insertMultiple(Arrays.asList(records));
93+
}
94+
95+
default int insertMultiple(Collection<GeneratedAlwaysRecord> records) {
96+
return MyBatis3Utils.insertMultipleWithGeneratedKeys(this::insertMultiple, records, generatedAlways, c ->
97+
c.map(id).toProperty("id")
98+
.map(firstName).toProperty("firstName")
99+
.map(lastName).toProperty("lastName")
100+
);
101+
}
102+
103103
default int insertSelective(GeneratedAlwaysRecord record) {
104104
return MyBatis3Utils.insert(this::insert, record, generatedAlways, c ->
105105
c.map(id).toPropertyWhenPresent("id", record::getId)
@@ -133,16 +133,4 @@ static UpdateDSL<UpdateModel> updateSelectiveColumns(GeneratedAlwaysRecord recor
133133
.set(firstName).equalToWhenPresent(record::getFirstName)
134134
.set(lastName).equalToWhenPresent(record::getLastName);
135135
}
136-
137-
default int insertMultiple(GeneratedAlwaysRecord...records) {
138-
return insertMultiple(Arrays.asList(records));
139-
}
140-
141-
default int insertMultiple(Collection<GeneratedAlwaysRecord> records) {
142-
return MyBatis3Utils.insertMultiple(this::insertMultiple, records, generatedAlways, c ->
143-
c.map(id).toProperty("id")
144-
.map(firstName).toProperty("firstName")
145-
.map(lastName).toProperty("lastName")
146-
);
147-
}
148136
}

src/test/java/examples/generated/always/mybatis/GeneratedAlwaysMapperTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ void testMultiInsertWithArrayAndVariousMappings() {
227227

228228
assertThat(multiRowInsert.getInsertStatement()).isEqualTo(statement);
229229

230-
int rows = mapper.insertMultiple(multiRowInsert);
230+
int rows = mapper.insertMultiple(multiRowInsert.getInsertStatement(), multiRowInsert.getRecords());
231231

232232
assertAll(
233233
() -> assertThat(rows).isEqualTo(1),
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 static org.assertj.core.api.Assertions.assertThatExceptionOfType;
19+
20+
import org.junit.jupiter.api.Test;
21+
22+
import java.util.HashMap;
23+
import java.util.Map;
24+
25+
class SqlProviderAdapterTest {
26+
@Test
27+
void testThatInsertMultipleWithGeneratedKeysThrowsException() {
28+
Map<String, Object> parameters = new HashMap<>();
29+
SqlProviderAdapter adapter = new SqlProviderAdapter();
30+
31+
assertThatExceptionOfType(IllegalArgumentException.class)
32+
.isThrownBy(() -> adapter.insertMultipleWithGeneratedKeys(parameters))
33+
.withMessage("The parameters for insertMultipleWithGeneratedKeys must contain exactly one parameter of type String");
34+
}
35+
}

0 commit comments

Comments
 (0)