• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mybatis / mybatis-3 / 3448

11 Jul 2026 06:09PM UTC coverage: 87.453% (+0.003%) from 87.45%
3448

Pull #3690

github

web-flow
Merge 116c6233d into 870b6d7e6
Pull Request #3690: docs: add SqlSessionManager documentation

3873 of 4681 branches covered (82.74%)

9995 of 11429 relevant lines covered (87.45%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.03
/src/main/java/org/apache/ibatis/executor/keygen/Jdbc3KeyGenerator.java
1
/*
2
 *    Copyright 2009-2026 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
 *       https://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.apache.ibatis.executor.keygen;
17

18
import java.lang.reflect.Type;
19
import java.sql.ResultSet;
20
import java.sql.ResultSetMetaData;
21
import java.sql.SQLException;
22
import java.sql.Statement;
23
import java.util.ArrayList;
24
import java.util.Arrays;
25
import java.util.Collection;
26
import java.util.HashMap;
27
import java.util.Iterator;
28
import java.util.List;
29
import java.util.Map;
30
import java.util.Map.Entry;
31
import java.util.Set;
32

33
import org.apache.ibatis.binding.MapperMethod.ParamMap;
34
import org.apache.ibatis.executor.Executor;
35
import org.apache.ibatis.executor.ExecutorException;
36
import org.apache.ibatis.mapping.MappedStatement;
37
import org.apache.ibatis.reflection.ArrayUtil;
38
import org.apache.ibatis.reflection.MetaObject;
39
import org.apache.ibatis.reflection.ParamNameResolver;
40
import org.apache.ibatis.session.Configuration;
41
import org.apache.ibatis.session.defaults.DefaultSqlSession.StrictMap;
42
import org.apache.ibatis.type.JdbcType;
43
import org.apache.ibatis.type.TypeHandler;
44
import org.apache.ibatis.type.TypeHandlerRegistry;
45

46
/**
47
 * @author Clinton Begin
48
 * @author Kazuki Shimizu
49
 */
50
public class Jdbc3KeyGenerator implements KeyGenerator {
1✔
51

52
  private static final String SECOND_GENERIC_PARAM_NAME = ParamNameResolver.GENERIC_NAME_PREFIX + "2";
53

54
  /**
55
   * A shared instance.
56
   *
57
   * @since 3.4.3
58
   */
59
  public static final Jdbc3KeyGenerator INSTANCE = new Jdbc3KeyGenerator();
1✔
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
  private static final String MSG_TOO_MANY_KEYS_FOR_MAP = "Too many keys are generated. There are only %d target object(s). "
65
      + "A 'Map' passed as the sole parameter is treated as a single target object, so keys generated by a multi-row "
66
      + "insert cannot be assigned to a collection nested in the 'Map'. Pass the collection itself as the sole parameter, "
67
      + "or declare the parameters with @Param and prefix 'keyProperty' with the parameter name (e.g. 'list.id').";
68

69
  @Override
70
  public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
71
    // do nothing
72
  }
1✔
73

74
  @Override
75
  public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
76
    processBatch(ms, stmt, parameter);
1✔
77
  }
1✔
78

79
  public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
80
    final String[] keyProperties = ms.getKeyProperties();
1✔
81
    if (keyProperties == null || keyProperties.length == 0) {
1!
82
      return;
1✔
83
    }
84
    try (ResultSet rs = stmt.getGeneratedKeys()) {
1✔
85
      final ResultSetMetaData rsmd = rs.getMetaData();
1✔
86
      final Configuration configuration = ms.getConfiguration();
1✔
87
      if (rsmd.getColumnCount() < keyProperties.length) {
1!
88
        // Error?
89
      } else {
90
        assignKeys(configuration, rs, rsmd, keyProperties, parameter);
1✔
91
      }
92
    } catch (Exception e) {
1✔
93
      throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
1✔
94
    }
1✔
95
  }
1✔
96

97
  @SuppressWarnings("unchecked")
98
  private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
99
      Object parameter) throws SQLException {
100
    if (parameter instanceof ParamMap || parameter instanceof StrictMap) {
1!
101
      // Multi-param or single param with @Param
102
      assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
1✔
103
    } else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
1!
104
        && ((ArrayList<?>) parameter).get(0) instanceof ParamMap) {
1✔
105
      // Multi-param or single param with @Param in batch operation
106
      assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, (ArrayList<ParamMap<?>>) parameter);
