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

mybatis / mybatis-3 / 2604

03 Jan 2025 10:00AM UTC coverage: 87.524% (+0.3%) from 87.177%
2604

Pull #3146

github

web-flow
Merge 60c1f5fea into 8ac3920af
Pull Request #3146: Shared ambiguity instance

3633 of 4401 branches covered (82.55%)

4 of 4 new or added lines in 1 file covered. (100.0%)

254 existing lines in 22 files now uncovered.

9569 of 10933 relevant lines covered (87.52%)

0.88 hits per line

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

95.93
/src/main/java/org/apache/ibatis/executor/keygen/Jdbc3KeyGenerator.java
1
/*
2
 *    Copyright 2009-2025 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.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

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

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

53
  /**
54
   * A shared instance.
55
   *
56
   * @since 3.4.3
57
   */
58
  public static final Jdbc3KeyGenerator INSTANCE = new Jdbc3KeyGenerator();
1✔
59

60
  private static final String MSG_TOO_MANY_KEYS = "Too many keys are generated. There are only %d target objects. "
61
      + "You either specified a wrong 'keyProperty' or encountered a driver bug like #1523.";
62

63
  @Override
64
  public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
65
    // do nothing
66
  }
1✔
67

68
  @Override
69
  public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
70
    processBatch(ms, stmt, parameter);
1✔
71
  }
1✔
72

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

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

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

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

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

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

207
  private Entry<String, KeyAssigner> getAssignerForSingleParam(Configuration config, ResultSetMetaData rsmd,
208
      int columnPosition, Map<String, ?> paramMap, String keyProperty, boolean omitParamName) {
209
    // Assume 'keyProperty' to be a property of the single param.
210
    String singleParamName = nameOfSingleParam(paramMap);
1✔
211
    String argParamName = omitParamName ? null : singleParamName;
1✔
212
    return Map.entry(singleParamName, new KeyAssigner(config, rsmd, columnPosition, argParamName, keyProperty));
1✔
213
  }
214

215
  private static String nameOfSingleParam(Map<String, ?> paramMap) {
216
    // There is virtually one parameter, so any key works.
217
    return paramMap.keySet().iterator().next();
1✔
218
  }
219

220
  private static Collection<?> collectionize(Object param) {
221
    if (param instanceof Collection) {
1✔
222
      return (Collection<?>) param;
1✔
223
    }
224
    if (param instanceof Object[]) {
1✔
225
      return Arrays.asList((Object[]) param);
1✔
226
    } else {
227
      return Arrays.asList(param);
1✔
228
    }
229
  }
230

231
  private static class KeyAssigner {
232
    private final Configuration configuration;
233
    private final ResultSetMetaData rsmd;
234
    private final TypeHandlerRegistry typeHandlerRegistry;
235
    private final int columnPosition;
236
    private final String paramName;
237
    private final String propertyName;
238
    private TypeHandler<?> typeHandler;
239

240
    protected KeyAssigner(Configuration configuration, ResultSetMetaData rsmd, int columnPosition, String paramName,
241
        String propertyName) {
1✔
242
      this.configuration = configuration;
1✔
243
      this.rsmd = rsmd;
1✔
244
      this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
1✔
245
      this.columnPosition = columnPosition;
1✔
246
      this.paramName = paramName;
1✔
247
      this.propertyName = propertyName;
1✔
248
    }
1✔
249

250
    protected void assign(ResultSet rs, Object param) {
251
      if (paramName != null) {
1✔
252
        // If paramName is set, param is ParamMap
253
        param = ((ParamMap<?>) param).get(paramName);
1✔
254
      }
255
      MetaObject metaParam = configuration.newMetaObject(param);
1✔
256
      try {
257
        if (typeHandler == null) {
1✔
258
          if (!metaParam.hasSetter(propertyName)) {
1✔
259
            throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '"
1✔
260
                + metaParam.getOriginalObject().getClass().getName() + "'.");
1✔
261
          }
262
          Class<?> propertyType = metaParam.getSetterType(propertyName);
1✔
263
          typeHandler = typeHandlerRegistry.getTypeHandler(propertyType,
1✔
264
              JdbcType.forCode(rsmd.getColumnType(columnPosition)));
1✔
265
        }
266
        if (typeHandler == null) {
1!
267
          // Error?
268
        } else {
269
          Object value = typeHandler.getResult(rs, columnPosition);
1✔
270
          metaParam.setValue(propertyName, value);
1✔
271
        }
UNCOV
272
      } catch (SQLException e) {
×
273
        throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e,
×
274
            e);
275
      }
1✔
276
    }
1✔
277
  }
278
}
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

© 2025 Coveralls, Inc