Skip to content

Commit 55ae75c

Browse files
authored
Merge pull request #19 from kazuki43zoo/gh-10
Add SQL provider class that help detecting a template file automatically
2 parents 18cd409 + 3abeb07 commit 55ae75c

23 files changed

+990
-3
lines changed

.travis.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ jdk:
66
- oraclejdk8
77

88
script:
9-
# build using mybatis latest version
9+
# build using mybatis latest released version
1010
- ./mvnw clean verify
11-
# build using mybatis 3.4.x line
12-
- ./mvnw clean verify -Dmybatis.version=3.4.6
11+
# test using mybatis 3.4.x line
12+
- ./mvnw test -Dmybatis.version=3.4.6
1313
# build using mybatis 3.5.x snapshot
1414
- ./mvnw clean verify -Dmybatis.version=3.5.2-SNAPSHOT
1515

pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,18 @@
117117
</dependencies>
118118

119119
<build>
120+
<pluginManagement>
121+
<plugins>
122+
<plugin>
123+
<artifactId>maven-surefire-plugin</artifactId>
124+
<configuration>
125+
<systemPropertyVariables>
126+
<mybatis.version>${mybatis.version}</mybatis.version>
127+
</systemPropertyVariables>
128+
</configuration>
129+
</plugin>
130+
</plugins>
131+
</pluginManagement>
120132
<plugins>
121133
<plugin>
122134
<groupId>org.asciidoctor</groupId>

src/main/asciidoc/user-guide.adoc

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,76 @@ using <<Configuration properties, Configuration properties>>.
963963
AND firstName LIKE #{patternFirstName} ESCAPE '\'
964964
----
965965

966+
== Support classes
967+
968+
We provides useful classes for supporting development.
969+
970+
=== TemplateFilePathProvider
971+
972+
The `TemplateFilePathProvider` is SQL provider class that return the SQL template file path(Available since 1.0.1).
973+
This class use with SQL provider annotation(`@InsertProvider`, `@UpdateProvider`, `@DeleteProvider` and `@SelectProvider`}) as follow:
974+
975+
[NOTE]
976+
====
977+
**This class required to use on MyBatis 3.5.1+.**
978+
====
979+
980+
.Usage:
981+
982+
[source, java]
983+
----
984+
package com.example.mapper;
985+
986+
public interface BaseMapper<T> {
987+
988+
@Options(useGeneratedKeys = true, keyProperty = "id")
989+
@InsertProvider(type = TemplateFilePathProvider.class)
990+
void insert(T entity);
991+
992+
@UpdateProvider(type = TemplateFilePathProvider.class)
993+
void update(T entity);
994+
995+
@DeleteProvider(type = TemplateFilePathProvider.class)
996+
void delete(T entity);
997+
998+
@SelectProvider(type = TemplateFilePathProvider.class)
999+
T findById(Integer id);
1000+
1001+
}
1002+
----
1003+
1004+
[source, java]
1005+
----
1006+
package com.example.mapper;
1007+
1008+
public interface NameMapper extends BaseMapper {
1009+
1010+
@SelectProvider(type = TemplateFilePathProvider.class)
1011+
List<Name> findByCondition(NameCondition condition);
1012+
1013+
}
1014+
----
1015+
1016+
By default implementation, a template file path resolve following format and priority order.
1017+
If does not match all, it throw an exception that indicate not found a template file.
1018+
1019+
* `com/example/mapper/NameMapper/NameMapper-{methodName}-{databaseId}.sql`
1020+
* `com/example/mapper/NameMapper/NameMapper-{methodName}.sql` +
1021+
(fallback using default database)
1022+
* `com/example/mapper/BaseMapper/BaseMapper-{methodName}-{databaseId}.sql` +
1023+
(fallback using declaring class of mapper method)
1024+
* `com/example/mapper/BaseMapper/BaseMapper-{methodName}.sql` +
1025+
(fallback using declaring class of mapper method and default database)
1026+
1027+
If you want to customize the template file path format,
1028+
please call static setter methods of the `TemplateFilePathProvider` **before initialize the MyBatis module**.
1029+
1030+
[NOTE]
1031+
====
1032+
If you applied an user defined `ThymeleafLanguageDriverConfig` for `ThymeleafLanguageDriver`,
1033+
please apply same instance to the `TemplateFilePathProvider` using the `setLanguageDriverConfig` method.
1034+
====
1035+
9661036