1✔
107
    } else {
108
      // Single param without @Param
109
      assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
1✔
110
    }
111
  }
1✔
112

113
  private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
114
      String[] keyProperties, Object parameter) throws SQLException {
115
    Collection<?> params = collectionize(parameter);
1✔
116
    if (params.isEmpty()) {
1!
117
      return;
×
118
    }
119
    List<KeyAssigner> assignerList = new ArrayList<>();
1✔
120
    for (int i = 0; i < keyProperties.length; i++) {
1✔
121
      assignerList.add(new KeyAssigner(configuration, rsmd, i + 1, null, keyProperties[i]));
1✔
122
    }
123
    Iterator<?> iterator = params.iterator();
1✔
124
    while (rs.next()) {
1✔
125
      if (!iterator.hasNext()) {
1✔
126
        throw new ExecutorException(
1✔
127
            String.format(parameter instanceof Map ? MSG_TOO_MANY_KEYS_FOR_MAP : MSG_TOO_MANY_KEYS, params.size()));
1✔
128
      }
129
      Object param = iterator.next();
1✔
130
      assignerList.forEach(x -> x.assign(rs, param));
1✔
131
    }
1✔
132
  }
1✔
133

134
  private void assignKeysToParamMapList(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
135
      String[] keyProperties, ArrayList<ParamMap<?>> paramMapList) throws SQLException {
136
    Iterator<ParamMap<?>> iterator = paramMapList.iterator();
1✔
137
    List<KeyAssigner> assignerList = new ArrayList<>();
1✔
138
    long counter = 0;
1✔
139
    while (rs.next()) {
1✔
140
      if (!iterator.hasNext()) {
1✔
141
        throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, counter));
1✔
142
      }
143
      ParamMap<?> paramMap = iterator.next();
1✔
144
      if (assignerList.isEmpty()) {
1✔
145
        for (int i = 0; i < keyProperties.length; i++) {
1✔
146
          assignerList
1✔
147
              .add(getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i], keyProperties, false)
1✔
148
                  .getValue());
1✔
149
        }
150
      }
151
      assignerList.forEach(x -> x.assign(rs, paramMap));
1✔
152
      counter++;
1✔
153
    }
1✔
154
  }
1✔
155

156
  private void assignKeysToParamMap(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
157
      String[] keyProperties, Map<String, ?> paramMap) throws SQLException {
158
    if (paramMap.isEmpty()) {
1!
159
      return;
×
160
    }
161
    Map<String, Entry<Iterator<?>, List<KeyAssigner>>> assignerMap = new HashMap<>();
1✔
162
    for (int i = 0; i < keyProperties.length; i++) {
1✔
163
      Entry<String, KeyAssigner> entry = getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i],
1✔
164
          keyProperties, true);
165
      Entry<Iterator<?>, List<KeyAssigner>> iteratorPair = assignerMap.computeIfAbsent(entry.getKey(),
1✔
166
          k -> Map.entry(collectionize(paramMap.get(k)).iterator(), new ArrayList<>()));
1✔
167
      iteratorPair.getValue().add(entry.getValue());
1✔
168
    }
169
    long counter = 0;
1✔
170
    while (rs.next()) {
1✔
171
      for (Entry<Iterator<?>, List<KeyAssigner>> pair : assignerMap.values()) {
1✔
172
        if (!pair.getKey().hasNext()) {
1✔
173
          throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, counter));
1✔
174
        }
175
        Object param = pair.getKey().next();
1✔
176
        pair.getValue().forEach(x -> x.assign(rs, param));
1✔
177
      }
1✔
178
      counter++;
1✔
179
    }
180
  }
1✔
181

