Skip to content

Commit 44225b2

Browse files
SQL Fragments can be included from XML files that were not parsed yet.
1 parent 70a4cd0 commit 44225b2

33 files changed

+1133
-13
lines changed

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

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ private void configurationElement(XNode context) {
6666
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
6767
resultMapElements(context.evalNodes("/mapper/resultMap"));
6868
sqlElement(context.evalNodes("/mapper/sql"));
69-
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
69+
bufferStatementNodes(context.evalNodes("select|insert|update|delete"));
7070
} catch (Exception e) {
7171
throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
7272
}
@@ -75,7 +75,7 @@ private void configurationElement(XNode context) {
7575

7676
private void cacheRefElement(XNode context) {
7777
if (context != null) {
78-
builderAssistant.useCacheRef(context.getStringAttribute("namespace"));
78+
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
7979
}
8080
}
8181

@@ -198,11 +198,17 @@ private void sqlElement(List<XNode> list) throws Exception {
198198
}
199199
}
200200

201-
private void buildStatementFromContext(List<XNode> list) {
202-
for (XNode context : list) {
203-
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, this);
204-
statementParser.parseStatementNode(context);
205-
}
201+
/**
202+
* To achieve mapper-order-independent parsing, stores the statement nodes
203+
* into the temporary map without actually parsing them.
204+
*
205+
* @param list
206+
* @see Configuration#getMappedStatement(String)
207+
* @see Configuration#buildAllStatements()
208+
*/
209+
private void bufferStatementNodes(List<XNode> list) {
210+
String currentNamespace = builderAssistant.getCurrentNamespace();
211+
configuration.addStatementNodes(currentNamespace, list);
206212
}
207213

