1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.ibatis.builder;
17
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Properties;
25 import java.util.Set;
26 import java.util.StringTokenizer;
27
28 import org.apache.ibatis.cache.Cache;
29 import org.apache.ibatis.cache.decorators.LruCache;
30 import org.apache.ibatis.cache.impl.PerpetualCache;
31 import org.apache.ibatis.executor.ErrorContext;
32 import org.apache.ibatis.executor.keygen.KeyGenerator;
33 import org.apache.ibatis.mapping.CacheBuilder;
34 import org.apache.ibatis.mapping.Discriminator;
35 import org.apache.ibatis.mapping.MappedStatement;
36 import org.apache.ibatis.mapping.ParameterMap;
37 import org.apache.ibatis.mapping.ParameterMapping;
38 import org.apache.ibatis.mapping.ParameterMode;
39 import org.apache.ibatis.mapping.ResultFlag;
40 import org.apache.ibatis.mapping.ResultMap;
41 import org.apache.ibatis.mapping.ResultMapping;
42 import org.apache.ibatis.mapping.ResultSetType;
43 import org.apache.ibatis.mapping.SqlCommandType;
44 import org.apache.ibatis.mapping.SqlSource;
45 import org.apache.ibatis.mapping.StatementType;
46 import org.apache.ibatis.reflection.MetaClass;
47 import org.apache.ibatis.scripting.LanguageDriver;
48 import org.apache.ibatis.session.Configuration;
49 import org.apache.ibatis.type.JdbcType;
50 import org.apache.ibatis.type.TypeHandler;
51
52
53
54
55 public class MapperBuilderAssistant extends BaseBuilder {
56
57 private String currentNamespace;
58 private final String resource;
59 private Cache currentCache;
60 private boolean unresolvedCacheRef;
61
62 public MapperBuilderAssistant(Configuration configuration, String resource) {
63 super(configuration);
64 ErrorContext.instance().resource(resource);
65 this.resource = resource;
66 }
67
68 public String getCurrentNamespace() {
69 return currentNamespace;
70 }
71
72 public void setCurrentNamespace(String currentNamespace) {
73 if (currentNamespace == null) {
74 throw new BuilderException("The mapper element requires a namespace attribute to be specified.");
75 }
76
77 if (this.currentNamespace != null && !this.currentNamespace.equals(currentNamespace)) {
78 throw new BuilderException(
79 "Wrong namespace. Expected '" + this.currentNamespace + "' but found '" + currentNamespace + "'.");
80 }
81
82 this.currentNamespace = currentNamespace;
83 }
84
85 public String applyCurrentNamespace(String base, boolean isReference) {
86 if (base == null) {
87 return null;
88 }
89 if (isReference) {
90
91 if (base.contains(".")) {
92 return base;
93 }
94 } else {
95
96 if (base.startsWith(currentNamespace + ".")) {
97 return base;
98 }
99 if (base.contains(".")) {
100 throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
101 }
102 }
103 return currentNamespace + "." + base;
104 }
105
106 public Cache useCacheRef(String namespace) {
107 if (namespace == null) {
108 throw new BuilderException("cache-ref element requires a namespace attribute.");
109 }
110 try {
111 unresolvedCacheRef = true;
112 Cache cache = configuration.getCache(namespace);
113 if (cache == null) {
114 throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
115 }
116 currentCache = cache;
117 unresolvedCacheRef = false;
118 return cache;
119 } catch (IllegalArgumentException e) {
120 throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
121 }
122 }
123
124 public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval,
125 Integer size, boolean readWrite, boolean blocking, Properties props) {
126 Cache cache = new CacheBuilder(currentNamespace).implementation(valueOrDefault(typeClass, PerpetualCache.class))
127 .addDecorator(valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval).size(size)
128 .readWrite(readWrite).blocking(blocking).properties(props).build();
129 configuration.addCache(cache);
130 currentCache = cache;
131 return cache;
132 }
133
134 public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) {
135 id = applyCurrentNamespace(id, false);
136 ParameterMap parameterMap = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings).build();
137 configuration.addParameterMap(parameterMap);
138 return parameterMap;
139 }
140
141 public ParameterMapping buildParameterMapping(Class<?> parameterType, String property, Class<?> javaType,
142 JdbcType jdbcType, String resultMap, ParameterMode parameterMode, Class<? extends TypeHandler<?>> typeHandler,
143 Integer numericScale) {
144 resultMap = applyCurrentNamespace(resultMap, true);
145
146
147 Class<?> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType);
148 TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
149
150 return new ParameterMapping.Builder(configuration, property, javaTypeClass).jdbcType(jdbcType)
151 .resultMapId(resultMap).mode(parameterMode).numericScale(numericScale).typeHandler(typeHandlerInstance).build();
152 }
153
154 public ResultMap addResultMap(String id, Class<?> type, String extend, Discriminator discriminator,
155 List<ResultMapping> resultMappings, Boolean autoMapping) {
156 id = applyCurrentNamespace(id, false);
157 extend = applyCurrentNamespace(extend, true);
158
159 if (extend != null) {
160 if (!configuration.hasResultMap(extend)) {
161 throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
162 }
163 ResultMap resultMap = configuration.getResultMap(extend);
164 List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
165 extendedResultMappings.removeAll(resultMappings);
166
167 boolean declaresConstructor = false;
168 for (ResultMapping resultMapping : resultMappings) {
169 if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
170 declaresConstructor = true;
171 break;
172 }
173 }
174 if (declaresConstructor) {
175 extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
176 }
177 resultMappings.addAll(extendedResultMappings);
178 }
179 ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
180 .discriminator(discriminator).build();
181 configuration.addResultMap(resultMap);
182 return resultMap;
183 }
184
185 public Discriminator buildDiscriminator(Class<?> resultType, String column, Class<?> javaType, JdbcType jdbcType,
186 Class<? extends TypeHandler<?>> typeHandler, Map<String, String> discriminatorMap) {
187 ResultMapping resultMapping = buildResultMapping(resultType, null, column, javaType, jdbcType, null, null, null,
188 null, typeHandler, new ArrayList<>(), null, null, false);
189 Map<String, String> namespaceDiscriminatorMap = new HashMap<>();
190 for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
191 String resultMap = e.getValue();
192 resultMap = applyCurrentNamespace(resultMap, true);
193 namespaceDiscriminatorMap.put(e.getKey(), resultMap);
194 }
195 return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
196 }
197
198 public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
199 SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
200 String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
201 boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
202 LanguageDriver lang, String resultSets, boolean dirtySelect) {
203
204 if (unresolvedCacheRef) {
205 throw new IncompleteElementException("Cache-ref not yet resolved");
206 }
207
208 id = applyCurrentNamespace(id, false);
209
210 MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
211 .resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType)
212 .keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang)
213 .resultOrdered(resultOrdered).resultSets(resultSets)
214 .resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType)
215 .flushCacheRequired(flushCache).useCache(useCache).cache(currentCache).dirtySelect(dirtySelect);
216
217 ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
218 if (statementParameterMap != null) {
219 statementBuilder.parameterMap(statementParameterMap);
220 }
221
222 MappedStatement statement = statementBuilder.build();
223 configuration.addMappedStatement(statement);
224 return statement;
225 }
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271 public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
272 SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
273 String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
274 boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
275 LanguageDriver lang, String resultSets) {
276 return addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
277 parameterType, resultMap, resultType, resultSetType, flushCache, useCache, resultOrdered, keyGenerator,
278 keyProperty, keyColumn, databaseId, lang, null, false);
279 }
280
281 public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType,
282 SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType,
283 String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache,
284 boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId,
285 LanguageDriver lang) {
286 return addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
287 parameterType, resultMap, resultType, resultSetType, flushCache, useCache, resultOrdered, keyGenerator,
288 keyProperty, keyColumn, databaseId, lang, null);
289 }
290
291 private <T> T valueOrDefault(T value, T defaultValue) {
292 return value == null ? defaultValue : value;
293 }
294
295 private ParameterMap getStatementParameterMap(String parameterMapName, Class<?> parameterTypeClass,
296 String statementId) {
297 parameterMapName = applyCurrentNamespace(parameterMapName, true);
298 ParameterMap parameterMap = null;
299 if (parameterMapName != null) {
300 try {
301 parameterMap = configuration.getParameterMap(parameterMapName);
302 } catch (IllegalArgumentException e) {
303 throw new IncompleteElementException("Could not find parameter map " + parameterMapName, e);
304 }
305 } else if (parameterTypeClass != null) {
306 List<ParameterMapping> parameterMappings = new ArrayList<>();
307 parameterMap = new ParameterMap.Builder(configuration, statementId + "-Inline", parameterTypeClass,
308 parameterMappings).build();
309 }
310 return parameterMap;
311 }
312
313 private List<ResultMap> getStatementResultMaps(String resultMap, Class<?> resultType, String statementId) {
314 resultMap = applyCurrentNamespace(resultMap, true);
315
316 List<ResultMap> resultMaps = new ArrayList<>();
317 if (resultMap != null) {
318 String[] resultMapNames = resultMap.split(",");
319 for (String resultMapName : resultMapNames) {
320 try {
321 resultMaps.add(configuration.getResultMap(resultMapName.trim()));
322 } catch (IllegalArgumentException e) {
323 throw new IncompleteElementException(
324 "Could not find result map '" + resultMapName + "' referenced from '" + statementId + "'", e);
325 }
326 }
327 } else if (resultType != null) {
328 ResultMap inlineResultMap = new ResultMap.Builder(configuration, statementId + "-Inline", resultType,
329 new ArrayList<>(), null).build();
330 resultMaps.add(inlineResultMap);
331 }
332 return resultMaps;
333 }
334
335 public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, Class<?> javaType,
336 JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix,
337 Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags, String resultSet, String foreignColumn,
338 boolean lazy) {
339 Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
340 TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
341 List<ResultMapping> composites;
342 if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {
343 composites = Collections.emptyList();
344 } else {
345 composites = parseCompositeColumnName(column);
346 }
347 return new ResultMapping.Builder(configuration, property, column, javaTypeClass).jdbcType(jdbcType)
348 .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
349 .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true)).resultSet(resultSet)
350 .typeHandler(typeHandlerInstance).flags(flags == null ? new ArrayList<>() : flags).composites(composites)
351 .notNullColumns(parseMultipleColumnNames(notNullColumn)).columnPrefix(columnPrefix).foreignColumn(foreignColumn)
352 .lazy(lazy).build();
353 }
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383 public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, Class<?> javaType,
384 JdbcType jdbcType, String nestedSelect, String nestedResultMap, String notNullColumn, String columnPrefix,
385 Class<? extends TypeHandler<?>> typeHandler, List<ResultFlag> flags) {
386 return buildResultMapping(resultType, property, column, javaType, jdbcType, nestedSelect, nestedResultMap,
387 notNullColumn, columnPrefix, typeHandler, flags, null, null, configuration.isLazyLoadingEnabled());
388 }
389
390
391
392
393
394
395
396
397
398
399
400 @Deprecated
401 public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
402 return configuration.getLanguageDriver(langClass);
403 }
404
405 private Set<String> parseMultipleColumnNames(String columnName) {
406 Set<String> columns = new HashSet<>();
407 if (columnName != null) {
408 if (columnName.indexOf(',') > -1) {
409 StringTokenizer parser = new StringTokenizer(columnName, "{}, ", false);
410 while (parser.hasMoreTokens()) {
411 String column = parser.nextToken();
412 columns.add(column);
413 }
414 } else {
415 columns.add(columnName);
416 }
417 }
418 return columns;
419 }
420
421 private List<ResultMapping> parseCompositeColumnName(String columnName) {
422 List<ResultMapping> composites = new ArrayList<>();
423 if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
424 StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
425 while (parser.hasMoreTokens()) {
426 String property = parser.nextToken();
427 String column = parser.nextToken();
428 ResultMapping complexResultMapping = new ResultMapping.Builder(configuration, property, column,
429 configuration.getTypeHandlerRegistry().getUnknownTypeHandler()).build();
430 composites.add(complexResultMapping);
431 }
432 }
433 return composites;
434 }
435
436 private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {
437 if (javaType == null && property != null) {
438 try {
439 MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
440 javaType = metaResultType.getSetterType(property);
441 } catch (Exception e) {
442
443 }
444 }
445 if (javaType == null) {
446 javaType = Object.class;
447 }
448 return javaType;
449 }
450
451 private Class<?> resolveParameterJavaType(Class<?> resultType, String property, Class<?> javaType,
452 JdbcType jdbcType) {
453 if (javaType == null) {
454 if (JdbcType.CURSOR.equals(jdbcType)) {
455 javaType = java.sql.ResultSet.class;
456 } else if (Map.class.isAssignableFrom(resultType)) {
457 javaType = Object.class;
458 } else {
459 MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
460 javaType = metaResultType.getGetterType(property);
461 }
462 }
463 if (javaType == null) {
464 javaType = Object.class;
465 }
466 return javaType;
467 }
468
469 }