Skip to content

Commit dd377a8

Browse files
committed
fix for http://code.google.com/p/mybatis/issues/detail?id=354 File order matters when extending resultMaps
1 parent 2dec22c commit dd377a8

File tree

11 files changed

+291
-79
lines changed

11 files changed

+291
-79
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.apache.ibatis.builder;
2+
3+
import org.apache.ibatis.builder.BuilderException;
4+
5+
public class IncompleteResultMapException extends BuilderException {
6+
private static final long serialVersionUID = 5710340627945568032L;
7+
public IncompleteResultMapException() {
8+
super();
9+
}
10+
public IncompleteResultMapException(String message, Throwable cause) {
11+
super(message, cause);
12+
}
13+
public IncompleteResultMapException(String message) {
14+
super(message);
15+
}
16+
public IncompleteResultMapException(Throwable cause) {
17+
super(cause);
18+
}
19+
}

src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ public ResultMap addResultMap(
131131

132132
ResultMap.Builder resultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings);
133133
if (extend != null) {
134+
if (!configuration.hasResultMap(extend)) {
135+
throw new IncompleteResultMapException("Could not find a parent resultmap with id '" + extend + "'");
136+
}
134137
ResultMap resultMap = configuration.getResultMap(extend);
135138
List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
136139
extendedResultMappings.removeAll(resultMappings);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.apache.ibatis.builder;
2+
3+
import java.util.List;
4+
5+
import org.apache.ibatis.mapping.Discriminator;
6+
import org.apache.ibatis.mapping.ResultMap;
7+
import org.apache.ibatis.mapping.ResultMapping;
8+
9+
public class ResultMapResolver {
10+
private final MapperBuilderAssistant assistant;
11+
private String id;
12+
private Class<?> type;
13+
private String extend;
14+
private Discriminator discriminator;
15+
private String notNullColumn;
16+
private List<ResultMapping> resultMappings;
17+
18+
public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, String extend, Discriminator discriminator, String notNullColumn,
19+
List<ResultMapping> resultMappings) {
20+
this.assistant = assistant;
21+
this.id = id;
22+
this.type = type;
23+
this.extend = extend;
24+
this.discriminator = discriminator;
25+
this.notNullColumn = notNullColumn;
26+
this.resultMappings = resultMappings;
27+
}
28+
29+
public ResultMap resolve() {
30+
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.notNullColumn, this.resultMappings);
31+
}
32+
33+
}

src/main/java/org/apache/ibatis/builder/xml/XMLMapperBuilder.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
import org.apache.ibatis.builder.BuilderException;
1616
import org.apache.ibatis.builder.CacheRefResolver;
1717
import org.apache.ibatis.builder.IncompleteCacheException;
18+
import org.apache.ibatis.builder.IncompleteResultMapException;
1819
import org.apache.ibatis.builder.MapperBuilderAssistant;
20+
import org.apache.ibatis.builder.ResultMapResolver;
1921
import org.apache.ibatis.cache.Cache;
2022
import org.apache.ibatis.executor.ErrorContext;
2123
import org.apache.ibatis.io.Resources;
@@ -75,6 +77,7 @@ public void parse() {
7577
bindMapperForNamespace();
7678
}
7779

80+
parsePendingResultMaps();
7881
parsePendingChacheRefs();
7982
parsePendingStatements();
8083
}
@@ -109,6 +112,21 @@ private void buildStatementFromContext(List<XNode> list) {
109112
}
110113
}
111114

115+
private void parsePendingResultMaps() {
116+
Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
117+
synchronized (incompleteResultMaps) {
118+
Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
119+
while (iter.hasNext()) {
120+
try {
121+
iter.next().resolve();
122+
iter.remove();
123+
} catch (IncompleteResultMapException e) {
124+
// ResultMap is still missing a resource...
125+
}
126+
}
127+
}
128+
}
129+
112130
private void parsePendingChacheRefs() {
113131
Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
114132
synchronized (incompleteCacheRefs) {
@@ -193,7 +211,11 @@ private void parameterMapElement(List<XNode> list) throws Exception {
193211

194212
private void resultMapElements(List<XNode> list) throws Exception {
195213
for (XNode resultMapNode : list) {
196-
resultMapElement(resultMapNode);
214+
try {
215+
resultMapElement(resultMapNode);
216+
} catch (IncompleteResultMapException e) {
217+
// ignore, it will be retried
218+
}
197219
}
198220
}
199221

@@ -229,7 +251,13 @@ private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> addi
229251
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
230252
}
231253
}
232-
return builderAssistant.addResultMap(id, typeClass, extend, discriminator, notNullColumn, resultMappings);
254+
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, notNullColumn, resultMappings);
255+
try {
256+
return resultMapResolver.resolve();
257+
} catch (IncompleteResultMapException e) {
258+
configuration.addIncompleteResultMap(resultMapResolver);
259+
throw e;
260+
}
233261
}
234262

