Skip to content

Commit f44dcef

Browse files
authored
Merge pull request #1249 from harawata/multi-row-insert-improvement-2
Remove parameter name restriction when assigning keys generated by multi-row insert.
2 parents 2aaac21 + fc4c6f3 commit f44dcef

File tree

6 files changed

+642
-53
lines changed

6 files changed

+642
-53
lines changed

src/main/java/org/apache/ibatis/executor/keygen/Jdbc3KeyGenerator.java

Lines changed: 90 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,18 @@
1919
import java.sql.ResultSetMetaData;
2020
import java.sql.SQLException;
2121
import java.sql.Statement;
22-
import java.util.ArrayList;
2322
import java.util.Arrays;
2423
import java.util.Collection;
25-
import java.util.List;
2624
import java.util.Map;
2725

26+
import org.apache.ibatis.binding.MapperMethod.ParamMap;
2827
import org.apache.ibatis.executor.Executor;
2928
import org.apache.ibatis.executor.ExecutorException;
3029
import org.apache.ibatis.mapping.MappedStatement;
30+
import org.apache.ibatis.reflection.ArrayUtil;
3131
import org.apache.ibatis.reflection.MetaObject;
3232
import org.apache.ibatis.session.Configuration;
33+
import org.apache.ibatis.session.defaults.DefaultSqlSession.StrictMap;
3334
import org.apache.ibatis.type.JdbcType;
3435
import org.apache.ibatis.type.TypeHandler;
3536
import org.apache.ibatis.type.TypeHandlerRegistry;
@@ -42,6 +43,7 @@ public class Jdbc3KeyGenerator implements KeyGenerator {
4243

4344
/**
4445
* A shared instance.
46+
*
4547
* @since 3.4.3
4648
*/
4749
public static final Jdbc3KeyGenerator INSTANCE = new Jdbc3KeyGenerator();
@@ -53,29 +55,24 @@ public void processBefore(Executor executor, MappedStatement ms, Statement stmt,
5355

5456
@Override
5557
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
56-
processBatch(ms, stmt, getParameters(parameter));
58+
processBatch(ms, stmt, parameter);
5759
}
5860

59-
public void processBatch(MappedStatement ms, Statement stmt, Collection<Object> parameters) {
61+
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
62+
final String[] keyProperties = ms.getKeyProperties();
63+
if (keyProperties == null || keyProperties.length == 0) {
64+
return;
65+
}
6066
ResultSet rs = null;
6167
try {
6268
rs = stmt.getGeneratedKeys();
6369
final Configuration configuration = ms.getConfiguration();
64-
final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
65-
final String[] keyProperties = ms.getKeyProperties();
66-
final ResultSetMetaData rsmd = rs.getMetaData();
67-
TypeHandler<?>[] typeHandlers = null;
68-
if (keyProperties != null && rsmd.getColumnCount() >= keyProperties.length) {
69-
for (Object parameter : parameters) {
70-
// there should be one row for each statement (also one for each parameter)
71-
if (!rs.next()) {
72-
break;
73-
}
74-
final MetaObject metaParam = configuration.newMetaObject(parameter);
75-
if (typeHandlers == null) {
76-
typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd);
77-
}
78-
populateKeys(rs, metaParam, keyProperties, typeHandlers);
70+
if (rs.getMetaData().getColumnCount() >= keyProperties.length) {
71+
Object soleParam = getSoleParameter(parameter);
72+
if (soleParam != null) {
73+
assignKeysToParam(configuration, rs, keyProperties, soleParam);
74+
} else {
75+
assignKeysToOneOfParams(configuration, rs, keyProperties, (Map<?, ?>) parameter);
7976
}
8077
}
8178
} catch (Exception e) {
@@ -91,25 +88,83 @@ public void processBatch(MappedStatement ms, Statement stmt, Collection<Object>
9188
}
9289
}
9390

94-
private Collection<Object> getParameters(Object parameter) {
95-
Collection<Object> parameters = null;
96-
if (parameter instanceof Collection) {
97-
parameters = (Collection) parameter;
98-
} else if (parameter instanceof Map) {
99-
Map parameterMap = (Map) parameter;
100-
if (parameterMap.containsKey("collection")) {
101-
parameters = (Collection) parameterMap.get("collection");
102-
} else if (parameterMap.containsKey("list")) {
103-
parameters = (List) parameterMap.get("list");
104-
} else if (parameterMap.containsKey("array")) {
105-
parameters = Arrays.asList((Object[]) parameterMap.get("array"));
91+
protected void assignKeysToOneOfParams(final Configuration configuration, ResultSet rs, final String[] keyProperties,
92+
Map<?, ?> paramMap) throws SQLException {
93+
// Assuming 'keyProperty' includes the parameter name. e.g. 'param.id'.
94+
int firstDot = keyProperties[0].indexOf('.');
95+
if (firstDot == -1) {
96+
throw new ExecutorException(
97+
"Could not determine which parameter to assign generated keys to. "
98+
+ "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
99+
+ "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
100+
+ paramMap.keySet());
101+
}
102+
String paramName = keyProperties[0].substring(0, firstDot);
103+
Object param;
104+
if (paramMap.containsKey(paramName)) {
105+
param = paramMap.get(paramName);
106+
} else {
107+
throw new ExecutorException("Could not find parameter '" + paramName + "'. "
108+
+ "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
109+
+ "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
110+
+ paramMap.keySet());
111+
}
112+
// Remove param name from 'keyProperty' string. e.g. 'param.id' -> 'id'
113+
String[] modifiedKeyProperties = new String[keyProperties.length];
114+
for (int i = 0; i < keyProperties.length; i++) {
115+
if (keyProperties[i].charAt(firstDot) == '.' && keyProperties[i].startsWith(paramName)) {
116+
modifiedKeyProperties[i] = keyProperties[i].substring(firstDot + 1);
117+
} else {
118+
throw new ExecutorException("Assigning generated keys to multiple parameters is not supported. "
119+
+ "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
120+
+ "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
121+
+ paramMap.keySet());
106122
}
107123
}
108-
if (parameters == null) {
109-
parameters = new ArrayList<>();
110-
parameters.add(parameter);
124+
assignKeysToParam(configuration, rs, modifiedKeyProperties, param);
125+
}
126+
127+
private void assignKeysToParam(final Configuration configuration, ResultSet rs, final String[] keyProperties,
128+
Object param)
129+
throws SQLException {
130+
final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
131+
final ResultSetMetaData rsmd = rs.getMetaData();
132+
// Wrap the parameter in Collection to normalize the logic.
133+
Collection<?> paramAsCollection = null;
134+
if (param instanceof Object[]) {
135+
paramAsCollection = Arrays.asList((Object[]) param);
136+
} else if (!(param instanceof Collection)) {
137+
paramAsCollection = Arrays.asList(param);
138+
} else {
139+
paramAsCollection = (Collection<?>) param;
140+
}
141+
TypeHandler<?>[] typeHandlers = null;
142+
for (Object obj : paramAsCollection) {
143+
if (!rs.next()) {
144+
break;
145+
}
146+
MetaObject metaParam = configuration.newMetaObject(obj);
147+
if (typeHandlers == null) {
148+
typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd);
149+
}
150+
populateKeys(rs, metaParam, keyProperties, typeHandlers);
151+
}
152+
}
153+
154+
private Object getSoleParameter(Object parameter) {
155+
if (!(parameter instanceof ParamMap || parameter instanceof StrictMap)) {
156+
return parameter;
157+
}
158+
Object soleParam = null;
159+
for (Object paramValue : ((Map<?, ?>) parameter).values()) {
160+
if (soleParam == null) {
161+
soleParam = paramValue;
162+
} else if (soleParam != paramValue) {
163+
soleParam = null;
164+
break;
165+
}
111166
}
112-
return parameters;
167+
return soleParam;
113168
}
114169

115170
private TypeHandler<?>[] getTypeHandlers(TypeHandlerRegistry typeHandlerRegistry, MetaObject metaParam, String[] keyProperties, ResultSetMetaData rsmd) throws SQLException {

src/test/java/org/apache/ibatis/submitted/keygen/CountryMapper.java

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2009-2016 the original author or authors.
2+
* Copyright 2009-2018 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.
@@ -16,10 +16,75 @@
1616
package org.apache.ibatis.submitted.keygen;
1717

1818
import java.util.List;
19+
import java.util.Map;
20+
import java.util.Set;
21+
22+
import org.apache.ibatis.annotations.Insert;
23+
import org.apache.ibatis.annotations.Options;
24+
import org.apache.ibatis.annotations.Param;
1925

2026
public interface CountryMapper {
2127

28+
@Options(useGeneratedKeys = true, keyProperty = "id")
29+
@Insert({ "insert into country (countryname,countrycode) values (#{countryname},#{countrycode})" })
30+
int insertBean(Country country);
31+
32+
@Options(useGeneratedKeys = true, keyProperty = "id")
33+
@Insert({ "insert into country (countryname,countrycode) values (#{country.countryname},#{country.countrycode})" })
34+
int insertNamedBean(@Param("country") Country country);
35+
2236
int insertList(List<Country> countries);
37+
38+
int insertNamedList(@Param("countries") List<Country> countries);
39+
40+
int insertSet(Set<Country> countries);
41+
42+
int insertNamedSet(@Param("countries") Set<Country> countries);
43+
44+
int insertArray(Country[] countries);
45+
46+
int insertNamedArray(@Param("countries") Country[] countries);
47+
48+
@Options(useGeneratedKeys = true, keyProperty = "country.id")
49+
@Insert({ "insert into country (countryname,countrycode) values (#{country.countryname},#{country.countrycode})" })
50+
int insertMultiParams(@Param("country") Country country, @Param("someId") Integer someId);
51+
52+
@Options(useGeneratedKeys = true, keyProperty = "id")
53+
@Insert({ "insert into country (countryname,countrycode) values (#{country.countryname},#{country.countrycode})" })
54+
int insertMultiParams_keyPropertyWithoutParamName(@Param("country") Country country, @Param("someId") Integer someId);
55+
56+
@Options(useGeneratedKeys = true, keyProperty = "bogus.id")
57+
@Insert({ "insert into country (countryname,countrycode) values (#{country.countryname},#{country.countrycode})" })
58+
int insertMultiParams_keyPropertyWithWrongParamName(@Param("country") Country country,
59+
@Param("someId") Integer someId);
60+
61+
int insertListAndSomeId(@Param("list") List<Country> countries, @Param("someId") Integer someId);
62+
63+
int insertSetAndSomeId(@Param("collection") Set<Country> countries, @Param("someId") Integer someId);
64+
65+
int insertArrayAndSomeId(@Param("array") Country[] countries, @Param("someId") Integer someId);
66+
67+
int insertList_MultiParams(@Param("countries") List<Country> countries, @Param("someId") Integer someId);
68+
69+
int insertSet_MultiParams(@Param("countries") Set<Country> countries, @Param("someId") Integer someId);
70+
71+
int insertArray_MultiParams(@Param("countries") Country[] countries, @Param("someId") Integer someId);
72+
2373
int insertUndefineKeyProperty(Country country);
2474

75+
@Options(useGeneratedKeys = true, keyProperty = "id,code")
76+
@Insert({ "insert into planet (name) values (#{name})" })
77+
int insertPlanet(Planet planet);
78+
79+
int insertPlanets(List<Planet> planets);
80+
81+
@Options(useGeneratedKeys = true, keyProperty = "planet.id,planet.code")
82+
@Insert({ "insert into planet (name) values (#{planet.name})" })
83+
int insertPlanet_MultiParams(@Param("planet") Planet planet, @Param("someId") Integer someId);
84+
85+
int insertPlanets_MultiParams(@Param("planets") List<Planet> planets, @Param("someId") Integer someId);
86+
87+
@Options(useGeneratedKeys = true, keyProperty = "planet.id,map.code")
88+
@Insert({ "insert into planet (name) values (#{planet.name})" })
89+
int insertAssignKeysToTwoParams(@Param("planet") Planet planet, @Param("map") Map<String, Object> map);
2590
}
Lines changed: 113 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!--
33
4-
Copyright 2009-2016 the original author or authors.
4+
Copyright 2009-2018 the original author or authors.
55
66
Licensed under the Apache License, Version 2.0 (the "License");
77
you may not use this file except in compliance with the License.
@@ -19,15 +19,117 @@
1919
<!DOCTYPE mapper
2020
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
2121
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
22-
<mapper namespace="org.apache.ibatis.submitted.keygen.CountryMapper" >
23-
<insert id="insertList" parameterType="org.apache.ibatis.submitted.keygen.Country" useGeneratedKeys="true" keyProperty="id">
24-
insert into country (countryname,countrycode)
25-
values
26-
<foreach collection="list" separator="," item="country">
27-
(#{country.countryname},#{country.countrycode})
28-
</foreach>
29-
</insert>
30-
<insert id="insertUndefineKeyProperty" parameterType="org.apache.ibatis.submitted.keygen.Country" useGeneratedKeys="true" keyProperty="country_id">
31-
insert into country (countryname,countrycode) values (#{countryname},#{countrycode})
22+
<mapper namespace="org.apache.ibatis.submitted.keygen.CountryMapper">
23+
<insert id="insertList" useGeneratedKeys="true" keyProperty="id">
24+
insert into country (countryname,countrycode)
25+
values
26+
<foreach collection="list" separator="," item="country">
27+
(#{country.countryname},#{country.countrycode})
28+
</foreach>
29+
</insert>
30+
<insert id="insertNamedList" useGeneratedKeys="true"
31+
keyProperty="id">
32+
insert into country (countryname,countrycode)
33+
values
34+
<foreach collection="countries" separator="," item="country">
35+
(#{country.countryname},#{country.countrycode})
36+
</foreach>
37+
</insert>
38+
<insert id="insertSet" useGeneratedKeys="true" keyProperty="id">
39+
insert into country (countryname,countrycode)
40+
values
41+
<foreach collection="collection" separator="," item="country">
42+
(#{country.countryname},#{country.countrycode})
43+
</foreach>
44+
</insert>
45+
<insert id="insertNamedSet" useGeneratedKeys="true" keyProperty="id">
46+
insert into country (countryname,countrycode)
47+
values
48+
<foreach collection="countries" separator="," item="country">
49+
(#{country.countryname},#{country.countrycode})
50+
</foreach>
51+
</insert>
52+
<insert id="insertArray" useGeneratedKeys="true" keyProperty="id">
53+
insert into country (countryname,countrycode)
54+
values
55+
<foreach collection="array" separator="," item="country">
56+
(#{country.countryname},#{country.countrycode})
57+
</foreach>
58+
</insert>
59+
<insert id="insertNamedArray" useGeneratedKeys="true" keyProperty="id">
60+
insert into country (countryname,countrycode)
61+
values
62+
<foreach collection="countries" separator="," item="country">
63+
(#{country.countryname},#{country.countrycode})
64+
</foreach>
65+
</insert>
66+
<insert id="insertListAndSomeId" useGeneratedKeys="true"
67+
keyProperty="id">
68+
insert into country (countryname,countrycode)
69+
values
70+
<foreach collection="list" separator="," item="country">
71+
(#{country.countryname},#{country.countrycode})
72+
</foreach>
73+
</insert>
74+
<insert id="insertSetAndSomeId" useGeneratedKeys="true"
75+
keyProperty="id">
76+
insert into country (countryname,countrycode)
77+
values
78+
<foreach collection="collection" separator="," item="country">
79+
(#{country.countryname},#{country.countrycode})
80+
</foreach>
81+
</insert>
82+
<insert id="insertArrayAndSomeId" useGeneratedKeys="true"
83+
keyProperty="id">
84+
insert into country (countryname,countrycode)
85+
values
86+
<foreach collection="array" separator="," item="country">
87+
(#{country.countryname},#{country.countrycode})
88+
</foreach>
89+
</insert>
90+
<insert id="insertList_MultiParams" useGeneratedKeys="true"
91+
keyProperty="countries.id">
92+
insert into country (countryname,countrycode)
93+
values
94+
<foreach collection="countries" separator="," item="country">
95+
(#{country.countryname},#{country.countrycode})
96+
</foreach>
97+
</insert>
98+
<insert id="insertSet_MultiParams" useGeneratedKeys="true"
99+
keyProperty="countries.id">
100+
insert into country (countryname,countrycode)
101+
values
102+
<foreach collection="countries" separator="," item="country">
103+
(#{country.countryname},#{country.countrycode})
104+
</foreach>
105+
</insert>
106+
<insert id="insertArray_MultiParams" useGeneratedKeys="true"
107+
keyProperty="countries.id">
108+
insert into country (countryname,countrycode)
109+
values
110+
<foreach collection="countries" separator="," item="country">
111+
(#{country.countryname},#{country.countrycode})
112+
</foreach>
113+
</insert>
114+
<insert id="insertUndefineKeyProperty" useGeneratedKeys="true"
115+
keyProperty="country_id">
116+
insert into country (countryname,countrycode) values
117+
(#{countryname},#{countrycode})
118+
</insert>
119+
<insert id="insertPlanets" useGeneratedKeys="true" keyProperty="id,code">
120+
insert into planet (name) values
121+
<foreach collection="list" separator="," item="planet">
122+
(#{planet.name})
123+
</foreach>
124+
</insert>
125+
<insert id="insertPlanets_MultiParams" useGeneratedKeys="true" keyProperty="planets.id,planets.code">
126+
insert into planet (name) values
127+
<foreach collection="planets" separator="," item="planet">
128+
(#{planet.name})
129+
</foreach>
130+
</insert>
131+
<insert id="insertPlanetAndCountry" useGeneratedKeys="true" keyProperty="planet.id,planet.code,country.id">
132+
insert into planet (name) values (#{planet.name});
133+
insert into country (countryname,countrycode) values (#{country.countryname},#{country.countrycode});
32134
</insert>
33135
</mapper>

0 commit comments

Comments
 (0)