182
  private Entry<String, KeyAssigner> getAssignerForParamMap(Configuration config, ResultSetMetaData rsmd,
183
      int columnPosition, Map<String, ?> paramMap, String keyProperty, String[] keyProperties, boolean omitParamName) {
184
    Set<String> keySet = paramMap.keySet();
1✔
185
    // A caveat : if the only parameter has {@code @Param("param2")} on it,
186
    // it must be referenced with param name e.g. 'param2.x'.
187
    boolean singleParam = !keySet.contains(SECOND_GENERIC_PARAM_NAME);
1✔
188
    int firstDot = keyProperty.indexOf('.');
1✔
189
    if (firstDot == -1) {
1✔
190
      if (singleParam) {
1✔
191
        return getAssignerForSingleParam(config, rsmd, columnPosition, paramMap, keyProperty, omitParamName);
1✔
192
      }
193
      throw new ExecutorException("Could not determine which parameter to assign generated keys to. "
1✔
194
          + "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
195
          + "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
1✔
196
          + keySet);
197
    }
198
    String paramName = keyProperty.substring(0, firstDot);
1✔
199
    if (keySet.contains(paramName)) {
1✔
200
      String argParamName = omitParamName ? null : paramName;
1✔
201
      String argKeyProperty = keyProperty.substring(firstDot + 1);
1✔
202
      return Map.entry(paramName, new KeyAssigner(config, rsmd, columnPosition, argParamName, argKeyProperty));
1✔
203
    }
204
    if (singleParam) {
1!
205
      return getAssignerForSingleParam(config, rsmd, columnPosition, paramMap, keyProperty, omitParamName);
×
206
    } else {
207
      throw new ExecutorException("Could not find parameter '" + paramName + "'. "
1✔
208
          + "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
209
          + "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
1✔
210
          + keySet);
211
    }
212
  }
213

214
  private Entry<String, KeyAssigner> getAssignerForSingleParam(Configuration config, ResultSetMetaData rsmd,
215
      int columnPosition, Map<String, ?> paramMap, String keyProperty, boolean omitParamName) {
216
    // Assume 'keyProperty' to be a property of the single param.
217
    String singleParamName = nameOfSingleParam(paramMap);
1✔
218
    String argParamName = omitParamName ? null : singleParamName;
1✔
219
    return Map.entry(singleParamName, new KeyAssigner(config, rsmd, columnPosition, argParamName, keyProperty));
1✔
220
  }
221

222
  private static String nameOfSingleParam(Map<String, ?> paramMap) {
223
    // There is virtually one parameter, so any key works.
224
    return paramMap.keySet().iterator().next();
1✔
225
  }
226

227
  private static Collection<?> collectionize(Object param) {
228
    if (param instanceof Collection) {
1✔
229
      return (Collection<?>) param;
1✔
230
    }
231
    if (param instanceof Object[]) {
1✔
232
      return Arrays.asList((Object[]) param);
1✔
233
    } else {
234
      return Arrays.asList(param);
1✔
235
    }
236
  }
237

238
  private static class KeyAssigner {
239
    private final Configuration configuration;
240
    private final ResultSetMetaData rsmd;
241
    private final TypeHandlerRegistry typeHandlerRegistry;
242
    private final int columnPosition;
243
    private final String paramName;
244
    private final String propertyName;
245
    private TypeHandler<?> typeHandler;
246

247
    protected KeyAssigner(Configuration configuration, ResultSetMetaData rsmd, int columnPosition, String paramName,
248
        String propertyName) {
1✔
249
      this.configuration = configuration;
1✔
250
      this.rsmd = rsmd;
1✔
251
      this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
1✔
252
      this.columnPosition = columnPosition;
1✔
253
      this.paramName = paramName;
1✔
254
      this.propertyName = propertyName;
1✔
255
    }
1✔
256

257
    protected void assign(ResultSet rs, Object param) {
258
      if (paramName != null) {
1✔
259
        // If paramName is set, param is ParamMap
260
        param = ((ParamMap<?>) param).get(paramName);
1✔
261
      }
262
      MetaObject metaParam = configuration.newMetaObject(param);
1✔
263
      try {
264
        if (typeHandler == null) {
1✔
265
          if (!metaParam.hasSetter(propertyName)) {
1✔
266
            throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '"
1✔
267
                + metaParam.getOriginalObject().getClass().getName() + "'.");
1✔
268
          }
269
          Type propertyType = metaParam.getGenericSetterType(propertyName).getKey();
1✔
270
          JdbcType jdbcType = JdbcType.forCode(rsmd.getColumnType(columnPosition));
1✔
271
          typeHandler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
1✔
272
          if (typeHandler == null) {
1✔
273
            typeHandler = typeHandlerRegistry.getTypeHandler(jdbcType);
1✔
274
          }
275
        }
276
        if (typeHandler == null) {
1!
277
          // Error?
278
        } else {
279
          Object value = typeHandler.getResult(rs, columnPosition);
1✔
280
          metaParam.setValue(propertyName, value);
1✔
281
        }
282
      } catch (SQLException e) {
×
283
        throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e,
×
284
            e);
285
      }
1✔
286
    }
1✔
287
  }
288
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc