Skip to content

Commit d8f03c5

Browse files
committed
Merge pull request mybatis#331 from kmoco2am/master
Parameterized include (https://code.google.com/p/mybatis/issues/detail?id=652)
2 parents 100171d + 4df0298 commit d8f03c5

File tree

7 files changed

+185
-23
lines changed

7 files changed

+185
-23
lines changed

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

100644100755
Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.apache.ibatis.builder.xml;
1717

18+
import org.apache.ibatis.builder.BuilderException;
1819
import org.apache.ibatis.builder.IncompleteElementException;
1920
import org.apache.ibatis.builder.MapperBuilderAssistant;
2021
import org.apache.ibatis.parsing.PropertyParser;
@@ -23,6 +24,8 @@
2324
import org.w3c.dom.Node;
2425
import org.w3c.dom.NodeList;
2526

27+
import java.util.Properties;
28+
2629
/**
2730
* @author Frank D. Martinez [mnesarco]
2831
*/
@@ -37,9 +40,39 @@ public XMLIncludeTransformer(Configuration configuration, MapperBuilderAssistant
3740
}
3841

3942
public void applyIncludes(Node source) {
43+
Properties variablesContext = new Properties();
44+
Properties configurationVariables = configuration.getVariables();
45+
if (configurationVariables != null) {
46+
variablesContext.putAll(configurationVariables);
47+
}
48+
applyIncludes(source, variablesContext);
49+
}
50+
51+
/**
52+
* Recursively apply includes through all SQL fragments.
53+
* @param source Include node in DOM tree
54+
* @param variablesContext Current context for static variables with values
55+
*/
56+
private void applyIncludes(Node source, final Properties variablesContext) {
4057
if (source.getNodeName().equals("include")) {
41-
Node toInclude = findSqlFragment(getStringAttribute(source, "refid"));
42-
applyIncludes(toInclude);
58+
// new full context for included SQL - contains inherited context and new variables from current include node
59+
Properties fullContext;
60+
61+
String refid = getStringAttribute(source, "refid");
62+
// replace variables in include refid value
63+
refid = PropertyParser.parse(refid, variablesContext);
64+
Node toInclude = findSqlFragment(refid);
65+
Properties newVariablesContext = getVariablesContext(source, variablesContext);
66+
if (!newVariablesContext.isEmpty()) {
67+
// merge contexts
68+
fullContext = new Properties();
69+
fullContext.putAll(variablesContext);
70+
fullContext.putAll(newVariablesContext);
71+
} else {
72+
// no new context - use inherited fully
73+
fullContext = variablesContext;
74+
}
75+
applyIncludes(toInclude, fullContext);
4376
if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
4477
toInclude = source.getOwnerDocument().importNode(toInclude, true);
4578
}
@@ -51,13 +84,18 @@ public void applyIncludes(Node source) {
5184
} else if (source.getNodeType() == Node.ELEMENT_NODE) {
5285
NodeList children = source.getChildNodes();
5386
for (int i=0; i<children.getLength(); i++) {
54-
applyIncludes(children.item(i));
87+
applyIncludes(children.item(i), variablesContext);
5588
}
89+
} else if (source.getNodeType() == Node.ATTRIBUTE_NODE && !variablesContext.isEmpty()) {
90+
// replace variables in all attribute values
91+
source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
92+
} else if (source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) {
93+
// replace variables ins all text nodes
94+
source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
5695
}
5796
}
5897

5998
private Node findSqlFragment(String refid) {
60-
refid = PropertyParser.parse(refid, configuration.getVariables());
6199
refid = builderAssistant.applyCurrentNamespace(refid, true);
62100
try {
63101
XNode nodeToInclude = configuration.getSqlFragments().get(refid);
@@ -70,4 +108,31 @@ private Node findSqlFragment(String refid) {
70108
private String getStringAttribute(Node node, String name) {
71109
return node.getAttributes().getNamedItem(name).getNodeValue();
72110
}
111+
112+
/**
113+
* Read placholders and their values from include node definition.
114+
* @param node Include node instance
115+
* @param inheritedVariablesContext Current context used for replace variables in new variables values
116+
* @return variables context from include instance (no inherited values)
117+
*/
118+
private Properties getVariablesContext(Node node, Properties inheritedVariablesContext) {
119+
Properties variablesContext = new Properties();
120+
NodeList children = node.getChildNodes();
121+
for (int i = 0; i < children.getLength(); i++) {
122+
Node n = children.item(i);
123+
if (n.getNodeType() == Node.ELEMENT_NODE) {
124+
String name = getStringAttribute(n, "name");
125+
String value = getStringAttribute(n, "value");
126+
// Replace variables inside
127+
value = PropertyParser.parse(value, inheritedVariablesContext);
128+
// Push new value
129+
Object originalValue = variablesContext.put(name, value);
130+
if (originalValue != null) {
131+
throw new BuilderException("Variable " + name + " defined twice in the same include definition");
132+
}
133+
}
134+
}
135+
return variablesContext;
136+
}
137+
73138
}

src/main/java/org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd

100644100755
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ lang CDATA #IMPLIED
238238

239239
<!-- Dynamic -->
240240

241-
<!ELEMENT include EMPTY>
241+
<!ELEMENT include (property+)?>
242242
<!ATTLIST include
243243
refid CDATA #REQUIRED
244244
>

src/site/xdoc/sqlmap-xml.xml

100644100755
Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -485,19 +485,44 @@ ps.setInt(1,id);]]></source>
485485
<subsection name="sql">
486486
<p>
487487
This element can be used to define a reusable fragment of SQL code that can be
488-
included in other statements. For example:
488+
included in other statements. It can be statically (during load phase) parametrized. Different property values can
489+
vary in include instances. For example:
489490
</p>
490491

491-
<source><![CDATA[<sql id="userColumns"> id,username,password </sql>]]></source>
492+
<source><![CDATA[<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>]]></source>
492493

493494
<p>
494495
The SQL fragment can then be included in another statement, for example:
495496
</p>
496497

497498
<source><![CDATA[<select id="selectUsers" resultType="map">
498-
select <include refid="userColumns"/>
499-
from some_table
500-
where id = #{id}
499+
select
500+
<include refid="userColumns"><property name="alias" value="t1"/></include>,
501+
<include refid="userColumns"><property name="alias" value="t2"/></include>
502+
from some_table t1
503+
cross join some_table t2
504+
</select>]]></source>
505+
506+
<p>
507+
Property value can be also used in include refid attribute or property values inside include clause, for example:
508+
</p>
509+
510+
<source><![CDATA[<sql id="sometable">
511+
${prefix}Table
512+
</sql>
513+
514+
<sql id="someinclude">
515+
from
516+
<include refid="${include_target}"/>
517+
</sql>
518+
519+
<select id="select" resultType="map">
520+
select
521+
field1, field2, field3
522+
<include refid="someinclude">
523+
<property name="prefix" value="Some"/>
524+
<property name="include_target" value="sometable"/>
525+
</include>
501526
</select>]]></source>
502527
</subsection>
503528

src/test/java/org/apache/ibatis/builder/PostMapper.xml

100644100755
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
</select>
8686

8787
<sql id="byBlogId">
88-
<if test="blog_id != null">blog_id = #{blog_id}</if>
88+
<if test="blog_id != null">${prefix}_id = #{blog_id}</if>
8989
</sql>
9090

9191
<select id="findPost" resultType="org.apache.ibatis.domain.blog.Post">
@@ -102,7 +102,9 @@
102102
</foreach>
103103
</if>
104104
<trim prefix="AND">
105-
<include refid="byBlogId"/>
105+
<include refid="byBlogId">
106+
<property name="prefix" value="blog"/>
107+
</include>
106108
</trim>
107109
</otherwise>
108110
</choose>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--
2+
-- Copyright 2009-2015 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+
-- http://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+
17+
drop table SomeTable if exists;
18+
19+
create table SomeTable (
20+
id int,
21+
field1 varchar(20),
22+
field2 varchar(20),
23+
field3 varchar(20)
24+
);
25+
26+
insert into SomeTable (id, field1, field2, field3) values(1, 'a', 'b', 'c');

src/test/java/org/apache/ibatis/submitted/includes/IncludeTest.java

100644100755
Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,63 @@
1616
package org.apache.ibatis.submitted.includes;
1717

1818
import org.apache.ibatis.io.Resources;
19+
import org.apache.ibatis.jdbc.ScriptRunner;
1920
import org.apache.ibatis.session.SqlSessionFactory;
2021
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
2122
import static org.junit.Assert.assertNotNull;
23+
24+
import org.junit.BeforeClass;
2225
import org.junit.Test;
2326

2427
import java.io.Reader;
28+
import java.sql.Connection;
29+
import java.util.Map;
30+
2531
import org.apache.ibatis.session.SqlSession;
2632
import org.junit.Assert;
2733

2834
public class IncludeTest {
2935

36+
private static SqlSessionFactory sqlSessionFactory;
37+
38+
@BeforeClass
39+
public static void setUp() throws Exception {
40+
// create a SqlSessionFactory
41+
Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/includes/MapperConfig.xml");
42+
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
43+
reader.close();
44+
45+
// populate in-memory database
46+
SqlSession session = sqlSessionFactory.openSession();
47+
Connection conn = session.getConnection();
48+
reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/includes/CreateDB.sql");
49+
ScriptRunner runner = new ScriptRunner(conn);
50+
runner.setLogWriter(null);
51+
runner.runScript(reader);
52+
reader.close();
53+
session.close();
54+
}
55+
3056
@Test
3157
public void testIncludes() throws Exception {
32-
String resource = "org/apache/ibatis/submitted/includes/MapperConfig.xml";
33-
Reader reader = Resources.getResourceAsReader(resource);
34-
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
35-
SqlSessionFactory sqlMapper = builder.build(reader);
36-
assertNotNull(sqlMapper);
37-
38-
final SqlSession sqlSession = sqlMapper.openSession();
58+
final SqlSession sqlSession = sqlSessionFactory.openSession();
3959
try {
4060
final Integer result = sqlSession.selectOne("org.apache.ibatis.submitted.includes.mapper.selectWithProperty");
4161
Assert.assertEquals(Integer.valueOf(1), result);
4262
} finally {
4363
sqlSession.close();
4464
}
4565
}
66+
67+
@Test
68+
public void testParametrizedIncludes() throws Exception {
69+
final SqlSession sqlSession = sqlSessionFactory.openSession();
70+
try {
71+
final Map<String, Object> result = sqlSession.selectOne("org.apache.ibatis.submitted.includes.mapper.select");
72+
//Assert.assertEquals(Integer.valueOf(1), result);
73+
} finally {
74+
sqlSession.close();
75+
}
76+
}
77+
4678
}

src/test/java/org/apache/ibatis/submitted/includes/Mapper.xml

100644100755
Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,23 @@
2323

2424
<mapper namespace="org.apache.ibatis.submitted.includes.mapper">
2525
<sql id="sometable">
26-
SomeTable
26+
${prefix}Table
27+
</sql>
28+
29+
<sql id="someinclude">
30+
<include refid="${include_target}"/>
2731
</sql>
2832

2933
<select id="select" resultType="map">
30-
<include refid="org.apache.ibatis.submitted.includes.fragments.select"/>
34+
<include refid="someinclude">
35+
<property name="include_target" value="org.apache.ibatis.submitted.includes.fragments.select"/>
36+
</include>
3137
field1, field2, field3
32-
from<include refid="sometable"/>
38+
from
39+
<include refid="someinclude">
40+
<property name="prefix" value="Some"/>
41+
<property name="include_target" value="sometable"/>
42+
</include>
3343
</select>
3444

3545
<select id="selectWithProperty" resultType="_int">
@@ -38,7 +48,9 @@
3848

3949
<update id="update" parameterType="map">
4050
<include refid="org.apache.ibatis.submitted.includes.fragments.update"/>
41-
<include refid="org.apache.ibatis.submitted.includes.mapper.sometable"/>
51+
<include refid="org.apache.ibatis.submitted.includes.mapper.sometable">
52+
<property name="prefix" value="Some"/>
53+
</include>
4254
set Field2 = #{field2,jdbcType=INTEGER},
4355
Field3 = #{field3,jdbcType=VARCHAR},
4456
where field1 = #{field1,jdbcType=INTEGER}

0 commit comments

Comments
 (0)