235263
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {

src/main/java/org/apache/ibatis/session/Configuration.java

Lines changed: 55 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import org.apache.ibatis.binding.MapperRegistry;
1212
import org.apache.ibatis.builder.CacheRefResolver;
13+
import org.apache.ibatis.builder.ResultMapResolver;
1314
import org.apache.ibatis.builder.xml.XMLStatementBuilder;
1415
import org.apache.ibatis.cache.Cache;
1516
import org.apache.ibatis.cache.decorators.FifoCache;
@@ -76,23 +77,18 @@ public class Configuration {
7677
protected final InterceptorChain interceptorChain = new InterceptorChain();
7778
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
7879
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
79-
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<String, MappedStatement>(
80-
"Mapped Statements collection");
81-
protected final Map<String, Cache> caches = new StrictMap<String, Cache>(
82-
"Caches collection");
83-
protected final Map<String, ResultMap> resultMaps = new StrictMap<String, ResultMap>(
84-
"Result Maps collection");
85-
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<String, ParameterMap>(
86-
"Parameter Maps collection");
87-
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<String, KeyGenerator>(
88-
"Key Generators collection");
80+
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<String, MappedStatement>("Mapped Statements collection");
81+
protected final Map<String, Cache> caches = new StrictMap<String, Cache>("Caches collection");
82+
protected final Map<String, ResultMap> resultMaps = new StrictMap<String, ResultMap>("Result Maps collection");
83+
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<String, ParameterMap>("Parameter Maps collection");
84+
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<String, KeyGenerator>("Key Generators collection");
8985

9086
protected final Set<String> loadedResources = new HashSet<String>();
91-
protected final Map<String, XNode> sqlFragments = new StrictMap<String, XNode>(
92-
"XML fragments parsed from previous mappers");
87+
protected final Map<String, XNode> sqlFragments = new StrictMap<String, XNode>("XML fragments parsed from previous mappers");
9388

9489
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
9590
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
91+
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
9692
/**
9793
* A map holds cache-ref relationship. The key is the namespace that
9894
* references a cache bound to another namespace and the value is the
@@ -106,19 +102,13 @@ public Configuration(Environment environment) {
106102
}
107103

108104
public Configuration() {
109-
typeAliasRegistry.registerAlias("JDBC",
110-
JdbcTransactionFactory.class.getName());
111-
typeAliasRegistry.registerAlias("MANAGED",
112-
ManagedTransactionFactory.class.getName());
113-
typeAliasRegistry.registerAlias("JNDI",
114-
JndiDataSourceFactory.class.getName());
115-
typeAliasRegistry.registerAlias("POOLED",
116-
PooledDataSourceFactory.class.getName());
117-
typeAliasRegistry.registerAlias("UNPOOLED",
118-
UnpooledDataSourceFactory.class.getName());
119-
120-
typeAliasRegistry
121-
.registerAlias("PERPETUAL", PerpetualCache.class.getName());
105+
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class.getName());
106+
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class.getName());
107+
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class.getName());
108+
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class.getName());
109+
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class.getName());
110+
111+
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class.getName());
122112
typeAliasRegistry.registerAlias("FIFO", FifoCache.class.getName());
123113
typeAliasRegistry.registerAlias("LRU", LruCache.class.getName());
124114
typeAliasRegistry.registerAlias("SOFT", SoftCache.class.getName());
@@ -158,9 +148,7 @@ public void setLazyLoadingEnabled(boolean lazyLoadingEnabled) {
158148
try {
159149
Resources.classForName("net.sf.cglib.proxy.Enhancer");
160150
} catch (Throwable e) {
161-
throw new IllegalStateException(
162-
"Cannot enable lazy loading because CGLIB is not available. Add CGLIB to your classpath.",
163-
e);
151+
throw new IllegalStateException("Cannot enable lazy loading because CGLIB is not available. Add CGLIB to your classpath.", e);
164152
}
165153
}
166154
this.lazyLoadingEnabled = lazyLoadingEnabled;
@@ -258,35 +246,23 @@ public MetaObject newMetaObject(Object object) {
258246
return MetaObject.forObject(object, objectFactory, objectWrapperFactory);
259247
}
260248

261-
public ParameterHandler newParameterHandler(MappedStatement mappedStatement,
262-
Object parameterObject, BoundSql boundSql) {
263-
ParameterHandler parameterHandler = new DefaultParameterHandler(
264-
mappedStatement, parameterObject, boundSql);
265-
parameterHandler = (ParameterHandler) interceptorChain
266-
.pluginAll(parameterHandler);
249+
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
250+
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
251+
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
267252
return parameterHandler;
268253
}
269254

270-
public ResultSetHandler newResultSetHandler(Executor executor,
271-
MappedStatement mappedStatement, RowBounds rowBounds,
272-
ParameterHandler parameterHandler, ResultHandler resultHandler,
273-
BoundSql boundSql) {
274-
ResultSetHandler resultSetHandler = mappedStatement.hasNestedResultMaps() ? new NestedResultSetHandler(
275-
executor, mappedStatement, parameterHandler, resultHandler, boundSql,
276-
rowBounds) : new FastResultSetHandler(executor, mappedStatement,
277-
parameterHandler, resultHandler, boundSql, rowBounds);
278-
resultSetHandler = (ResultSetHandler) interceptorChain
279-
.pluginAll(resultSetHandler);
255+
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
256+
ResultHandler resultHandler, BoundSql boundSql) {
257+
ResultSetHandler resultSetHandler = mappedStatement.hasNestedResultMaps() ? new NestedResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql,
258+
rowBounds) : new FastResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
259+
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
280260
return resultSetHandler;
281261
}
282262

283-
public StatementHandler newStatementHandler(Executor executor,
284-
MappedStatement mappedStatement, Object parameterObject,
285-
RowBounds rowBounds, ResultHandler resultHandler) {
286-
StatementHandler statementHandler = new RoutingStatementHandler(executor,
287-
mappedStatement, parameterObject, rowBounds, resultHandler);
288-
statementHandler = (StatementHandler) interceptorChain
289-
.pluginAll(statementHandler);
263+
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) {
264+
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler);
265+
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
290266
return statementHandler;
291267
}
292268

@@ -407,13 +383,13 @@ public void addMappedStatement(MappedStatement ms, String databaseId) {
407383
if (previous == null || previous.getDatabaseId() == null) {
408384
mappedStatements.put(ms.getId(), ms);
409385
}
410-
}
386+
}
411387
} else {
412-
mappedStatements.put(ms.getId(), ms);
388+
mappedStatements.put(ms.getId(), ms);
413389
}
414390
}
415391
}
416-
392+
417393
public void addMappedStatement(MappedStatement ms) {
418394
addMappedStatement(ms, null);
419395
}
@@ -444,12 +420,19 @@ public void addIncompleteCacheRef(CacheRefResolver incompleteCacheRef) {
444420
incompleteCacheRefs.add(incompleteCacheRef);
445421
}
446422

423+
public Collection<ResultMapResolver> getIncompleteResultMaps() {
424+
return incompleteResultMaps;
425+
}
426+
427+
public void addIncompleteResultMap(ResultMapResolver resultMapResolver) {
428+
incompleteResultMaps.add(resultMapResolver);
429+
}
430+
447431
public MappedStatement getMappedStatement(String id) {
448432
return this.getMappedStatement(id, true);
449433
}
450434

451-
public MappedStatement getMappedStatement(String id,
452-
boolean validateIncompleteStatements) {
435+
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
453436
if (validateIncompleteStatements) {
454437
buildAllStatements();
455438
}
@@ -486,7 +469,7 @@ public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
486469
return mapperRegistry.getMapper(type, sqlSession);
487470
}
488471

489-
public boolean hasMapper(Class type) {
472+
public boolean hasMapper(Class<?> type) {
490473
return mapperRegistry.hasMapper(type);
491474
}
492475

@@ -505,6 +488,12 @@ public void addCacheRef(String namespace, String referencedNamespace) {
505488
* statement validation.
506489
*/
507490
protected void buildAllStatements() {
491+
if (!incompleteResultMaps.isEmpty()) {
492+
synchronized (incompleteResultMaps) {
493+
// This always throws a BuilderException.
494+
incompleteResultMaps.iterator().next().resolve();
495+
}
496+
}
508497
if (!incompleteCacheRefs.isEmpty()) {
509498
synchronized (incompleteCacheRefs) {
510499
// This always throws a BuilderException.
@@ -537,10 +526,8 @@ protected void checkGloballyForDiscriminatedNestedResultMaps(ResultMap rm) {
537526
Object value = entry.getValue();
538527
if (value instanceof ResultMap) {
539528
ResultMap entryResultMap = (ResultMap) value;
540-
if (!entryResultMap.hasNestedResultMaps()
541-
&& entryResultMap.getDiscriminator() != null) {
542-
Collection<String> discriminatedResultMapNames = entryResultMap
543-
.getDiscriminator().getDiscriminatorMap().values();
529+
if (!entryResultMap.hasNestedResultMaps() && entryResultMap.getDiscriminator() != null) {
530+
Collection<String> discriminatedResultMapNames = entryResultMap.getDiscriminator().getDiscriminatorMap().values();
544531
if (discriminatedResultMapNames.contains(rm.getId())) {
545532
entryResultMap.forceNestedResultMaps();
546533
}
@@ -553,12 +540,10 @@ protected void checkGloballyForDiscriminatedNestedResultMaps(ResultMap rm) {
553540
// Slow but a one time cost. A better solution is welcome.
554541
protected void checkLocallyForDiscriminatedNestedResultMaps(ResultMap rm) {
555542
if (!rm.hasNestedResultMaps() && rm.getDiscriminator() != null) {
556-
for (Map.Entry entry : rm.getDiscriminator().getDiscriminatorMap()
557-
.entrySet()) {
543+
for (Map.Entry entry : rm.getDiscriminator().getDiscriminatorMap().entrySet()) {
558544
String discriminatedResultMapName = (String) entry.getValue();
559545
if (hasResultMap(discriminatedResultMapName)) {
560-
ResultMap discriminatedResultMap = resultMaps
561-
.get(discriminatedResultMapName);
546+
ResultMap discriminatedResultMap = resultMaps.get(discriminatedResultMapName);
562547
if (discriminatedResultMap.hasNestedResultMaps()) {
563548
rm.forceNestedResultMaps();
564549
break;
@@ -568,8 +553,7 @@ protected void checkLocallyForDiscriminatedNestedResultMaps(ResultMap rm) {
568553
}
569554
}
570555

571-
protected static class StrictMap<J extends String, K extends Object> extends
572-
HashMap<J, K> {
556+
protected static class StrictMap<J extends String, K extends Object> extends HashMap<J, K> {
573557

574558
private String name;
575559

@@ -595,8 +579,7 @@ public StrictMap(String name, Map<? extends J, ? extends K> m) {
595579

596580
public K put(J key, K value) {
597581
if (containsKey(key))
598-
throw new IllegalArgumentException(name
599-
+ " already contains value for " + key);
582+
throw new IllegalArgumentException(name + " already contains value for " + key);
600583
if (key.contains(".")) {
601584
final String shortKey = getShortName(key);
602585
if (super.get(shortKey) == null) {
@@ -611,15 +594,11 @@ public K put(J key, K value) {
611594
public K get(Object key) {
612595
K value = super.get(key);
613596
if (value == null) {
614-
throw new IllegalArgumentException(name
615-
+ " does not contain value for " + key);
597+
throw new IllegalArgumentException(name + " does not contain value for " + key);
616598
}
617599
if (value instanceof Ambiguity) {
618-
throw new IllegalArgumentException(
619-
((Ambiguity) value).getSubject()
620-
+ " is ambiguous in "
621-
+ name
622-
+ " (try using the full name including the namespace, or rename one of the entries)");
600+
throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
601+
+ " (try using the full name including the namespace, or rename one of the entries)");
623602
}
624603
return value;
625604
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.apache.ibatis.submitted.xml_external_ref;
2+
3+
public class Dog extends Pet {
4+
5+
}

0 commit comments

Comments
 (0)