Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SQL provider class that help detecting a template file automatically #19

Merged
merged 1 commit into from
Apr 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ jdk:
- oraclejdk8

script:
# build using mybatis latest version
# build using mybatis latest released version
- ./mvnw clean verify
# build using mybatis 3.4.x line
- ./mvnw clean verify -Dmybatis.version=3.4.6
# test using mybatis 3.4.x line
- ./mvnw test -Dmybatis.version=3.4.6
# build using mybatis 3.5.x snapshot
- ./mvnw clean verify -Dmybatis.version=3.5.2-SNAPSHOT

Expand Down
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,18 @@
</dependencies>

<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<mybatis.version>${mybatis.version}</mybatis.version>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.asciidoctor</groupId>
Expand Down
70 changes: 70 additions & 0 deletions src/main/asciidoc/user-guide.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,76 @@ using <<Configuration properties, Configuration properties>>.
AND firstName LIKE #{patternFirstName} ESCAPE '\'
----

== Support classes

We provides useful classes for supporting development.

=== TemplateFilePathProvider

The `TemplateFilePathProvider` is SQL provider class that return the SQL template file path(Available since 1.0.1).
This class use with SQL provider annotation(`@InsertProvider`, `@UpdateProvider`, `@DeleteProvider` and `@SelectProvider`}) as follow:

[NOTE]
====
**This class required to use on MyBatis 3.5.1+.**
====

.Usage:

[source, java]
----
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);

}
----

[source, java]
----
package com.example.mapper;

public interface NameMapper extends BaseMapper {

@SelectProvider(type = TemplateFilePathProvider.class)
List<Name> findByCondition(NameCondition condition);

}
----

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,
please call static setter methods of the `TemplateFilePathProvider` **before initialize the MyBatis module**.

[NOTE]
====
If you applied an user defined `ThymeleafLanguageDriverConfig` for `ThymeleafLanguageDriver`,
please apply same instance to the `TemplateFilePathProvider` using the `setLanguageDriverConfig` method.
====


