|
19 | 19 | import java.sql.ResultSetMetaData;
|
20 | 20 | import java.sql.SQLException;
|
21 | 21 | import java.sql.Statement;
|
| 22 | +import java.util.AbstractMap; |
| 23 | +import java.util.ArrayList; |
22 | 24 | import java.util.Arrays;
|
23 | 25 | import java.util.Collection;
|
| 26 | +import java.util.HashMap; |
| 27 | +import java.util.Iterator; |
| 28 | +import java.util.List; |
24 | 29 | import java.util.Map;
|
| 30 | +import java.util.Map.Entry; |
25 | 31 |
|
26 | 32 | import org.apache.ibatis.binding.MapperMethod.ParamMap;
|
27 | 33 | import org.apache.ibatis.executor.Executor;
|
@@ -64,122 +70,190 @@ public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
|
64 | 70 | return;
|
65 | 71 | }
|
66 | 72 | try (ResultSet rs = stmt.getGeneratedKeys()) {
|
| 73 | + final ResultSetMetaData rsmd = rs.getMetaData(); |
67 | 74 | final Configuration configuration = ms.getConfiguration();
|
68 |
| - if (rs.getMetaData().getColumnCount() >= keyProperties.length) { |
69 |
| - Object soleParam = getSoleParameter(parameter); |
70 |
| - if (soleParam != null) { |
71 |
| - assignKeysToParam(configuration, rs, keyProperties, soleParam); |
72 |
| - } else { |
73 |
| - assignKeysToOneOfParams(configuration, rs, keyProperties, (Map<?, ?>) parameter); |
74 |
| - } |
| 75 | + if (rsmd.getColumnCount() < keyProperties.length) { |
| 76 | + // Error? |
| 77 | + } else { |
| 78 | + assignKeys(configuration, rs, rsmd, keyProperties, parameter); |
75 | 79 | }
|
76 | 80 | } catch (Exception e) {
|
77 | 81 | throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
|
78 | 82 | }
|
79 | 83 | }
|
80 | 84 |
|
81 |
| - protected void assignKeysToOneOfParams(final Configuration configuration, ResultSet rs, final String[] keyProperties, |
82 |
| - Map<?, ?> paramMap) throws SQLException { |
83 |
| - // Assuming 'keyProperty' includes the parameter name. e.g. 'param.id'. |
84 |
| - int firstDot = keyProperties[0].indexOf('.'); |
85 |
| - if (firstDot == -1) { |
86 |
| - throw new ExecutorException( |
87 |
| - "Could not determine which parameter to assign generated keys to. " |
88 |
| - + "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). " |
89 |
| - + "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are " |
90 |
| - + paramMap.keySet()); |
91 |
| - } |
92 |
| - String paramName = keyProperties[0].substring(0, firstDot); |
93 |
| - Object param; |
94 |
| - if (paramMap.containsKey(paramName)) { |
95 |
| - param = paramMap.get(paramName); |
| 85 | + @SuppressWarnings("unchecked") |
| 86 | + private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties, |
| 87 | + Object parameter) throws SQLException { |
| 88 | + if (parameter instanceof ParamMap || parameter instanceof StrictMap) { |
| 89 | + // Multi-param or single param with @Param |
| 90 | + assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter); |
| 91 | + } else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty() |
| 92 | + && ((ArrayList<?>) parameter).get(0) instanceof ParamMap) { |
| 93 | + // Multi-param or single param with @Param in batch operation |
| 94 | + assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, ((ArrayList<ParamMap<?>>) parameter)); |
96 | 95 | } else {
|
97 |
| - throw new ExecutorException("Could not find parameter '" + paramName + "'. " |
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()); |
| 96 | + // Single param without @Param |
| 97 | + assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter); |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, |
| 102 | + String[] keyProperties, Object parameter) throws SQLException { |
| 103 | + Collection<?> params = collectionize(parameter); |
| 104 | + if (params.isEmpty()) { |
| 105 | + return; |
101 | 106 | }
|
102 |
| - // Remove param name from 'keyProperty' string. e.g. 'param.id' -> 'id' |
103 |
| - String[] modifiedKeyProperties = new String[keyProperties.length]; |
| 107 | + List<KeyAssigner> assignerList = new ArrayList<>(); |
104 | 108 | for (int i = 0; i < keyProperties.length; i++) {
|
105 |
| - if (keyProperties[i].charAt(firstDot) == '.' && keyProperties[i].startsWith(paramName)) { |
106 |
| - modifiedKeyProperties[i] = keyProperties[i].substring(firstDot + 1); |
107 |
| - } else { |
108 |
| - throw new ExecutorException("Assigning generated keys to multiple parameters is not supported. " |
109 |
| - + "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). " |
110 |
| - + "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are " |
111 |
| - + paramMap.keySet()); |
112 |
| - } |
| 109 | + assignerList.add(new KeyAssigner(configuration, rsmd, i + 1, null, keyProperties[i])); |
| 110 | + } |
| 111 | + Iterator<?> iterator = params.iterator(); |
| 112 | + while (rs.next()) { |
| 113 | + Object param = iterator.next(); |
| 114 | + assignerList.forEach(x -> x.assign(rs, param)); |
113 | 115 | }
|
114 |
| - assignKeysToParam(configuration, rs, modifiedKeyProperties, param); |
115 | 116 | }
|
116 | 117 |
|
117 |
| - private void assignKeysToParam(final Configuration configuration, ResultSet rs, final String[] keyProperties, |
118 |
| - Object param) |
119 |
| - throws SQLException { |
120 |
| - final TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); |
121 |
| - final ResultSetMetaData rsmd = rs.getMetaData(); |
122 |
| - // Wrap the parameter in Collection to normalize the logic. |
123 |
| - Collection<?> paramAsCollection; |
124 |
| - if (param instanceof Object[]) { |
125 |
| - paramAsCollection = Arrays.asList((Object[]) param); |
126 |
| - } else if (!(param instanceof Collection)) { |
127 |
| - paramAsCollection = Arrays.asList(param); |
128 |
| - } else { |
129 |
| - paramAsCollection = (Collection<?>) param; |
130 |
| - } |
131 |
| - TypeHandler<?>[] typeHandlers = null; |
132 |
| - for (Object obj : paramAsCollection) { |
133 |
| - if (!rs.next()) { |
134 |
| - break; |
135 |
| - } |
136 |
| - MetaObject metaParam = configuration.newMetaObject(obj); |
137 |
| - if (typeHandlers == null) { |
138 |
| - typeHandlers = getTypeHandlers(typeHandlerRegistry, metaParam, keyProperties, rsmd); |
| 118 | + private void assignKeysToParamMapList(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, |
| 119 | + String[] keyProperties, ArrayList<ParamMap<?>> paramMapList) throws SQLException { |
| 120 | + Iterator<ParamMap<?>> iterator = paramMapList.iterator(); |
| 121 | + List<KeyAssigner> assignerList = new ArrayList<>(); |
| 122 | + while (rs.next()) { |
| 123 | + ParamMap<?> paramMap = iterator.next(); |
| 124 | + if (assignerList.isEmpty()) { |
| 125 | + for (int i = 0; i < keyProperties.length; i++) { |
| 126 | + assignerList |
| 127 | + .add(getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i], keyProperties, false) |
| 128 | + .getValue()); |
| 129 | + } |
139 | 130 | }
|
140 |
| - populateKeys(rs, metaParam, keyProperties, typeHandlers); |
| 131 | + assignerList.forEach(x -> x.assign(rs, paramMap)); |
141 | 132 | }
|
142 | 133 | }
|
143 | 134 |
|
144 |
| - private Object getSoleParameter(Object parameter) { |
145 |
| - if (!(parameter instanceof ParamMap || parameter instanceof StrictMap)) { |
146 |
| - return parameter; |
| 135 | + private void assignKeysToParamMap(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, |
| 136 | + String[] keyProperties, Map<String, ?> paramMap) throws SQLException { |
| 137 | + if (paramMap.isEmpty()) { |
| 138 | + return; |
| 139 | + } |
| 140 | + Map<String, Entry<Iterator<?>, List<KeyAssigner>>> assignerMap = new HashMap<>(); |
| 141 | + for (int i = 0; i < keyProperties.length; i++) { |
| 142 | + Entry<String, KeyAssigner> entry = getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i], |
| 143 | + keyProperties, true); |
| 144 | + Entry<Iterator<?>, List<KeyAssigner>> iteratorPair = assignerMap.computeIfAbsent(entry.getKey(), |
| 145 | + k -> entry(collectionize(paramMap.get(k)).iterator(), new ArrayList<>())); |
| 146 | + iteratorPair.getValue().add(entry.getValue()); |
147 | 147 | }
|
148 |
| - Object soleParam = null; |
149 |
| - for (Object paramValue : ((Map<?, ?>) parameter).values()) { |
150 |
| - if (soleParam == null) { |
151 |
| - soleParam = paramValue; |
152 |
| - } else if (soleParam != paramValue) { |
153 |
| - soleParam = null; |
154 |
| - break; |
| 148 | + while (rs.next()) { |
| 149 | + for (Entry<Iterator<?>, List<KeyAssigner>> pair : assignerMap.values()) { |
| 150 | + Object param = pair.getKey().next(); |
| 151 | + pair.getValue().forEach(x -> x.assign(rs, param)); |
155 | 152 | }
|
156 | 153 | }
|
157 |
| - return soleParam; |
158 | 154 | }
|
159 | 155 |
|
160 |
| - private TypeHandler<?>[] getTypeHandlers(TypeHandlerRegistry typeHandlerRegistry, MetaObject metaParam, String[] keyProperties, ResultSetMetaData rsmd) throws SQLException { |
161 |
| - TypeHandler<?>[] typeHandlers = new TypeHandler<?>[keyProperties.length]; |
162 |
| - for (int i = 0; i < keyProperties.length; i++) { |
163 |
| - if (metaParam.hasSetter(keyProperties[i])) { |
164 |
| - Class<?> keyPropertyType = metaParam.getSetterType(keyProperties[i]); |
165 |
| - typeHandlers[i] = typeHandlerRegistry.getTypeHandler(keyPropertyType, JdbcType.forCode(rsmd.getColumnType(i + 1))); |
166 |
| - } else { |
167 |
| - throw new ExecutorException("No setter found for the keyProperty '" + keyProperties[i] + "' in '" |
168 |
| - + metaParam.getOriginalObject().getClass().getName() + "'."); |
| 156 | + private Entry<String, KeyAssigner> getAssignerForParamMap(Configuration config, ResultSetMetaData rsmd, |
| 157 | + int columnPosition, Map<String, ?> paramMap, String keyProperty, String[] keyProperties, boolean omitParamName) { |
| 158 | + boolean singleParam = paramMap.values().stream().distinct().count() == 1; |
| 159 | + int firstDot = keyProperty.indexOf('.'); |
| 160 | + if (firstDot == -1) { |
| 161 | + if (singleParam) { |
| 162 | + // Assume 'keyProperty' to be a property of the single param. |
| 163 | + String singleParamName = nameOfSingleParam(paramMap); |
| 164 | + String argParamName = omitParamName ? null : singleParamName; |
| 165 | + return entry(singleParamName, new KeyAssigner(config, rsmd, columnPosition, argParamName, keyProperty)); |
169 | 166 | }
|
| 167 | + throw new ExecutorException("Could not determine which parameter to assign generated keys to. " |
| 168 | + + "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). " |
| 169 | + + "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are " |
| 170 | + + paramMap.keySet()); |
| 171 | + } |
| 172 | + String paramName = keyProperty.substring(0, firstDot); |
| 173 | + if (paramMap.containsKey(paramName)) { |
| 174 | + String argParamName = omitParamName ? null : paramName; |
| 175 | + String argKeyProperty = keyProperty.substring(firstDot + 1); |
| 176 | + return entry(paramName, new KeyAssigner(config, rsmd, columnPosition, argParamName, argKeyProperty)); |
| 177 | + } else if (singleParam) { |
| 178 | + // Assume 'keyProperty' to be a property of the single param. |
| 179 | + String singleParamName = nameOfSingleParam(paramMap); |
| 180 | + String argParamName = omitParamName ? null : singleParamName; |
| 181 | + return entry(singleParamName, new KeyAssigner(config, rsmd, columnPosition, argParamName, keyProperty)); |
| 182 | + } else { |
| 183 | + throw new ExecutorException("Could not find parameter '" + paramName + "'. " |
| 184 | + + "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). " |
| 185 | + + "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are " |
| 186 | + + paramMap.keySet()); |
170 | 187 | }
|
171 |
| - return typeHandlers; |
172 | 188 | }
|
173 | 189 |
|
174 |
| - private void populateKeys(ResultSet rs, MetaObject metaParam, String[] keyProperties, TypeHandler<?>[] typeHandlers) throws SQLException { |
175 |
| - for (int i = 0; i < keyProperties.length; i++) { |
176 |
| - String property = keyProperties[i]; |
177 |
| - TypeHandler<?> th = typeHandlers[i]; |
178 |
| - if (th != null) { |
179 |
| - Object value = th.getResult(rs, i + 1); |
180 |
| - metaParam.setValue(property, value); |
181 |
| - } |
| 190 | + private static String nameOfSingleParam(Map<String, ?> paramMap) { |
| 191 | + // There is virtually one parameter, so any key works. |
| 192 | + return paramMap.keySet().iterator().next(); |
| 193 | + } |
| 194 | + |
| 195 | + private static Collection<?> collectionize(Object param) { |
| 196 | + if (param instanceof Collection) { |
| 197 | + return (Collection<?>) param; |
| 198 | + } else if (param instanceof Object[]) { |
| 199 | + return Arrays.asList((Object[]) param); |
| 200 | + } else { |
| 201 | + return Arrays.asList(param); |
182 | 202 | }
|
183 | 203 | }
|
184 | 204 |
|
| 205 | + private static <K, V> Entry<K, V> entry(K key, V value) { |
| 206 | + // Replace this with Map.entry(key, value) in Java 9. |
| 207 | + return new AbstractMap.SimpleImmutableEntry<>(key, value); |
| 208 | + } |
| 209 | + |
| 210 | + private class KeyAssigner { |
| 211 | + protected final Configuration configuration; |
| 212 | + protected final ResultSetMetaData rsmd; |
| 213 | + protected final TypeHandlerRegistry typeHandlerRegistry; |
| 214 | + protected final int columnPosition; |
| 215 | + protected final String paramName; |
| 216 | + protected final String propertyName; |
| 217 | + protected TypeHandler<?> typeHandler; |
| 218 | + |
| 219 | + protected KeyAssigner(Configuration configuration, ResultSetMetaData rsmd, int columnPosition, String paramName, |
| 220 | + String propertyName) { |
| 221 | + super(); |
| 222 | + this.configuration = configuration; |
| 223 | + this.rsmd = rsmd; |
| 224 | + this.typeHandlerRegistry = configuration.getTypeHandlerRegistry(); |
| 225 | + this.columnPosition = columnPosition; |
| 226 | + this.paramName = paramName; |
| 227 | + this.propertyName = propertyName; |
| 228 | + } |
| 229 | + |
| 230 | + protected void assign(ResultSet rs, Object param) { |
| 231 | + if (paramName != null) { |
| 232 | + // If paramName is set, param is ParamMap |
| 233 | + param = ((ParamMap<?>) param).get(paramName); |
| 234 | + } |
| 235 | + MetaObject metaParam = configuration.newMetaObject(param); |
| 236 | + try { |
| 237 | + if (typeHandler == null) { |
| 238 | + if (metaParam.hasSetter(propertyName)) { |
| 239 | + Class<?> propertyType = metaParam.getSetterType(propertyName); |
| 240 | + typeHandler = typeHandlerRegistry.getTypeHandler(propertyType, |
| 241 | + JdbcType.forCode(rsmd.getColumnType(columnPosition))); |
| 242 | + } else { |
| 243 | + throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '" |
| 244 | + + metaParam.getOriginalObject().getClass().getName() + "'."); |
| 245 | + } |
| 246 | + } |
| 247 | + if (typeHandler == null) { |
| 248 | + // Error? |
| 249 | + } else { |
| 250 | + Object value = typeHandler.getResult(rs, columnPosition); |
| 251 | + metaParam.setValue(propertyName, value); |
| 252 | + } |
| 253 | + } catch (SQLException e) { |
| 254 | + throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, |
| 255 | + e); |
| 256 | + } |
| 257 | + } |
| 258 | + } |
185 | 259 | }
|
0 commit comments