9671037
== Cautions for usage
9681038

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
/**
2+
* Copyright 2018-2019 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+
package org.mybatis.scripting.thymeleaf.support;
17+
18+
import java.io.IOException;
19+
import java.lang.reflect.Method;
20+
import java.util.Optional;
21+
22+
import org.apache.ibatis.builder.annotation.ProviderContext;
23+
import org.apache.ibatis.io.Resources;
24+
import org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriver;
25+
import org.mybatis.scripting.thymeleaf.ThymeleafLanguageDriverConfig;
26+
27+
/**
28+
* The SQL provider class that return the SQL template file path. <br>
29+
* <b>IMPORTANT: This class required to use with mybatis 3.5.1+</b> and need to use with SQL provider annotation (such
30+
* as {@link org.apache.ibatis.annotations.SelectProvider} as follow: <br>
31+
* <br>
32+
*
33+
* <pre>
34+
* package com.example.mapper;
35+
*
36+
* public interface BaseMapper&lt;T&gt; {
37+
*
38+
* &#64;Options(useGeneratedKeys = true, keyProperty = "id")
39+
* &#64;InsertProvider(type = TemplateFilePathProvider.class)
40+
* void insert(T entity);
41+
*
42+
* &#64;UpdateProvider(type = TemplateFilePathProvider.class)
43+
* void update(T entity);
44+
*
45+
* &#64;DeleteProvider(type = TemplateFilePathProvider.class)
46+
* void delete(T entity);
47+
*
48+
* &#64;SelectProvider(type = TemplateFilePathProvider.class)
49+
* T findById(Integer id);
50+
*
51+
* }
52+
* </pre>
53+
*
54+
* <pre>
55+
* package com.example.mapper;
56+
*
57+
* public interface NameMapper extends BaseMapper {
58+
*
59+
* &#64;SelectProvider(type = TemplateFilePathProvider.class)
60+
* List&lt;Name&gt; findByConditions(NameConditions conditions);
61+
*
62+
* }
63+
* </pre>
64+
*
65+
* @author Kazuki Shimizu
66+
* @version 1.0.1
67+
*/
68+
public class TemplateFilePathProvider {
69+
70+
private static final PathGenerator DEFAULT_PATH_GENERATOR = TemplateFilePathProvider::generateTemplatePath;
71+
private static final ThymeleafLanguageDriverConfig DEFAULT_LANGUAGE_DRIVER_CONFIG = ThymeleafLanguageDriverConfig
72+
.newInstance();
73+
74+
private static String prefix = "";
75+
private static boolean includesPackagePath = true;
76+
private static boolean separateDirectoryPerMapper = true;
77+
private static boolean includesMapperNameWhenSeparateDirectory = true;
78+
private static PathGenerator pathGenerator = DEFAULT_PATH_GENERATOR;
79+
private static ThymeleafLanguageDriverConfig languageDriverConfig = DEFAULT_LANGUAGE_DRIVER_CONFIG;
80+
81+
/**
82+
* Set a prefix for adding to template file path.
83+
* <p>
84+
* Default is {@code ""}.
85+
* </p>
86+
*
87+
* @param prefix
88+
* a prefix for adding to template file path
89+
*/
90+
public static void setPrefix(String prefix) {
91+
TemplateFilePathProvider.prefix = Optional.ofNullable(prefix).orElse("");
92+
}
93+
94+
/**
95+
* Set whether includes package path part.
96+
* <p>
97+
* Default is {@code true}.
98+
* </p>
99+
*
100+
* @param includesPackagePath
101+
* If want to includes, set {@code true}
102+
*/
103+
public static void setIncludesPackagePath(boolean includesPackagePath) {
104+
TemplateFilePathProvider.includesPackagePath = includesPackagePath;
105+
}
106+
107+
/**
108+
* Set whether separate directory per mapper.
109+
* <p>
110+
* Default is {@code true}.
111+
* </p>
112+
*
113+
* @param separateDirectoryPerMapper
114+
* If want to separate directory, set {@code true}
115+
*/
116+
public static void setSeparateDirectoryPerMapper(boolean separateDirectoryPerMapper) {
117+
TemplateFilePathProvider.separateDirectoryPerMapper = separateDirectoryPerMapper;
118+
}
119+
120+
/**
121+
* Set whether includes mapper name into file name when separate directory per mapper.
122+
* <p>
123+
* Default is {@code true}.
124+
* </p>
125+
*
126+
* @param includesMapperNameWhenSeparateDirectory
127+
* If want to includes, set {@code true}
128+
*/
129+
public static void setIncludesMapperNameWhenSeparateDirectory(boolean includesMapperNameWhenSeparateDirectory) {
130+
TemplateFilePathProvider.includesMapperNameWhenSeparateDirectory = includesMapperNameWhenSeparateDirectory;
131+
}
132+
133+
/**
134+
* Set custom implementation for {@link PathGenerator}.
135+
*
136+
* @param generator
137+
* a instance for generating a template file path
138+
*/
139+
public static void setCustomTemplateFilePathGenerator(PathGenerator generator) {
140+
TemplateFilePathProvider.pathGenerator = Optional.ofNullable(generator).orElse(DEFAULT_PATH_GENERATOR);
141+
}
142+
143+
/**
144+
* Set a configuration instance for {@link ThymeleafLanguageDriver}.
145+
* <p>
146+
* By default, {@link ThymeleafLanguageDriverConfig#newInstance()} will used.
147+
* </p>
148+
* <p>
149+
* If you applied an user define {@link ThymeleafLanguageDriverConfig} for {@link ThymeleafLanguageDriver}, please
150+
* same instance to the this class.
151+
* </p>
152+
*
153+
* @param languageDriverConfig
154+
* A user defined {@link ThymeleafLanguageDriverConfig}
155+
*/
156+
public static void setLanguageDriverConfig(ThymeleafLanguageDriverConfig languageDriverConfig) {
157+
TemplateFilePathProvider.languageDriverConfig = Optional.ofNullable(languageDriverConfig)
158+
.orElse(DEFAULT_LANGUAGE_DRIVER_CONFIG);
159+
}
160+
161+
/**
162+
* Provide an SQL scripting string(template file path).
163+
*
164+
* <br>
165+
* By default implementation, a template file path resolve following format and priority order. If does not match all,
166+
* it throw an exception that indicate not found a template file.
167+
* <ul>
168+
* <li>com/example/mapper/NameMapper/NameMapper-{methodName}-{databaseId}.sql</li>
169+
* <li>com/example/mapper/NameMapper/NameMapper-{methodName}.sql (fallback using default database)</li>
170+
* <li>com/example/mapper/BaseMapper/BaseMapper-{methodName}-{databaseId}.sql (fallback using declaring class of
171+
* method)</li>
172+
* <li>com/example/mapper/BaseMapper/BaseMapper-{methodName}.sql (fallback using declaring class of method and default
173+
* database)</li>
174+
* </ul>
175+
* <br>
176+
* If you want to customize path format, please call the following methods on application initialize phase.
177+
* <ul>
178+
* <li>{@link #setPrefix(String)}</li>
179+
* <li>{@link #setIncludesPackagePath(boolean)}</li>
180+
* <li>{@link #setSeparateDirectoryPerMapper(boolean)}</li>
181+
* <li>{@link #setIncludesMapperNameWhenSeparateDirectory(boolean)}</li>
182+
* <li>{@link #setLanguageDriverConfig(ThymeleafLanguageDriverConfig)}</li>
183+
* <li>{@link #setCustomTemplateFilePathGenerator(PathGenerator)}</li>
184+
* </ul>
185+
*
186+
* @param context
187+
* a context of SQL provider
188+
* @return an SQL scripting string(template file path)
189+
*/
190+
public static String provideSql(ProviderContext context) {
191+
return providePath(context.getMapperType(), context.getMapperMethod(), context.getDatabaseId());
192+
}
193+
194+
static String providePath(Class<?> mapperType, Method mapperMethod, String databaseId) {
195+
boolean fallbackDeclaringClass = mapperType != mapperMethod.getDeclaringClass();
196+
boolean fallbackDatabase = databaseId != null;
197+
String path = pathGenerator.generatePath(mapperType, mapperMethod, databaseId);
198+
if (exists(path)) {
199+
return path;
200+
}
201+
if (fallbackDatabase) {
202+
path = pathGenerator.generatePath(mapperType, mapperMethod, null);
203+
if (exists(path)) {
204+
return path;
205+
}
206+
}
207+
if (fallbackDeclaringClass) {
208+
path = pathGenerator.generatePath(mapperMethod.getDeclaringClass(), mapperMethod, databaseId);
209+
if (exists(path)) {
210+
return path;
211+
}
212+
}
213+
if (fallbackDatabase) {
214+
path = pathGenerator.generatePath(mapperMethod.getDeclaringClass(), mapperMethod, null);
215+
if (exists(path)) {
216+
return path;
217+
}
218+
}
219+
throw new IllegalStateException("The SQL template file not found. mapperType:[" + mapperType + "] mapperMethod:["
220+
+ mapperMethod + "] databaseId:[" + databaseId + "]");
221+
}
222+
223+
private static String generateTemplatePath(Class<?> type, Method method, String databaseId) {
224+
Package pkg = type.getPackage();
225+
String packageName = pkg == null ? "" : pkg.getName();
226+
String className = type.getName().substring(packageName.length() + (packageName.length() == 0 ? 0 : 1));
227+
228+
StringBuilder path = new StringBuilder();
229+
if (!prefix.isEmpty()) {
230+
path.append(prefix);
231+
}
232+
if (includesPackagePath && !packageName.isEmpty()) {
233+
path.append(packageName.replace('.', '/')).append('/');
234+
}
235+
path.append(className);
236+
if (separateDirectoryPerMapper) {
237+
path.append('/');
238+
if (includesMapperNameWhenSeparateDirectory) {
239+
path.append(className).append('-');
240+
}
241+
} else {
242+
path.append('-');
243+
}
244+
path.append(method.getName());
245+
if (databaseId != null) {
246+
path.append('-').append(databaseId);
247+
}
248+
path.append(".sql");
249+
return path.toString();
250+
}
251+
252+
private static boolean exists(String path) {
253+
String actualPath;
254+
if (languageDriverConfig.getTemplateFile().getBaseDir().isEmpty()) {
255+
actualPath = path;
256+
} else {
257+
actualPath = languageDriverConfig.getTemplateFile().getBaseDir().endsWith("/")
258+
? languageDriverConfig.getTemplateFile().getBaseDir() + path
259+
: languageDriverConfig.getTemplateFile().getBaseDir() + "/" + path;
260+
}
261+
try {
262+
return Resources.getResourceAsFile(actualPath).exists();
263+
} catch (IOException e) {
264+
return false;
265+
}
266+
}
267+
268+
/**
269+
* The interface that implements a function for generating template file path.
270+
*/
271+
@FunctionalInterface
272+
public interface PathGenerator {
273+
274+
/**
275+
* Generate a template file path.
276+
*
277+
* @param type
278+
* mapper interface type that specified provider (or declaring interface type of mapper method)
279+
* @param method
280+
* a mapper method that specified provider
281+
* @param databaseId
282+
* a database id that provided from {@link org.apache.ibatis.mapping.DatabaseIdProvider}
283+
* @return a template file path
284+
*/
285+
String generatePath(Class<?> type, Method method, String databaseId);
286+
287+
}
288+
289+
}

0 commit comments

Comments
 (0)