Skip to content

Commit 1845f0b

Browse files
committed
Remove the parameter name restriction when assigning keys generated by multi-row insert.
1 parent 115d565 commit 1845f0b

File tree

6 files changed

+714
-50
lines changed

6 files changed

+714
-50
lines changed

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

Lines changed: 110 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,20 @@
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;
2524
import java.util.List;
2625
import java.util.Map;
2726

2827
import org.apache.ibatis.binding.BindingException;
28+
import org.apache.ibatis.binding.MapperMethod.ParamMap;
2929
import org.apache.ibatis.executor.Executor;
3030
import org.apache.ibatis.executor.ExecutorException;
3131
import org.apache.ibatis.mapping.MappedStatement;
32+
import org.apache.ibatis.reflection.ArrayUtil;
3233
import org.apache.ibatis.reflection.MetaObject;
3334
import org.apache.ibatis.session.Configuration;
35+
import org.apache.ibatis.session.defaults.DefaultSqlSession.StrictMap;
3436
import org.apache.ibatis.type.JdbcType;
3537
import org.apache.ibatis.type.TypeHandler;
3638
import org.apache.ibatis.type.TypeHandlerRegistry;
@@ -43,6 +45,7 @@ public class Jdbc3KeyGenerator implements KeyGenerator {
4345

4446
/**
4547
* A shared instance.
48+
*
4649
* @since 3.4.3
4750
*/
4851
public static final Jdbc3KeyGenerator INSTANCE = new Jdbc3KeyGenerator();
@@ -54,29 +57,24 @@ public void processBefore(Executor executor, MappedStatement ms, Statement stmt,
5457

5558
@Override
5659
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
57-
processBatch(ms, stmt, getParameters(parameter));
60+
processBatch(ms, stmt, parameter);
5861
}
5962

60-
public void processBatch(MappedStatement ms, Statement stmt, Collection<Object> parameters) {
63+
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
64+
final String[] keyProperties = ms.getKeyProperties();
65+
if (keyProperties == null || keyProperties.length == 0) {
66+
return;
67+
}
6168
ResultSet rs = null;
6269
try {
6370
rs = stmt.getGeneratedKeys();
6471
final Configuration configuration = ms.getConfiguration();
65-
final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
66-
final String[] keyProperties = ms.getKeyProperties();
67-
final ResultSetMetaData rsmd = rs.getMetaData();
68-
TypeHandler<?>[] typeHandlers = null;
69-
if (keyProperties != null && rsmd.getColumnCount() >= keyProperties.length) {
70-
for (Object parameter : parameters) {
71-
// there should be one row for each statement (also one for each parameter)
72-
if (!rs.next()) {
73-
break;
74-
}
75-
final MetaObject metaParam = configuration.newMetaObject(parameter);
76-
if (typeHandlers == null) {
77-
typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd);
78-
}
79-
populateKeys(rs, metaParam, keyProperties, typeHandlers);
72+
if (rs.getMetaData().getColumnCount() >= keyProperties.length) {
73+
Object soleParam = getSoleParameter(parameter);
74+
if (soleParam != null) {
75+
assignKeysToParam(configuration, rs, keyProperties, soleParam);
76+
} else {
77+
assignKeysToOneOfParams(configuration, rs, keyProperties, (Map<?, ?>) parameter);
8078
}
8179
}
8280
} catch (Exception e) {
@@ -92,25 +90,103 @@ public void processBatch(MappedStatement ms, Statement stmt, Collection<Object>
9290
}
9391
}
9492

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

116192
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-2017 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
}

0 commit comments

Comments
 (0)