== Cautions for usage

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
/**
* Copyright 2018-2019 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mybatis.scripting.thymeleaf.support;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Optional;

import org.apache.ibatis.builder.annotation.ProviderContext;
import org.apache.ibatis.io.Resources;
import org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriver;
import org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriverConfig;

/**
* The SQL provider class that return the SQL template file path. <br>
* <b>IMPORTANT: This class required to use with mybatis 3.5.1+</b> and need to use with SQL provider annotation (such
* as {@link org.apache.ibatis.annotations.SelectProvider} as follow: <br>
* <br>
*
* <pre>
* package com.example.mapper;
*
* public interface BaseMapper&lt;T&gt; {
*
* &#64;Options(useGeneratedKeys = true, keyProperty = "id")
* &#64;InsertProvider(type = TemplateFilePathProvider.class)
* void insert(T entity);
*
* &#64;UpdateProvider(type = TemplateFilePathProvider.class)
* void update(T entity);
*
* &#64;DeleteProvider(type = TemplateFilePathProvider.class)
* void delete(T entity);
*
* &#64;SelectProvider(type = TemplateFilePathProvider.class)
* T findById(Integer id);
*
* }
* </pre>
*
* <pre>
* package com.example.mapper;
*
* public interface NameMapper extends BaseMapper {
*
* &#64;SelectProvider(type = TemplateFilePathProvider.class)
* List&lt;Name&gt; findByConditions(NameConditions conditions);
*
* }
* </pre>
*
* @author Kazuki Shimizu
* @version 1.0.1
*/
public class TemplateFilePathProvider {

private static final PathGenerator DEFAULT_PATH_GENERATOR = TemplateFilePathProvider::generateTemplatePath;
private static final ThymeleafLanguageDriverConfig DEFAULT_LANGUAGE_DRIVER_CONFIG = ThymeleafLanguageDriverConfig
.newInstance();

private static String prefix = "";
private static boolean includesPackagePath = true;
private static boolean separateDirectoryPerMapper = true;
private static boolean includesMapperNameWhenSeparateDirectory = true;
private static PathGenerator pathGenerator = DEFAULT_PATH_GENERATOR;
private static ThymeleafLanguageDriverConfig languageDriverConfig = DEFAULT_LANGUAGE_DRIVER_CONFIG;

/**
* Set a prefix for adding to template file path.
* <p>
* Default is {@code ""}.
* </p>
*
* @param prefix
* a prefix for adding to template file path
*/
public static void setPrefix(String prefix) {
TemplateFilePathProvider.prefix = Optional.ofNullable(prefix).orElse("");
}

/**
* Set whether includes package path part.
* <p>
* Default is {@code true}.
* </p>
*
* @param includesPackagePath
* If want to includes, set {@code true}
*/
public static void setIncludesPackagePath(boolean includesPackagePath) {
TemplateFilePathProvider.includesPackagePath = includesPackagePath;
}

/**
* Set whether separate directory per mapper.
* <p>
* Default is {@code true}.
* </p>
*
* @param separateDirectoryPerMapper
* If want to separate directory, set {@code true}
*/
public static void setSeparateDirectoryPerMapper(boolean separateDirectoryPerMapper) {
TemplateFilePathProvider.separateDirectoryPerMapper = separateDirectoryPerMapper;
}

/**
* Set whether includes mapper name into file name when separate directory per mapper.
* <p>
* Default is {@code true}.
* </p>
*
* @param includesMapperNameWhenSeparateDirectory
* If want to includes, set {@code true}
*/
public static void setIncludesMapperNameWhenSeparateDirectory(boolean includesMapperNameWhenSeparateDirectory) {
TemplateFilePathProvider.includesMapperNameWhenSeparateDirectory = includesMapperNameWhenSeparateDirectory;
}

/**
* Set custom implementation for {@link PathGenerator}.
*
* @param generator
* a instance for generating a template file path
*/
public static void setCustomTemplateFilePathGenerator(PathGenerator generator) {
TemplateFilePathProvider.pathGenerator = Optional.ofNullable(generator).orElse(DEFAULT_PATH_GENERATOR);
}

/**
* Set a configuration instance for {@link ThymeleafLanguageDriver}.
* <p>
* By default, {@link ThymeleafLanguageDriverConfig#newInstance()} will used.
* </p>
* <p>
* If you applied an user define {@link ThymeleafLanguageDriverConfig} for {@link ThymeleafLanguageDriver}, please
* same instance to the this class.
* </p>
*
* @param languageDriverConfig
* A user defined {@link ThymeleafLanguageDriverConfig}
*/
public static void setLanguageDriverConfig(ThymeleafLanguageDriverConfig languageDriverConfig) {
TemplateFilePathProvider.languageDriverConfig = Optional.ofNullable(languageDriverConfig)
.orElse(DEFAULT_LANGUAGE_DRIVER_CONFIG);
}

/**
* Provide an SQL scripting string(template file path).
*
* <br>
* 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.
* <ul>
* <li>com/example/mapper/NameMapper/NameMapper-{methodName}-{databaseId}.sql</li>
* <li>com/example/mapper/NameMapper/NameMapper-{methodName}.sql (fallback using default database)</li>
* <li>com/example/mapper/BaseMapper/BaseMapper-{methodName}-{databaseId}.sql (fallback using declaring class of
* method)</li>
* <li>com/example/mapper/BaseMapper/BaseMapper-{methodName}.sql (fallback using declaring class of method and default
* database)</li>
* </ul>
* <br>
* If you want to customize path format, please call the following methods on application initialize phase.
* <ul>
* <li>{@link #setPrefix(String)}</li>
* <li>{@link #setIncludesPackagePath(boolean)}</li>
* <li>{@link #setSeparateDirectoryPerMapper(boolean)}</li>
* <li>{@link #setIncludesMapperNameWhenSeparateDirectory(boolean)}</li>
* <li>{@link #setLanguageDriverConfig(ThymeleafLanguageDriverConfig)}</li>
* <li>{@link #setCustomTemplateFilePathGenerator(PathGenerator)}</li>
* </ul>
*
* @param context
* a context of SQL provider
* @return an SQL scripting string(template file path)
*/
public static String provideSql(ProviderContext context) {
return providePath(context.getMapperType(), context.getMapperMethod(), context.getDatabaseId());
}

static String providePath(Class<?> mapperType, Method mapperMethod, String databaseId) {
boolean fallbackDeclaringClass = mapperType != mapperMethod.getDeclaringClass();
boolean fallbackDatabase = databaseId != null;
String path = pathGenerator.generatePath(mapperType, mapperMethod, databaseId);
if (exists(path)) {
return path;
}
if (fallbackDatabase) {
path = pathGenerator.generatePath(mapperType, mapperMethod, null);
if (exists(path)) {
return path;
}
}
if (fallbackDeclaringClass) {
path = pathGenerator.generatePath(mapperMethod.getDeclaringClass(), mapperMethod, databaseId);
if (exists(path)) {
return path;
}
}
if (fallbackDatabase) {
path = pathGenerator.generatePath(mapperMethod.getDeclaringClass(), mapperMethod, null);
if (exists(path)) {
return path;
}
}
throw new IllegalStateException("The SQL template file not found. mapperType:[" + mapperType + "] mapperMethod:["
+ mapperMethod + "] databaseId:[" + databaseId + "]");
}

private static String generateTemplatePath(Class<?> type, Method method, String databaseId) {
Package pkg = type.getPackage();
String packageName = pkg == null ? "" : pkg.getName();
String className = type.getName().substring(packageName.length() + (packageName.length() == 0 ? 0 : 1));

StringBuilder path = new StringBuilder();
if (!prefix.isEmpty()) {
path.append(prefix);
}
if (includesPackagePath && !packageName.isEmpty()) {
path.append(packageName.replace('.', '/')).append('/');
}
path.append(className);
if (separateDirectoryPerMapper) {
path.append('/');
if (includesMapperNameWhenSeparateDirectory) {
path.append(className).append('-');
}
} else {
path.append('-');
}
path.append(method.getName());
if (databaseId != null) {
path.append('-').append(databaseId);
}
path.append(".sql");
return path.toString();
}

private static boolean exists(String path) {
String actualPath;
if (languageDriverConfig.getTemplateFile().getBaseDir().isEmpty()) {
actualPath = path;
} else {
actualPath = languageDriverConfig.getTemplateFile().getBaseDir().endsWith("/")
? languageDriverConfig.getTemplateFile().getBaseDir() + path
: languageDriverConfig.getTemplateFile().getBaseDir() + "/" + path;
}
try {
return Resources.getResourceAsFile(actualPath).exists();
} catch (IOException e) {
return false;
}
}

/**
* The interface that implements a function for generating template file path.
*/
@FunctionalInterface
public interface PathGenerator {

/**
* Generate a template file path.
*
* @param type
* mapper interface type that specified provider (or declaring interface type of mapper method)
* @param method
* a mapper method that specified provider
* @param databaseId
* a database id that provided from {@link org.apache.ibatis.mapping.DatabaseIdProvider}
* @return a template file path
*/
String generatePath(Class<?> type, Method method, String databaseId);

}

}
Loading