1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.ibatis.executor.keygen;
17
18 import java.sql.ResultSet;
19 import java.sql.ResultSetMetaData;
20 import java.sql.SQLException;
21 import java.sql.Statement;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.Set;
31
32 import org.apache.ibatis.binding.MapperMethod.ParamMap;
33 import org.apache.ibatis.executor.Executor;
34 import org.apache.ibatis.executor.ExecutorException;
35 import org.apache.ibatis.mapping.MappedStatement;
36 import org.apache.ibatis.reflection.ArrayUtil;
37 import org.apache.ibatis.reflection.MetaObject;
38 import org.apache.ibatis.reflection.ParamNameResolver;
39 import org.apache.ibatis.session.Configuration;
40 import org.apache.ibatis.session.defaults.DefaultSqlSession.StrictMap;
41 import org.apache.ibatis.type.JdbcType;
42 import org.apache.ibatis.type.TypeHandler;
43 import org.apache.ibatis.type.TypeHandlerRegistry;
44 import org.apache.ibatis.util.MapUtil;
45
46
47
48
49
50 public class Jdbc3KeyGenerator implements KeyGenerator {
51
52 private static final String SECOND_GENERIC_PARAM_NAME = ParamNameResolver.GENERIC_NAME_PREFIX + "2";
53
54
55
56
57
58
59 public static final Jdbc3KeyGenerator INSTANCE = new Jdbc3KeyGenerator();
60
61 private static final String MSG_TOO_MANY_KEYS = "Too many keys are generated. There are only %d target objects. "
62 + "You either specified a wrong 'keyProperty' or encountered a driver bug like #1523.";
63
64 @Override
65 public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
66
67 }
68
69 @Override
70 public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
71 processBatch(ms, stmt, parameter);
72 }
73
74 public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
75 final String[] keyProperties = ms.getKeyProperties();
76 if (keyProperties == null || keyProperties.length == 0) {
77 return;
78 }
79 try (ResultSet rs = stmt.getGeneratedKeys()) {
80 final ResultSetMetaData rsmd = rs.getMetaData();
81 final Configuration configuration = ms.getConfiguration();
82 if (rsmd.getColumnCount() < keyProperties.length) {
83
84 } else {
85 assignKeys(configuration, rs, rsmd, keyProperties, parameter);
86 }
87 } catch (Exception e) {
88 throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
89 }
90 }
91
92 @SuppressWarnings("unchecked")
93 private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
94 Object parameter) throws SQLException {
95 if (parameter instanceof ParamMap || parameter instanceof StrictMap) {
96
97 assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
98 } else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
99 && ((ArrayList<?>) parameter).get(0) instanceof ParamMap) {
100
101 assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, (ArrayList<ParamMap<?>>) parameter);
102 } else {
103
104 assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
105 }
106 }
107
108 private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
109 String[] keyProperties, Object parameter) throws SQLException {
110 Collection<?> params = collectionize(parameter);
111 if (params.isEmpty()) {
112 return;
113 }
114 List<KeyAssigner> assignerList = new ArrayList<>();
115 for (int i = 0; i < keyProperties.length; i++) {
116 assignerList.add(new KeyAssigner(configuration, rsmd, i + 1, null, keyProperties[i]));
117 }
118 Iterator<?> iterator = params.iterator();
119 while (rs.next()) {
120 if (!iterator.hasNext()) {
121 throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, params.size()));
122 }
123 Object param = iterator.next();
124 assignerList.forEach(x -> x.assign(rs, param));
125 }
126 }
127
128 private void assignKeysToParamMapList(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
129 String[] keyProperties, ArrayList<ParamMap<?>> paramMapList) throws SQLException {
130 Iterator<ParamMap<?>> iterator = paramMapList.iterator();
131 List<KeyAssigner> assignerList = new ArrayList<>();
132 long counter = 0;
133 while (rs.next()) {
134 if (!iterator.hasNext()) {
135 throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, counter));
136 }
137 ParamMap<?> paramMap = iterator.next();
138 if (assignerList.isEmpty()) {
139 for (int i = 0; i < keyProperties.length; i++) {
140 assignerList
141 .add(getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i], keyProperties, false)
142 .getValue());
143 }
144 }
145 assignerList.forEach(x -> x.assign(rs, paramMap));
146 counter++;
147 }
148 }
149
150 private void assignKeysToParamMap(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
151 String[] keyProperties, Map<String, ?> paramMap) throws SQLException {
152 if (paramMap.isEmpty()) {
153 return;
154 }
155 Map<String, Entry<Iterator<?>, List<KeyAssigner>>> assignerMap = new HashMap<>();
156 for (int i = 0; i < keyProperties.length; i++) {
157 Entry<String, KeyAssigner> entry = getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i],
158 keyProperties, true);
159 Entry<Iterator<?>, List<KeyAssigner>> iteratorPair = MapUtil.computeIfAbsent(assignerMap, entry.getKey(),
160 k -> MapUtil.entry(collectionize(paramMap.get(k)).iterator(), new ArrayList<>()));
161 iteratorPair.getValue().add(entry.getValue());
162 }
163 long counter = 0;
164 while (rs.next()) {
165 for (Entry<Iterator<?>, List<KeyAssigner>> pair : assignerMap.values()) {
166 if (!pair.getKey().hasNext()) {
167 throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, counter));
168 }
169 Object param = pair.getKey().next();
170 pair.getValue().forEach(x -> x.assign(rs, param));
171 }
172 counter++;
173 }
174 }
175
176 private Entry<String, KeyAssigner> getAssignerForParamMap(Configuration config, ResultSetMetaData rsmd,
177 int columnPosition, Map<String, ?> paramMap, String keyProperty, String[] keyProperties, boolean omitParamName) {
178 Set<String> keySet = paramMap.keySet();
179
180
181 boolean singleParam = !keySet.contains(SECOND_GENERIC_PARAM_NAME);
182 int firstDot = keyProperty.indexOf('.');
183 if (firstDot == -1) {
184 if (singleParam) {
185 return getAssignerForSingleParam(config, rsmd, columnPosition, paramMap, keyProperty, omitParamName);
186 }
187 throw new ExecutorException("Could not determine which parameter to assign generated keys to. "
188 + "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
189 + "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
190 + keySet);
191 }
192 String paramName = keyProperty.substring(0, firstDot);
193 if (keySet.contains(paramName)) {
194 String argParamName = omitParamName ? null : paramName;
195 String argKeyProperty = keyProperty.substring(firstDot + 1);
196 return MapUtil.entry(paramName, new KeyAssigner(config, rsmd, columnPosition, argParamName, argKeyProperty));
197 }
198 if (singleParam) {
199 return getAssignerForSingleParam(config, rsmd, columnPosition, paramMap, keyProperty, omitParamName);
200 } else {
201 throw new ExecutorException("Could not find parameter '" + paramName + "'. "
202 + "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
203 + "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
204 + keySet);
205 }
206 }
207
208 private Entry<String, KeyAssigner> getAssignerForSingleParam(Configuration config, ResultSetMetaData rsmd,
209 int columnPosition, Map<String, ?> paramMap, String keyProperty, boolean omitParamName) {
210
211 String singleParamName = nameOfSingleParam(paramMap);
212 String argParamName = omitParamName ? null : singleParamName;
213 return MapUtil.entry(singleParamName, new KeyAssigner(config, rsmd, columnPosition, argParamName, keyProperty));
214 }
215
216 private static String nameOfSingleParam(Map<String, ?> paramMap) {
217
218 return paramMap.keySet().iterator().next();
219 }
220
221 private static Collection<?> collectionize(Object param) {
222 if (param instanceof Collection) {
223 return (Collection<?>) param;
224 }
225 if (param instanceof Object[]) {
226 return Arrays.asList((Object[]) param);
227 } else {
228 return Arrays.asList(param);
229 }
230 }
231
232 private static class KeyAssigner {
233 private final Configuration configuration;
234 private final ResultSetMetaData rsmd;
235 private final TypeHandlerRegistry typeHandlerRegistry;
236 private final int columnPosition;
237 private final String paramName;
238 private final String propertyName;
239 private TypeHandler<?> typeHandler;
240
241 protected KeyAssigner(Configuration configuration, ResultSetMetaData rsmd, int columnPosition, String paramName,
242 String propertyName) {
243 this.configuration = configuration;
244 this.rsmd = rsmd;
245 this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
246 this.columnPosition = columnPosition;
247 this.paramName = paramName;
248 this.propertyName = propertyName;
249 }
250
251 protected void assign(ResultSet rs, Object param) {
252 if (paramName != null) {
253
254 param = ((ParamMap<?>) param).get(paramName);
255 }
256 MetaObject metaParam = configuration.newMetaObject(param);
257 try {
258 if (typeHandler == null) {
259 if (!metaParam.hasSetter(propertyName)) {
260 throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '"
261 + metaParam.getOriginalObject().getClass().getName() + "'.");
262 }
263 Class<?> propertyType = metaParam.getSetterType(propertyName);
264 typeHandler = typeHandlerRegistry.getTypeHandler(propertyType,
265 JdbcType.forCode(rsmd.getColumnType(columnPosition)));
266 }
267 if (typeHandler == null) {
268
269 } else {
270 Object value = typeHandler.getResult(rs, columnPosition);
271 metaParam.setValue(propertyName, value);
272 }
273 } catch (SQLException e) {
274 throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e,
275 e);
276 }
277 }
278 }
279 }