Skip to content

Commit

Permalink
Closes #98. Detect and pre-process static statements during startup so
Browse files Browse the repository at this point in the history
the execution becames much faster (x2). No need to use "raw" anymore
because now "raw" is automatically applied.
  • Loading branch information
emacarron committed Nov 17, 2013
1 parent 50e91d0 commit 5f9a168
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class RawLanguageDriver implements LanguageDriver {

Expand All @@ -32,16 +30,7 @@ public ParameterHandler createParameterHandler(MappedStatement mappedStatement,
}

public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
StringBuilder contents = new StringBuilder();
NodeList children = script.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = script.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
contents.append(data);
}
}
return new RawSqlSource(configuration, contents.toString(), parameterType);
return new RawSqlSource(configuration, script, parameterType);
}

public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,37 @@
import org.apache.ibatis.builder.SqlSourceBuilder;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.session.Configuration;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class RawSqlSource implements SqlSource {

private final SqlSource sqlSource;

public RawSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
this(configuration, getString(script), parameterType);
}

public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}

private static String getString(XNode script) {
StringBuilder contents = new StringBuilder();
NodeList children = script.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = script.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
contents.append(data);
}
}
return contents.toString();
}

public BoundSql getBoundSql(Object parameterObject) {
return sqlSource.getBoundSql(parameterObject);
Expand Down
29 changes: 27 additions & 2 deletions src/main/java/org/apache/ibatis/scripting/xmltags/TextSqlNode.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2009-2012 the original author or authors.
* Copyright 2009-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -25,12 +25,23 @@ public class TextSqlNode implements SqlNode {
public TextSqlNode(String text) {
this.text = text;
}

public boolean isDynamic() {
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);
parser.parse(text);
return checker.isDynamic();
}

public boolean apply(DynamicContext context) {
GenericTokenParser parser = new GenericTokenParser("${", "}", new BindingTokenParser(context));
GenericTokenParser parser = createParser(new BindingTokenParser(context));
context.appendSql(parser.parse(text));
return true;
}

private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}

private static class BindingTokenParser implements TokenHandler {

Expand All @@ -52,4 +63,18 @@ public String handleToken(String content) {
}
}

private static class DynamicCheckerTokenParser implements TokenHandler {

private boolean isDynamic;

public boolean isDynamic() {
return isDynamic;
}

public String handleToken(String content) {
this.isDynamic = true;
return null;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,14 @@
*/
package org.apache.ibatis.scripting.xmltags;

import java.util.ArrayList;
import java.util.List;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.scripting.defaults.RawSqlSource;
import org.apache.ibatis.session.Configuration;

public class XMLLanguageDriver implements LanguageDriver {
Expand All @@ -34,19 +32,21 @@ public ParameterHandler createParameterHandler(MappedStatement mappedStatement,
}

public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script);
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}

public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
if (script.startsWith("<script>")) { // issue #3
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script);
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
} else {
List<SqlNode> contents = new ArrayList<SqlNode>();
contents.add(new TextSqlNode(script.toString()));
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
return new DynamicSqlSource(configuration, rootSqlNode);
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,47 @@
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.scripting.defaults.RawSqlSource;
import org.apache.ibatis.session.Configuration;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class XMLScriptBuilder extends BaseBuilder {

private XNode context;
private boolean isDynamic;
private Class<?> parameterType;

public XMLScriptBuilder(Configuration configuration, XNode context) {
this(configuration, context, null);
}

public XMLScriptBuilder(Configuration configuration, String context) {
this(configuration, context, null);
}

public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
}

public XMLScriptBuilder(Configuration configuration, String context) {
public XMLScriptBuilder(Configuration configuration, String context, Class<?> parameterType) {
super(configuration);
XPathParser parser = new XPathParser(context, false, configuration.getVariables(), new XMLMapperEntityResolver());
this.context = parser.evalNode("/script");
this.parameterType = parameterType;
}

public SqlSource parseScriptNode() {
List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, context, parameterType);
}
return sqlSource;
}