208214
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, ArrayList<ResultFlag> flags) throws Exception {

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,10 @@
1919
public class XMLStatementBuilder extends BaseBuilder {
2020

2121
private MapperBuilderAssistant builderAssistant;
22-
private XMLMapperBuilder xmlMapperParser;
2322

24-
public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XMLMapperBuilder xmlMapperParser) {
23+
public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant) {
2524
super(configuration);
2625
this.builderAssistant = builderAssistant;
27-
this.xmlMapperParser = xmlMapperParser;
2826
}
2927

3028
public void parseStatementNode(XNode context) {
@@ -154,10 +152,10 @@ private class IncludeNodeHandler implements NodeHandler {
154152
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
155153
String refid = nodeToHandle.getStringAttribute("refid");
156154
refid = builderAssistant.applyCurrentNamespace(refid);
157-
XNode includeNode = xmlMapperParser.getSqlFragment(refid);
155+
XNode includeNode = configuration.getSqlFragments().get(refid);
158156
if (includeNode == null) {
159157
String nsrefid = builderAssistant.applyCurrentNamespace(refid);
160-
includeNode = xmlMapperParser.getSqlFragment(nsrefid);
158+
includeNode = configuration.getSqlFragments().get(nsrefid);
161159
if (includeNode == null) {
162160
throw new BuilderException("Could not find SQL statement to include with refid '" + refid + "'");
163161
}

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

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.apache.ibatis.session;
22

33
import org.apache.ibatis.binding.MapperRegistry;
4+
import org.apache.ibatis.builder.MapperBuilderAssistant;
5+
import org.apache.ibatis.builder.xml.XMLStatementBuilder;
46
import org.apache.ibatis.cache.Cache;
57
import org.apache.ibatis.cache.decorators.FifoCache;
68
import org.apache.ibatis.cache.decorators.LruCache;
@@ -36,6 +38,8 @@
3638
import org.apache.ibatis.type.TypeHandlerRegistry;
3739

3840
import java.util.*;
41+
import java.util.concurrent.ConcurrentMap;
42+
import java.util.concurrent.ConcurrentHashMap;
3943

4044
public class Configuration {
4145

@@ -68,6 +72,15 @@ public class Configuration {
6872
protected final Set<String> loadedResources = new HashSet<String>();
6973
protected final Map<String, XNode> sqlFragments = new StrictMap<String, XNode>("XML fragments parsed from previous mappers");
7074

75+
/** A map holds statement nodes for a namespace. */
76+
protected final ConcurrentMap<String, List<XNode>> statementNodesToParse = new ConcurrentHashMap<String, List<XNode>>();
77+
/**
78+
* A map holds cache-ref relationship. The key is the namespace that
79+
* references a cache bound to another namespace and the value is the
80+
* namespace which the actual cache is bound to.
81+
*/
82+
protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
83+
7184
public Configuration(Environment environment) {
7285
this();
7386
this.environment = environment;
@@ -340,14 +353,19 @@ public void addMappedStatement(MappedStatement ms) {
340353
}
341354

342355
public Collection<String> getMappedStatementNames() {
356+
buildAllStatements();
343357
return mappedStatements.keySet();
344358
}
345359

346360
public Collection<MappedStatement> getMappedStatements() {
361+
buildAllStatements();
347362
return mappedStatements.values();
348363
}
349364

350365
public MappedStatement getMappedStatement(String id) {
366+
if (!mappedStatements.containsKey(id)) {
367+
buildStatementsFromId(id);
368+
}
351369
return mappedStatements.get(id);
352370
}
353371

@@ -386,9 +404,82 @@ public boolean hasMapper(Class type) {
386404
}
387405

388406
public boolean hasStatement(String statementName) {
407+
buildStatementsFromId(statementName);
389408
return mappedStatements.containsKey(statementName);
390409
}
391410

411+
public void addStatementNodes(String namespace, List<XNode> nodes) {
412+
statementNodesToParse.put(namespace, nodes);
413+
}
414+
415+
public void addCacheRef(String namespace, String referencedNamespace) {
416+
cacheRefMap.put(namespace, referencedNamespace);
417+
}
418+
419+
/**
420+
* Parses all the unprocessed statement nodes in the cache. It is recommended
421+
* to call this method once all the mappers are added as it provides fail-fast
422+
* statement validation.
423+
*/
424+
public void buildAllStatements() {
425+
if (!statementNodesToParse.isEmpty()) {
426+
Set<String> keySet = statementNodesToParse.keySet();
427+
for (String namespace : keySet) {
428+
buildStatementsForNamespace(namespace);
429+
}
430+
}
431+
}
432+
433+
protected void buildStatementsFromId(String id) {
434+
final String namespace = extractNamespace(id);
435+
buildStatementsForNamespace(namespace);
436+
}
437+
438+
/**
439+
* Extracts namespace from fully qualified statement id.
440+
*
441+
* @param statementId
442+
* @return namespace or null when id does not contain period.
443+
*/
444+
protected String extractNamespace(String statementId) {
445+
int lastPeriod = statementId.lastIndexOf('.');
446+
return lastPeriod > 0 ? statementId.substring(0, lastPeriod) : null;
447+
}
448+
449+
/**
450+
* Parses cached statement nodes for the specified namespace and stores the
451+
* generated mapped statements.
452+
*
453+
* @param namespace
454+
*/
455+
protected void buildStatementsForNamespace(String namespace) {
456+
if (namespace != null) {
457+
final List<XNode> list = statementNodesToParse.get(namespace);
458+
if (list != null) {
459+
final String resource = namespace.replace('.', '/') + ".xml";
460+
final MapperBuilderAssistant builderAssistant = new MapperBuilderAssistant(this, resource);
461+
builderAssistant.setCurrentNamespace(namespace);
462+
// Set a cache reference if one is bound to this namespace.
463+
if (caches.containsKey(namespace)) {
464+
builderAssistant.useCacheRef(namespace);
465+
}
466+
else if(cacheRefMap.containsKey(namespace)) {
467+
builderAssistant.useCacheRef(cacheRefMap.get(namespace));
468+
}
469+
parseStatementNodes(builderAssistant, list);
470+
// Remove the processed nodes and resource from the cache.
471+
statementNodesToParse.remove(namespace);
472+
}
473+
}
474+
}
475+
476+
protected void parseStatementNodes(final MapperBuilderAssistant builderAssistant, final List<XNode> list) {
477+
for (XNode context : list) {
478+
final XMLStatementBuilder statementParser = new XMLStatementBuilder(this, builderAssistant);
479+
statementParser.parseStatementNode(context);
480+
}
481+
}
482+
392483
//Slow but a one time cost. A better solution is welcome.
393484
protected void checkGloballyForDiscriminatedNestedResultMaps(ResultMap rm) {
394485
if (rm.hasNestedResultMaps()) {

src/test/java/org/apache/ibatis/submitted/parent_child_circular/NodeMapper.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
55
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
66

7-
<mapper namespace="testcase">
7+
<mapper namespace="org.apache.ibatis.submitted.parent_child_circular.NodeMapper">
88

99
<resultMap id="nodeResult" type="org.apache.ibatis.submitted.parent_child_circular.Node">
1010
<id property="id" column="id"/>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
drop table if exists person;
2+
create table person (person_id int, person_name varchar(32));
3+
4+
drop table if exists pet;
5+
create table pet (pet_id int, owner_id int, pet_name varchar(32));
6+
7+
insert into person (person_id, person_name) values (1, 'John');
8+
insert into person (person_id, person_name) values (2, 'Rebecca');
9+
10+
insert into pet (pet_id, owner_id, pet_name) values (1, 1, 'Kotetsu');
11+
insert into pet (pet_id, owner_id, pet_name) values (2, 1, 'Chien');
12+
insert into pet (pet_id, owner_id, pet_name) values (3, 2, 'Ren');
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.apache.ibatis.submitted.xml_external_ref;
2+
3+
import java.util.Map;
4+
5+
public interface InvalidMapper {
6+
Map selectAll();
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
3+
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
4+
<mapper namespace="org.apache.ibatis.submitted.xml_external_ref.InvalidMapper">
5+
<sql id="personColumnList">
6+
person_id, person_name
7+
</sql>
8+
<select id="selectAll" resultType="java.util.Map">
9+
SELECT
10+
<include refid="wrongId" />
11+
FROM person
12+
</select>
13+
</mapper>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
3+
<!DOCTYPE configuration
4+
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
5+
"http://mybatis.org/dtd/mybatis-3-config.dtd">
6+
7+
<configuration>
8+
9+
<environments default="development">
10+
<environment id="development">
11+
<transactionManager type="JDBC">
12+
<property name="" value=""/>
13+
</transactionManager>
14+
<dataSource type="UNPOOLED">
15+
<property name="driver" value="org.hsqldb.jdbcDriver"/>
16+
<property name="url" value="jdbc:hsqldb:mem:xmlextref"/>
17+
<property name="username" value="sa"/>
18+
</dataSource>
19+
</environment>
20+
</environments>
21+
22+
<mappers>
23+
<mapper resource="org/apache/ibatis/submitted/xml_external_ref/PersonMapper.xml"/>
24+
<mapper resource="org/apache/ibatis/submitted/xml_external_ref/PetMapper.xml"/>
25+
</mappers>
26+
27+
</configuration>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
3+
<!DOCTYPE configuration
4+
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
5+
"http://mybatis.org/dtd/mybatis-3-config.dtd">
6+
7+
<configuration>
8+
9+
<environments default="development">
10+
<environment id="development">
11+
<transactionManager type="JDBC">
12+
<property name="" value=""/>
13+
</transactionManager>
14+
<dataSource type="UNPOOLED">
15+
<property name="driver" value="org.hsqldb.jdbcDriver"/>
16+
<property name="url" value="jdbc:hsqldb:mem:xmlextref"/>
17+
<property name="username" value="sa"/>
18+
</dataSource>
19+
</environment>
20+
</environments>
21+
22+
<mappers>
23+
<mapper resource="org/apache/ibatis/submitted/xml_external_ref/MultipleCrossIncludePersonMapper.xml"/>
24+
<mapper resource="org/apache/ibatis/submitted/xml_external_ref/MultipleCrossIncludePetMapper.xml"/>
25+
</mappers>
26+
27+
</configuration>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.apache.ibatis.submitted.xml_external_ref;
2+
3+
public interface MultipleCrossIncludePersonMapper {
4+
Person select(Integer id);
5+
6+
Pet selectPet(Integer id);
7+
}

0 commit comments

Comments
 (0)