1. Overview
1.1. What is MyBatis Thymeleaf ?
The mybatis-thymeleaf is a plugin that helps applying the 2-way SQL/dynamic SQL feature to the MyBatis 3 using the template mechanism provided by Thymeleaf 3. If you are not familiar with MyBatis and Thymeleaf, you can see following official documentations.
1.2. What is 2-way SQL?
The 2-way SQL can be used by following two way.
-
It can be executed as-is in SQL execution tool (such as psql, mysql, sqlplus, plugins for IDE, etc…)
-
It can be used as a SQL template for creating a bindable and dynamically SQL that can be parsed by MyBatis core module
1.2.1. Simple bindable 2-way SQL
The mybatis-thymeleaf support a simple bindable 2-way SQL as follow:
SELECT * FROM names
WHERE id = /*[# mb:p="id"]*/ 1 /*[/]*/
1.2.2. Dynamically bindable 2-way SQL
The mybatis-thymeleaf support a dynamically bindable 2-way SQL as follow:
SELECT * FROM names
WHERE 1 = 1
/*[# th:if="${not #lists.isEmpty(ids)}"]*/
AND id IN (/*[# mb:p="ids"]*/ 1 /*[/]*/)
/*[/]*/
ORDER BY id
1.2.3. Dynamically bindable SQL
The mybatis-thymeleaf support a dynamically bindable SQL(non 2-way SQL) as follow:
SELECT * FROM names
WHERE 1 = 1
[# th:if="${not #lists.isEmpty(ids)}"]
AND id IN ([# mb:p="ids" /])
[/]
ORDER BY id
1.3. Mainly Features
The mybatis-thymeleaf provide following features using class that implements
the LanguageDriver
interface
for integrating with template engine provide by Thymeleaf.
-
Can write 2-way SQL/dynamic SQL
-
Can use a 2-way SQL/dynamic SQL via an annotation and mapper xml
-
Can read an SQL template from a Thymeleaf template file on classpath
-
Can use a custom dialect(attribute tag and expression utility method) on your SQL template
-
Can fully customize a template engine configuration
-
Can generate the SQL from SQL template without the MyBatis core module (since 1.0.2)
2. Requirements
The mybatis-thymeleaf supports using on following Java and artifacts version.
2.2. MyBatis
-
MyBatis 3.4.3+
As basically policy, we do test using following versions.
|
2.3. Thymeleaf
-
Thymeleaf 3.0+
As basically policy, we do test using following versions.
|
3. Getting Started
In this chapter, we explain basic usage of the mybatis-thymeleaf.
3.1. Installation
3.1.1. Maven
If you are using the Maven as build tool, you can add as follow:
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.16</version> <!-- Adjust to your application -->
</dependency>
<dependency>
<groupId>org.mybatis.scripting</groupId>
<artifactId>mybatis-thymeleaf</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>
If you use a snapshot version, you need add the configuration for using the "Sonatype OSS Snapshots Repository" as follow:
<repositories>
<repository>
<id>sonatype-oss-snapshots</id>
<name>Sonatype OSS Snapshots Repository</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
</repositories>
3.1.2. Gradle
If you are using the Gradle as build tool, you can add as follow:
dependencies {
compile("org.mybatis:mybatis:3.5.16") // Adjust version to your application
compile("org.mybatis.scripting:mybatis-thymeleaf:1.1.0")
}
If you use a snapshot version, you need add the configuration for using the "Sonatype OSS Snapshots Repository" as follow:
repositories {
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
}
3.2. Configuration
Set the ThymeleafLanguageDriver
as your default scripting language to MyBatis.
Configuration configuration = new Configuration();
configuration.setDefaultScriptingLanguage(ThymeleafLanguageDriver.class);
<settings>
<setting name="defaultScriptingLanguage"
value="org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriver"/>
</settings>
3.3. Creating Mapper
The MyBatis provides two ways(annotation driven and XML driven) for creating a Mapper.
3.3.1. Annotation driven mapper
If you use the annotation driven mapper, you can specify an SQL as follow:
public class NameMapper {
@Select("SELECT * FROM names WHERE id = /*[# mb:p='id']*/ 1 /*[/]*/")
Name findById(Integer id);
}
You can specify a readable SQL on annotation using "Multiline String Literals" provided by some JVM language. About "Multiline String Literals", please see the Section 11.3, “Multiline String Literals”. |
Also, you can specify an SQL on template file(e.g. NameMapper/findById.sql
on class path) as follow:
@Select("NameMapper/findById.sql")
Name findById(Integer id);
By default behavior, the mybatis-thymeleaf read a specified template file from just under classpath.
SELECT * FROM names
WHERE id = /*[# mb:p="id"]*/ 1 /*[/]*/
Since 1.0.1, we support to read a template file from under classpath automatically using the TemplateFilePathProvider. |
3.3.2. XML driven mapper
If you use the XML driven mapper, you can specify an SQL on mapper XML file as follow:
public class NameMapper {
Name findById(Integer id);
}
<mapper namespace="com.example..NameMapper">
<select id="findById" resultType="com.example.Name">
<![CDATA[
SELECT * FROM names
WHERE id = /*[# mb:p="id"]*/ 1 /*[/]*/
]]>
</select>
</mapper>
Also, you can specify an SQL on template file (see 'Annotation driven mapper' section) as follow:
<select id="findById" resultType="com.example.Name">
NameMapper/findById.sql
</select>
4. Configuration
In this chapter, we explain a way for applying mybatis-thymeleaf to the MyBatis in detail. About MyBatis core module configuration, please see the MyBatis reference document.
4.1. Basic configuration
You configure to use the org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriver
as scripting language driver.
4.1.1. Java based configuration
Configuration configuration = new Configuration();
configuration.setDefaultScriptingLanguage(ThymeleafLanguageDriver.class); (1)
// ...
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
1 | Set the ThymeleafLanguageDriver class to a Configuration instance as default scripting language driver |
4.1.2. XML based configuration
<settings>
<setting name="defaultScriptingLanguage"
value="org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriver"/> (1)
</settings>
SqlSessionFactory sqlSessionFactory;
try (Reader configReader = Resources.getResourceAsReader("mybatis-config.xml")) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(configReader);
}
1 | Set the ThymeleafLanguageDriver class to the defaultScriptingLanguage of setting item in configuration XML file |
4.2. Customizing configuration
The mybatis-thymeleaf provides three ways for customizing template engine configuration.
4.2.1. Using properties file
The mybatis-thymeleaf provide the special properties file for customizing default configuration.
By default behavior, the mybatis-thymeleaf load the mybatis-thymeleaf.properties
stored just under classpath.
About supported properties, please see the Section 11.1, “Configuration properties”.
Using an any properties file
You can use an any properties file instead of the default properties file.
If you use an any properties file, please specify a properties file using
the special system properties(mybatis-thymeleaf.config.file
) as follow:
$ java -Dmybatis-thymeleaf.config.file=mybatis-thymeleaf_production.properties ...
Using an any file encoding
You can use an any file encoding instead of the default file encoding(UTF-8
).
If you use an any file encoding, please specify a file encoding using
the special system properties(mybatis-thymeleaf.config.encoding
) as follow:
$ java -Dmybatis-thymeleaf.config.encoding=Windows-31J ...
4.2.2. Using functional interface
The mybatis-thymeleaf provide the special functional
interface(org.mybatis.scripting.thymeleaf.TemplateEngineCustomizer
) for customizing configurations using Java code.
This feature provide an opportunity to customize configuration that cannot customize it using properties file.
public class MyTemplateEngineCustomizer implements TemplateEngineCustomizer {
@Override
public void customize(TemplateEngine defaultTemplateEngine) {
// ... (1)
}
}
customizer = com.example.MyTemplateEngineCustomizer (2)
1 | Write a Java code for customizing template engine |
2 | Specify a FQCN of customizer class in mybatis-thymeleaf.properties |
4.2.3. Using user-defined template engine
When your application requirements cannot be satisfied using above customizing features, you can apply a user-defined template engine(full managed template engine) to the mybatis-thymeleaf as follow:
TemplateEngine templateEngine = new TemplateEngine(); (1)
templateEngine.addDialect(new MyBatisDialect());
templateEngine.setEngineContextFactory(new MyBatisIntegratingEngineContextFactory(
templateEngine.getEngineContextFactory()));
// ...
Configuration configuration = new Configuration();
configuration.getLanguageRegistry().register(new ThymeleafLanguageDriver(templateEngine)); (2)
configuration.setDefaultScriptingLanguage(ThymeleafLanguageDriver.class); (3)
1 | Create an instance of class that implements org.thymeleaf.ITemplateEngine |
2 | Register an instance of ThymeleafLanguageDriver that associate with user-defined template engine instance |
3 | Set the ThymeleafLanguageDriver class as default scripting language driver |
5. Writing 2-way SQL
In this section, we explain standard usage of 2-way SQL.
About cautions for usage 2-way SQL, please see the Section 10, “Cautions for usage”. |
5.1. Binding value
The mybatis-thymeleaf use the default binding feature provided by MyBatis core module. Therefore, you need to write a 2-way SQL template for generating string that can be parsed by MyBatis core module as follow:
SELECT * FROM names
WHERE id = #{id} (1)
SELECT * FROM names
WHERE id = /*[# mb:p="id"]*/ 1 /*[/]*/ (2)
1 | A bind value is specified by 「#{variable name} 」 format |
2 | Can render bind variable expression that can be parsed by MyBatis core module using custom attribute tag(mb:p="variable name" ) provided by mybatis-thymeleaf |
What can you bind? Basically, you can bind a parameter object(Mapper method arguments or |
5.2. Dynamic SQL
The Thymeleaf supports to create an any string dynamically using conditional evaluation and iterating evaluation feature. By using this feature, you can write a dynamic SQL.
5.2.1. Using tag for specifying condition
If you add a SQL part when any condition is matches or not, you can use following attribute tags.
-
th:if
-
th:unless
(denial version forth:if
) -
th:switch
-
th:case
SELECT * FROM names
WHERE 1 = 1 (1)
/*[# th:if="${firstName} != null"]*/ (2)
AND firstName = /*[# mb:p="firstName"]*/ 'Taro' /*[/]*/
/*[/]*/ (3)
ORDER BY id
UPDATE names
SET id = id (4)
/*[# th:if="${firstName} != null"]*/
, firstName = /*[# mb:p="firstName"]*/ 'Taro' /*[/]*/
/*[/]*/
WHERE id = /*[# mb:p="id"]*/ 1 /*[/]*/
1 | Specify a non-dynamic condition at first position because the mybatis-thymeleaf does not provide
the trimming feature such as <where> of XML based scripting language |
2 | Specify a condition on start tag using natural template |
3 | Specify an end tag of condition |
4 | Specify a non-dynamic updating column at first position because the mybatis-thymeleaf does not provide
the trimming feature such as <set> of XML based scripting language |
5.2.2. Using tag for iteration
The Thymeleaf supports to process for iteration object(List
etc..) using th:each
.
SELECT * FROM names
WHERE 1 = 1
/*[# th:if="${not #lists.isEmpty(ids)}"]*/
AND id IN (
/*[# th:each="id : ${ids}"]*/ (1)
/*[# mb:p="id"]*/ 1 /*[/]*/ (2)
/*[(${idStat.last} ? '' : ',')]*/ (3)
/*[/]*/ (4)
)
/*[/]*/
ORDER BY id
1 | Specify an iterable object on th:each |
2 | Specify a SQL template of binding value per iterable element.
A bind value specify by mb:p="{variable name of iterable element}" format (e.g. id ). |
3 | Append comma character when element position is not last.
You can access an iteration status object (IterationStatusVar ) that named by "{variable name of iterable element}Stat" format (e.g. idStat ). |
4 | Specify an end tag of iteration |
An above SQL template can be replaced using Use mb:p for creating bind variables string of IN clause
Since 1.0.2, We support the indexed access using an iteration status object as follow: Use indexed access using an iteration status object
|
About more advanced usage of th:each
, please see Section 11.2, “Bulk insert”.
5.3. Fragment
The Thymeleaf supports to insert template string from an another template file. By using this feature, you can share an SQL on multiple SQL template.
The standard use case using this feature is paging query as follow:
// Count a total record number that matches for criteria
@Select("NameMapper/countByCriteria.sql")
long countByCriteria(@Param("criteria") NameCriteria criteria);
// Search records that matches for criteria and specified page
@Select("NameMapper/findPageByCriteria.sql")
List<Name> findPageByCriteria(@Param("criteria") NameCriteria criteria, @Param("pageable") Pageable pageable);
SELECT COUNT(*) FROM names
WHERE 1 = 1
/*[# th:if="${criteria.firstName} != null"]*/
AND firstName = /*[# mb:p="criteria.firstName"]*/ 'Taro' /*[/]*/
/*[/]*/
/*[# th:if="${criteria.lastName} != null"]*/
AND lastName = /*[# mb:p="criteria.lastName"]*/ 'Yamada' /*[/]*/
/*[/]*/
SELECT * FROM names
WHERE 1 = 1
/*[# th:if="${criteria.firstName} != null"]*/
AND firstName = /*[# mb:p="criteria.firstName"]*/ 'Taro' /*[/]*/
/*[/]*/
/*[# th:if="${criteria.lastName} != null"]*/
AND lastName = /*[# mb:p="criteria.lastName"]*/ 'Yamada' /*[/]*/
/*[/]*/
LIMIT /*[# mb:p="pageable.pageSize"]*/ 20 /*[/]*/
OFFSET /*[# mb:p="pageable.offset"]*/ 0 /*[/]*/
ORDER BY id
Probably looking at above SQLs many developers will look for a way to share the WHERE
clause.
In such case, you can share any SQL part by multiple SQL using fragment feature.
5.3.1. Creating a fragment SQL
At first, you create a fragment SQL template file for sharing by multiple SQL as follow:
/*[- */ (1)
SELECT * FROM names
/* -]*/ (2)
WHERE 1 = 1
/*[# th:if="${criteria.firstName} != null"]*/
AND firstName = /*[# mb:p="criteria.firstName"]*/ 'Taro' /*[/]*/
/*[/]*/
/*[# th:if="${criteria.lastName} != null"]*/
AND lastName = /*[# mb:p="criteria.lastName"]*/ 'Yamada' /*[/]*/
/*[/]*/
1 | Start a block comment for ignoring "SELECT * FROM names" part at template processing time |
2 | End a block comment |
5.3.2. Insert a fragment SQL
You can insert a fragment SQL template file on each template as follow:
SELECT COUNT(*) FROM names
/*[# th:insert="~{NameMapper/whereByCriteria.sql}" /]*/ (1)
SELECT * FROM names
/*[# th:insert="~{NameMapper/whereByCriteria.sql}" /]*/ (1)
LIMIT /*[# mb:p="pageable.pageSize"]*/ 20 /*[/]*/
OFFSET /*[# mb:p="pageable.offset"]*/ 0 /*[/]*/
ORDER BY id
1 | Insert a fragment SQL template file |
5.4. Special variables
The MyBatis core module provides special variables that prefixed with _
as follows:
Variable Name | Description | Type |
---|---|---|
|
The parameter object that passed to the MyBatis |
Any type |
|
The id for identifying the database on current session
(If you want to this variable, you should be enabled the |
|
5.5. Using configuration properties
You can access the configuration properties of MyBatis from your SQL template. About configuration properties, please see the MyBatis reference documentation.
Configuration configuration = new Configuration();
Properties variables = new Properties();
variables.setProperty("tableNameOfUser", "accounts"); (1)
configuration.setVariables(variables);
<properties>
<property name="tableNameOfUser" value="accounts"/> (1)
</properties>
SELECT * FROM /*[# th:utext="${tableNameOfUser} ?: 'users'"]*/ users /*[/]*/ (2)
1 | Define an any property as MyBatis’s configuration properties |
2 | Access a configuration property using Thymeleaf’s expression |
Above SQL template translate to as follows:
SELECT * FROM accounts
SELECT * FROM users
6. Using non 2-way SQL mode
The non 2-way SQL is simple a little compare with 2-way SQL and limitations not found at now.
6.1. Configuration
By default, the mybatis-thymeleaf will be use the 2-way SQL mode. Therefore you should be configure explicitly to use the non 2-way SQL mode using configuration properties file or builder option as follow:
This configuration is optional. The non 2-way SQL can be use on the 2-way SQL mode. |
use2way = false (1)
1 | Set the use2way to false |
configuration.getLanguageRegistry().register(new ThymeleafLanguageDriver(
ThymeleafLanguageDriverConfig.newInstance(c -> c.setUse2Way(false)))); (1)
1 | Set the use2way property of ThymeleafLanguageDriverConfig to false |
6.2. Basic usage
The different with the 2-way SQL mode is that will be unnecessary to enclose the thymeleaf expressions as SQL comment(/*[…]*/
).
SELECT * FROM names
WHERE 1 = 1
[# th:if="${not #lists.isEmpty(ids)}"]
AND id IN (
[# mb:p="ids" /]
)
[/]
ORDER BY id
7. Custom Dialect
The mybatis-thymeleaf provide the custom dialect class(org.mybatis.scripting.thymeleaf.MyBatisDialect
)
that help for generating SQL template.
7.1. Attribute tag
The mybatis-thymeleaf provides following attribute tags.
By default, you can use it using mb
dialect prefix (default prefix is initial letter of "MyBatis").
Attribute Tag Name | Description | Attribute Value Format |
---|---|---|
Render bind variable(default format: |
|
|
Register an any value to the MyBatis’s bind variables
(Provides a feature similar to that of the |
|
7.1.1. p
SELECT * FROM names
WHERE id = /*[# mb:p="id"]*/ 1 /*[/]*/ (1)
SELECT * FROM names
WHERE id = /*[# mb:p="id,typeHandler=com.example.IdTypeHandler"]*/ 1 /*[/]*/ (2)
SELECT * FROM names
WHERE id IN (/*[# mb:p="ids"]*/ 1 /*[/]*/) (3)
1 | Render single bind variable(e.g. #{id} ) that can be parsed by MyBatis core module when specify a simple value object |
2 | Can specify parameter options(key=value format) separate with comma |
3 | Render multiple bind variables(e.g. #{ids[0]}, #{ids[1]}, … ) that can be parsed by MyBatis core module when specify a collection or array object |
7.1.2. bind
SELECT * FROM names
WHERE 1 = 1
/*[# th:if="${firstName} != null"]*/
/*[# mb:bind="patternFirstName=|${#likes.escapeWildcard(firstName)}%|" /]*/ (1)
AND firstName LIKE /*[# mb:p="patternFirstName"]*/ 'Taro' /*[/]*/ (2)
/*[/]*/
/*[# mb:bind="patternFirstName=|${#likes.escapeWildcard(firstName)}%|, patternLastName=|${#likes.escapeWildcard(lastName)}%|" /]*/ (3)
1 | Register an any value(e.g. editing value at template) as custom bind variables |
2 | Bind a custom bind variable |
3 | Also, you can register multiple custom bind variables separating with comma at the same time |
Why need the bind tag? The binding feature provide by MyBatis cannot access a variable that shared by the |
7.2. Expression utility method
7.2.1. #likes expression
The #likes
expression provide utility methods for LIKE clause.
Method | Arguments | Description | ||||||
---|---|---|---|---|---|---|---|---|
|
Return a value that escaped a wildcard character of LIKE condition.
By default behavior, this method escape the |
|||||||
None |
Return a escape clause string of LIKE condition.
By default behavior, this method return |
You can customize the default behavior for the |
escapeWildcard
/*[# th:if="${firstName} != null"]*/
/*[# mb:bind="patternFirstName=|${#likes.escapeWildcard(firstName)}%|" /]*/ (1)
AND firstName LIKE /*[# mb:p="patternFirstName"]*/ 'Taro%' /*[/]*/
/*[/]*/
1 | Register a value that escaped wildcard character of LIKE condition as custom bind variables |
AND firstName LIKE #{patternFirstName}
escapeClause
/*[# th:if="${firstName} != null"]*/
/*[# mb:bind="patternFirstName=|${#likes.escapeWildcard(firstName)}%|" /]*/
AND firstName LIKE /*[# mb:p="patternFirstName"]*/ 'Taro%' /*[/]*/ /*[(${#likes.escapeClause()})]*/ (1)
/*[/]*/
1 | Add ESCAPE clause at template processing time |
AND firstName LIKE #{patternFirstName} ESCAPE '\'
8. Using SQL Generator
Since 1.0.2, we separate the SQL generating feature from the ThymeleafLanguageDriver
and ThymeleafSqlSource
class,
we added the SqlGenerator
and SqlGeneratorConfig
for generating SQL from SQL template.
These classes does not depends on the MyBatis core module(mybatis-3.x.x.jar
).
So that, it also can be used in combination with any data access libraries(e.g. Spring JDBC, JPA, R2DBC, etc…) that provide with named parameter.
8.1. Configuration
By default, the SqlGenerator
applies settings for using together with the MyBatis core module(apply to #{…}
as the bind variable format),
but you can customize a default settings using configuration properties file or the SqlGeneratorConfig
.
The SqlGeneratorConfig
allows the same configurations as the ThymeleafLanguageDriverConfig
except the TemplateFilePathProvider
(template-file.path-provider.*
).
8.1.1. Customize the bind variable format
You can customize the bind variable format using configuration properties file or configuration class.
In the following example, it changes the bind variable format to the Spring JDBC format(e.g. :id
) from MyBatis core format(e.g. #{id}
).
dialect.bind-variable-render = org.mybatis.scripting.thymeleaf.support.spring.SpringNamedParameterBindVariableRender (1)
1 | Specify the BindVariableRender implementation class(built-in class) that render Spring JDBC bind variable format |
SqlGeneratorConfig config = SqlGeneratorConfig.newInstanceWithCustomizer(c ->
c.getDialect().setBindVariableRenderInstance(
BindVariableRender.BuiltIn.SPRING_NAMED_PARAMETER)); (1)
SqlGenerator sqlGenerator = new SqlGenerator(config); (2)
1 | Specify the BindVariableRender implementation (built-in class) that renders Spring JDBC bind variable format via BuiltIn enum |
2 | Create a SqlGenerator instance with user defined configuration |
If you use the custom bind variable format other than built-in format,
please create a implementation class of BindVariableRender
and apply it to the configuration.
public class R2dbcMySQLBindVariableRender implements BindVariableRender { (1)
public String render(String name) {
return "?" + name;
}
}
1 | Create a BindVariableRender implementation class |
8.1.2. Customize other configurations
Please see also the following sections.
8.2. Basic Usage
The SqlGenerator
provide feature for generating a SQL from SQL template using the Thymeleaf as follow:
SqlGenerator sqlGenerator = new SqlGenerator(); (1)
Conditions conditions = new Conditions();
conditions.setId(10);
// sql = "SELECT * FROM accounts WHERE id = #{id}"
String sql = sqlGenerator.generate(
"SELECT * FROM accounts WHERE id = /*[# mb:p='id']*/ 1 /*[/]*/", conditions); (2)
1 | Create a default instance of SqlGenerator |
2 | Generate an SQL from SQL template |
The |
8.2.1. Receiving generated bind variables
You can receiving bind variables that generated during template processing via a BiConsumer
function as follow:
The bind variables may generate when use |
SqlGenerator sqlGenerator = new SqlGenerator();
Map<String, Object> conditionsMap = new HashMap<>();
conditionsMap.put("name", "Yamada");
// sql = "SELECT * FROM accounts WHERE name = #{patternName}"
// conditionsMap = {"name":"Yamada", "patternName":"Yamada%"}
String sql = sqlGenerator.generate(
"/*[# mb:bind='patternName=|${#likes.escapeWildcard(name)}%|' /]*/" +
"SELECT * FROM accounts WHERE name = /*[# mb:p='patternName']*/ 'Sato' /*[/]*/",
conditionsMap, conditionsMap::put); (1)
1 | Specify(Pass) a BiConsumer function for receiving bind variables at 3th argument of generate method |
8.2.2. Specifying custom variables
You can specify any custom variables separately from the parameter object as follow:
SqlGenerator sqlGenerator = new SqlGenerator();
sqlGenerator.setDefaultCustomVariables(
Collections.singletonMap("accountsTableName", "users")); (1)
Account account = new Account();
account.setName("Taro Yamada");
Map<String, Object> customVariables = new HashMap<>(); (2)
customVariables.put("now", LocalDateTime.now());
customVariables.put("loginId", loginId);
// sql = "INSERT INTO users (name, created_at, created_by) VALUES(#{name}, #{now}, #{loginId})"
String sql = sqlGenerator.generate(
"INSERT INTO /*[# th:utext=\"${accountsTableName} ?: 'accounts'\"]*/ accounts /*[/]*/ " + (3)
"(name, created_at, created_by) VALUES(" +
"/*[# mb:p='name']*/ 'Hanako Yamada' /*[/]*/, " +
"/*[# mb:p='now']*/ current_timestamp() /*[/]*/, " + (4)
"/*[# mb:p='loginId']*/ 'A00000001' /*[/]*/" + (4)
")", account, customVariables); (5)
1 | Specify the default custom variable for sharing by every statements |
2 | Create custom variables per statement or transaction |
3 | Can be access to a custom variable at template processing time |
4 | Can be bind a custom variable |
5 | Specify(Pass) custom variables map to sql generator at 3rd argument of generate method |
8.3. Advanced Usage
8.3.1. Access JavaBeans property
By default, the SqlGenerator
use the JDK standard APIs(JavaBeans and Reflection API) for accessing a property of user defined Java object.
If there is a conflict with property accessing in the data access library,
you can change a default behavior by applying a custom org.mybatis.scripting.thymeleaf.PropertyAccessor
implementation class.
SqlGenerator sqlGenerator = new SqlGenerator();
sqlGenerator.setPropertyAccessor(new MyPropertyAccessor()); (1)
1 | Set a custom PropertyAccessor implementation class to the SqlGenerator |
9. Support classes
We provides useful classes for supporting development.
9.1. TemplateFilePathProvider
The org.mybatis.scripting.thymeleaf.support.TemplateFilePathProvider
is SQL provider class that return the SQL template file path. This class is available since 1.0.1.
This class use with SQL provider annotation(@InsertProvider
, @UpdateProvider
, @DeleteProvider
and @SelectProvider
) as follow:
This class required to use on MyBatis 3.5.1+. |
package com.example.mapper;
public interface BaseMapper<T> {
@Options(useGeneratedKeys = true, keyProperty = "id")
@InsertProvider(type = TemplateFilePathProvider.class)
void insert(T entity);
@UpdateProvider(type = TemplateFilePathProvider.class)
void update(T entity);
@DeleteProvider(type = TemplateFilePathProvider.class)
void delete(T entity);
@SelectProvider(type = TemplateFilePathProvider.class)
T findById(Integer id);
}
package com.example.mapper;
public interface NameMapper extends BaseMapper {
@SelectProvider(type = TemplateFilePathProvider.class)
List<Name> findByCondition(NameCondition condition);
}
Since the MyBatis 3.5.6, you can omit the Mapper interface
Java based configuration
XML based configuration
|
By default implementation, a template file path resolve following format and priority order. If does not match all, it throw an exception that indicate not found a template file.
-
com/example/mapper/NameMapper/NameMapper-{methodName}-{databaseId}.sql
-
com/example/mapper/NameMapper/NameMapper-{methodName}.sql
(fallback using default database) -
com/example/mapper/BaseMapper/BaseMapper-{methodName}-{databaseId}.sql
(fallback using declaring class of mapper method) -
com/example/mapper/BaseMapper/BaseMapper-{methodName}.sql
(fallback using declaring class of mapper method and default database)
If you want to customize the template file path format,
you can customize using the configuration properties that start with template-file.path-provider
.
9.2. SpringNamedParameterBindVariableRender
The org.mybatis.scripting.thymeleaf.support.spring.SpringNamedParameterBindVariableRender
is the BindVariableRender
implementation class for rendering bind variable to 「:variable name
」 format(e.g. :id
). This class is available since 1.0.2.
dialect.bind-variable-render = org.mybatis.scripting.thymeleaf.support.spring.SpringNamedParameterBindVariableRender
SqlGeneratorConfig config = SqlGeneratorConfig.newInstanceWithCustomizer(c ->
c.getDialect().setBindVariableRenderInstance(
BindVariableRender.BuiltIn.SPRING_NAMED_PARAMETER));
SqlGenerator sqlGenerator = new SqlGenerator(config);
10. Cautions for usage
The Thymeleaf 3 does not provide the template mode for SQL. Therefore there are some cautions for usage. |
10.1. Using '\'(backslash)
If you are using 2-way SQL mode, there is case that cannot parse a 2-way SQL when specify '\'
(backslash) within static template parts.
We know that following case cannot be parsed 2-way SQL. If you are not using 2-way SQL mode, this limitation can be ignore.
10.1.1. ESCAPE clause for LIKE
If you specify the ESCAPE '\'
directly as static template parts, the Thymeleaf cannot parse it.
/*[# mb:bind="patternFirstName=|${#likes.escapeWildcard(firstName)}%|" /]*/
AND firstName LIKE /*[('#{patternFirstName}')]*/ 'Taro%' /**/ ESCAPE '\' (1)
1 | Specify the ESCAPE '\' directly as static template parts |
As a solution for avoiding this behavior,
the mybatis-thymeleaf provide the expression utility method for adding the |
10.2. Using #likes.escapeClause()
You should use the #likes.escapeClause()
when specify escape character for LIKE phrase,
but you notice there is possible that will be removed characters after /*[(${#likes.escapeClause()})]*/
(see gh-66]).
SELECT * FROM area WHERE NAME LIKE 'Tara%' /*[(${#likes.escapeClause()})]*/ ORDER BY ID (1)
1 | Specify any sql phrase after /*[(${#likes.escapeClause()})]*/ without line break |
The above sql template will translate to follow(removed ORDER BY ID
):
SELECT * FROM area WHERE NAME LIKE 'Tara%' escape '\'
10.2.1. Workarounds
This behavior can be avoided to apply following workarounds.
Adding line break character
Add line break character after /*[(${#likes.escapeClause()})]*/
.
SELECT * FROM area
WHERE NAME LIKE 'Tara%' /*[(${#likes.escapeClause()})]*/
ORDER BY ID
Adding /**/
Add /**/
after /*[(${#likes.escapeClause()})]*/
.
SELECT * FROM area WHERE NAME LIKE 'Tara%' /*[(${#likes.escapeClause()})]*//**/ ORDER BY ID
The above sql template will translate to follow:
SELECT * FROM area WHERE NAME LIKE 'Tara%' ESCAPE '\'/**/ ORDER BY ID
11. Appendix
11.1. Configuration properties
The mybatis-thymeleaf provides following properties for customizing configurations.
Property Key | Description | Type | Default value |
---|---|---|---|
General configuration |
|||
|
Whether use the 2-way SQL feature |
|
|
|
The FQCN of class that implements the |
|
|
Template file configuration |
|||
|
Whether use the cache feature when load template resource file |
|
|
|
The cache TTL(millisecond) for resolved templates |
|
|
|
The character encoding for reading template resource file |
|
|
|
The base directory for reading template resource file |
|
|
|
The patterns for reading as template resource file
(Can specify multiple patterns using comma( |
|
|
Template file path provider configuration for TemplateFilePathProvider |
|||
|
The prefix for adding to template file path |
|
|
|
Whether includes package path part |
|
|
|
Whether separate directory per mapper |
|
|
|
Whether includes mapper name into file name when separate directory per mapper |
|
|
|
Whether cache a resolved template file path |
|
|
Dialect configuration |
|||
|
The prefix name of dialect provided by this project |
|
|
|
The escape character for wildcard of LIKE condition |
|
|
|
The format of escape clause for LIKE condition
(Can specify format that can be allowed by |
|
|
|
Additional escape target characters(custom wildcard characters) for LIKE condition
(Can specify multiple characters using comma( |
|
|
|
The FQCN of class that implements the |
|
|
use2way = false
customizer = com.example.MyTemplateEngineCustomizer
template-file.cache-enabled = true
template-file.cache-ttl = 3600000
template-file.encoding = UTF-8
template-file.base-dir = templates/
template-file.patterns = *sql, *.sql.template
template-file.path-provider.prefix = sqls/
template-file.path-provider.includes-package-path = false
template-file.path-provider.separate-directory-per-mapper = false
template-file.path-provider.includes-mapper-name-when-separate-directory = false
dialect.prefix = mybatis
dialect.like-escape-char = ~
dialect.like-escape-clause-format = escape '%s'
dialect.like-additional-escape-target-chars = %, _
dialect.bind-variable-render = org.mybatis.scripting.thymeleaf.support.spring.SpringNamedParameterBindVariableRender
These properties can be specified via factory method of
If you specify the value both with properties file and customizer function, the properties file value applied.
We provide following factory methods for creating a
These properties can be specified via factory method of
We provide following factory methods for creating a
|
11.2. Bulk insert
This section describe about bulk insert for collection or array using SQL rather than JDBC feature.
If you want to insert a collection or array by bulk, you can be realized it using the th:each
and mb:p
attribute tags as follow:
public class Person {
private int id;
private String firstName;
private String lastName;
private LocalDate dateOfBirth;
private List<Mail> mails;
// ...
}
public class Mail {
private int id;
private int personId;
private String address;
// ...
}
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("sqls/PersonMapper_insertByBulk.sql")
void insertByBulk(List<Person> persons);
@Insert("sqls/PersonMapper_insertMailsByBulk.sql")
void insertMailsByBulk(List<Person> persons);
INSERT INTO persons
(first_name, last_name, date_of_birth)
VALUES
/*[# th:each="person : ${list}"]*/ (1)
(
/*[# mb:p="person.firstName"]*/ 'Taro' /*[/]*/, (2)
/*[# mb:p="person.lastName"]*/ 'Yamada' /*[/]*/,
/*[# mb:p="person.dateOfBirth"]*/ '2000-03-28' /*[/]*/
)/*[(${personStat.last} ? '' : ',')]*/
/*[/]*/
INSERT INTO person_mails
(person_id, address)
VALUES
/*[# th:each="person : ${list}"]*/ (1)
/*[# th:each="mail : ${person.mails}"]*/ (3)
(
/*[# mb:p="person.id"]*/ 1 /*[/]*/,
/*[# mb:p="mail.address"]*/ 'taro.yamada@mybatis.org' /*[/]*/ (4)
)/*[(${personStat.last and mailStat.last} ? '' : ',')]*/
/*[/]*/
/*[/]*/
1 | Fetch and store to local variable(e.g. person ) an element object of top level collection/array using th:each |
2 | Specify a property of element object fetched from top level collection/array as bind variable using mb:p |
3 | Fetch and store to local variable(e.g. mail ) and element object of nested property collection or array |
4 | Specify a property of element object fetched from nested property collection/array as bind variable using mb:p |
The mb:p
register an element object that fetched from collection/array to the MyBatis’s bind variables.
11.3. Multiline String Literals
You can specify an SQL in annotation using "Text Blocks" feature that support since the JDK 13. Also,"Multiline String Literals" provided other JVM languages (such as Kotlin and Groovy) can be used. There is a good compatibility with annotation driven mapper.
11.3.1. Java
@Select("""
SELECT * FROM names
WHERE id = /*[# mb:p="id"]*/ 1 /*[/]*/
""")
Name findById(Integer id);
11.3.2. Kotlin
@Select("""
SELECT * FROM names
WHERE id = /*[# mb:p="id"]*/ 1 /*[/]*/
""")
fun findById(id: Int): Name
11.3.3. Groovy
@Select('''
SELECT * FROM names
WHERE id = /*[# mb:p="id"]*/ 1 /*[/]*/
''')
Name findById(int id);
11.4. Related resources
Resource name | Description |
---|---|
The mybatis-thymeleaf GiHub top page |
|
Explain how to integrate with an application framework |
|
Explain about code completion |
|
Explain how to use mybatis-thymeleaf quickly using the Spring Boot |