Expand All @@ -61,13 +79,20 @@ private List<SqlNode> parseDynamicTags(XNode node) {
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE
|| child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
contents.add(new TextSqlNode(data));
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE && !"selectKey".equals(nodeName)) { // issue #628
NodeHandler handler = nodeHandlers.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return contents;
Expand Down
48 changes: 19 additions & 29 deletions src/site/es/xdoc/dynamic-sql.xml
Original file line number Diff line number Diff line change
Expand Up @@ -187,47 +187,37 @@ AND title like ‘someTitle’]]></source>
<p>Desde la versión 3.2 MyBatis soporta la adición de lenguajes de scripting de forma que puedes
añadir un driver de lenguaje y usar dicho lenguaje para escribir tus sentencias SQL.
</p>
<p>Hay dos lenguajes predefinidos:</p>
<table>
<thead>
<tr><td>Alias</td><td>Driver</td>
</tr>
</thead>
<tbody>
<tr><td>xml</td><td>XmlLanguageDriver</td></tr>
<tr><td>raw</td><td>RawLanguageDriver</td></tr>
</tbody>
</table>
<p>El lenguaje <code>xml</code> es el lenguaje por defecto. Permite ejecutar todos los tags dinámicos que hemos visto en las secciones anteriores.</p>
<p>El lenguaje <code>raw</code> es en realidad la ausencia de lenguaje. Con esta configuración MyBatis sólo realiza la sustitución de parámetros
y pasa la sentencia al driver de base de datos. Como supondrás el lenguaje <code>raw</code> es más rápido que el <code>xml</code>.
</p>
<p>Puedes indicar le lenguaje por defecto para tu proyecto configurandolo en el fichero mybatis-config.xml:</p>
<source><![CDATA[<settings>
<setting name="defaultScriptingLanguage" value="raw"/>
<p>Puedes añadir un lenguaje implementando el siguiente interfaz:</p>
<source><![CDATA[public interface LanguageDriver {
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}]]></source>
<p>Una vez tienes tu driver de lenguaje puedes puede hacer que sea el de uso por defecto estableciéndolo en el fichero mybatis-config.xml:</p>
<source><![CDATA[<typeAliases>
<typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
<setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>
]]></source>
<p>Puedes indicar el lenguaje que quieres usar en un statement específico añadiendo el atributo <code>lang</code> de la siguiente forma:
]]></source>
<p>En lugar de cambiar el valor por defecto, puedes indicar el lenguaje para un statement específico
añadiendo el atributo <code>lang</code> de la siguiente forma:
</p>
<source><![CDATA[<select id="selectBlog" lang="raw">
<source><![CDATA[<select id="selectBlog" lang="myLanguage">
SELECT * FROM BLOG
</select>]]></source>
<p>O, en el caso de que uses mappers, usando la anotación <code>@Lang</code>:</p>
<source><![CDATA[public interface Mapper {
@Lang(RawLanguageDriver.class)
@Lang(MyLanguageDriver.class)
@Select("SELECT * FROM BLOG")
List<Blog> selectBlog();
}]]></source>

<p><span class="label important">NOTA</span> Puedes utilizar Apache Velocity como lenguaje dinámico. Echa un vistazo al proyecto MyBatis-Velocity para conocer los detalles.</p>

<p>También puedes implementar tu propio lenguaje implementado el siguiente interfaz:</p>
<source><![CDATA[public interface LanguageDriver {
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}]]></source>
<p></p>
<p>Todos los tags que has visto en las secciones previas se proporcionan por el lenguaje por defecto de MyBatis cuyo driver es
<code>org.apache.ibatis.scripting.xmltags.XmlLanguageDriver</code> que tiene un alias llamado <code>xml</code>.</p>
</subsection>
</section>
</body>
Expand Down
45 changes: 17 additions & 28 deletions src/site/xdoc/dynamic-sql.xml
Original file line number Diff line number Diff line change
Expand Up @@ -186,47 +186,36 @@ AND title like ‘someTitle’]]></source>
<p>Starting from version 3.2 MyBatis supports pluggable scripting languages,
so you can plug a language driver and use that language to write your dynamic
SQL queries.</p>
<p>There are two built-in languages:</p>
<table>
<thead>
<tr><td>Alias</td><td>Driver</td>
</tr>
</thead>
<tbody>
<tr><td>xml</td><td>XmlLanguageDriver</td></tr>
<tr><td>raw</td><td>RawLanguageDriver</td></tr>
</tbody>
</table>
<p>The <code>xml</code> language is the default one. It is able to execute all the dynamic tags we saw in the previous sections.</p>
<p>The <code>raw</code> language is in fact the absence of language. When using this setting MyBatis just performs the
parameter substitution and passes the statement to the database driver. As you may guess, the <code>raw</code> language
is much faster than the <code>xml</code> language.
</p>
<p>You can change the default language for your project by configuring it in the mybatis-config.xml file:</p>
<source><![CDATA[<settings>
<setting name="defaultScriptingLanguage" value="raw"/>
<p>You can plug a language by implementing the following interface:</p>
<source><![CDATA[public interface LanguageDriver {
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}]]></source>
<p>Once you have your custom language driver you can set it to be the default by configuring it in the mybatis-config.xml file:</p>
<source><![CDATA[<typeAliases>
<typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<settings>
<setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>
]]></source>
<p>You can also specify the language you want to use in an specific statement by adding the <code>lang</code> attribute as follows:
<p>Instead of changing the default, you can specify the language for an specific statement by adding the <code>lang</code> attribute as follows:
</p>
<source><![CDATA[<select id="selectBlog" lang="raw">
<source><![CDATA[<select id="selectBlog" lang="myLanguage">
SELECT * FROM BLOG
</select>]]></source>
<p>Or, in the case you are using mappers, using the <code>@Lang</code> annotation:</p>
<source><![CDATA[public interface Mapper {
@Lang(RawLanguageDriver.class)
@Lang(MyLanguageDriver.class)
@Select("SELECT * FROM BLOG")
List<Blog> selectBlog();
}]]></source>

<p><span class="label important">NOTE</span> You can use Apache Velocity as your dynamic language. Have a look at the MyBatis-Velocity project for the details.</p>

<p>You can also implement your own language driver by implementing the following interface:</p>
<source><![CDATA[public interface LanguageDriver {
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}]]></source>
<p>All the xml tags you have seen in the previous sections are provided by the default MyBatis language that is provided by the driver
<code>org.apache.ibatis.scripting.xmltags.XmlLanguageDriver</code> which is aliased as <code>xml</code>.</p>
</subsection>
</section>
</body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@
FROM Person
WHERE id = #{id,jdbcType=INTEGER}
</select>
<select id="getParentWithoutComplex" resultMap="personMap" parameterType="Person">
<!-- parameterType was removed because it failed when using RAW.
the DynamicSqlSource calculates the parameter out of the actual parameter used in the call
the RawSqlSource calculates it during startup.
This paremter is a Person when called directly but an Integer when calling from the ResultMap (nested select)
so it fails -->
<select id="getParentWithoutComplex" resultMap="personMap">
SELECT id, firstName, lastName, parent_id, parent_firstName, parent_lastName
FROM Person
WHERE id = #{id,jdbcType=INTEGER}
Expand Down

0 comments on commit 5f9a168

Please sign in to comment.