Skip to content

Commit fd49061

Browse files
authored
Merge pull request #1499 from kazuki43zoo/gh-1279
Allow omit a 'method' attribute on SqlProvider annotation
2 parents f738839 + 877284a commit fd49061

File tree

13 files changed

+611
-23
lines changed

13 files changed

+611
-23
lines changed

src/main/java/org/apache/ibatis/annotations/DeleteProvider.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2009-2016 the original author or authors.
2+
* Copyright 2009-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,7 +28,33 @@
2828
@Retention(RetentionPolicy.RUNTIME)
2929
@Target(ElementType.METHOD)
3030
public @interface DeleteProvider {
31+
32+
/**
33+
* Specify a type that implements an SQL provider method.
34+
*
35+
* @return a type that implements an SQL provider method
36+
*/
3137
Class<?> type();
3238

33-
String method();
39+
/**
40+
* Specify a method for providing an SQL.
41+
*
42+
* <p>
43+
* Since 3.5.1, this attribute can omit.
44+
* If this attribute omit, the MyBatis will call a method that decide by following rules.
45+
* <ul>
46+
* <li>
47+
* If class that specified the {@link #type()} attribute implements the {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver},
48+
* the MyBatis use a method that returned by it
49+
* </li>
50+
* <li>
51+
* If cannot resolve a method by {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver}(= not implement it or it was returned {@code null}),
52+
* the MyBatis will search and use a fallback method that named {@code provideSql} from specified type
53+
* </li>
54+
* </ul>
55+
*
56+
* @return a method name of method for providing an SQL
57+
*/
58+
String method() default "";
59+
3460
}

src/main/java/org/apache/ibatis/annotations/InsertProvider.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2009-2016 the original author or authors.
2+
* Copyright 2009-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,7 +28,33 @@
2828
@Retention(RetentionPolicy.RUNTIME)
2929
@Target(ElementType.METHOD)
3030
public @interface InsertProvider {
31+
32+
/**
33+
* Specify a type that implements an SQL provider method.
34+
*
35+
* @return a type that implements an SQL provider method
36+
*/
3137
Class<?> type();
3238

33-
String method();
39+
/**
40+
* Specify a method for providing an SQL.
41+
*
42+
* <p>
43+
* Since 3.5.1, this attribute can omit.
44+
* If this attribute omit, the MyBatis will call a method that decide by following rules.
45+
* <ul>
46+
* <li>
47+
* If class that specified the {@link #type()} attribute implements the {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver},
48+
* the MyBatis use a method that returned by it
49+
* </li>
50+
* <li>
51+
* If cannot resolve a method by {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver}(= not implement it or it was returned {@code null}),
52+
* the MyBatis will search and use a fallback method that named {@code provideSql} from specified type
53+
* </li>
54+
* </ul>
55+
*
56+
* @return a method name of method for providing an SQL
57+
*/
58+
String method() default "";
59+
3460
}

src/main/java/org/apache/ibatis/annotations/SelectProvider.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2009-2016 the original author or authors.
2+
* Copyright 2009-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,7 +28,33 @@
2828
@Retention(RetentionPolicy.RUNTIME)
2929
@Target(ElementType.METHOD)
3030
public @interface SelectProvider {
31+
32+
/**
33+
* Specify a type that implements an SQL provider method.
34+
*
35+
* @return a type that implements an SQL provider method
36+
*/
3137
Class<?> type();
3238

33-
String method();
39+
/**
40+
* Specify a method for providing an SQL.
41+
*
42+
* <p>
43+
* Since 3.5.1, this attribute can omit.
44+
* If this attribute omit, the MyBatis will call a method that decide by following rules.
45+
* <ul>
46+
* <li>
47+
* If class that specified the {@link #type()} attribute implements the {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver},
48+
* the MyBatis use a method that returned by it
49+
* </li>
50+
* <li>
51+
* If cannot resolve a method by {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver}(= not implement it or it was returned {@code null}),
52+
* the MyBatis will search and use a fallback method that named {@code provideSql} from specified type
53+
* </li>
54+
* </ul>
55+
*
56+
* @return a method name of method for providing an SQL
57+
*/
58+
String method() default "";
59+
3460
}

src/main/java/org/apache/ibatis/annotations/UpdateProvider.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2009-2016 the original author or authors.
2+
* Copyright 2009-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,7 +28,33 @@
2828
@Retention(RetentionPolicy.RUNTIME)
2929
@Target(ElementType.METHOD)
3030
public @interface UpdateProvider {
31+
32+
/**
33+
* Specify a type that implements an SQL provider method.
34+
*
35+
* @return a type that implements an SQL provider method
36+
*/
3137
Class<?> type();
3238

33-
String method();
39+
/**
40+
* Specify a method for providing an SQL.
41+
*
42+
* <p>
43+
* Since 3.5.1, this attribute can omit.
44+
* If this attribute omit, the MyBatis will call a method that decide by following rules.
45+
* <ul>
46+
* <li>
47+
* If class that specified the {@link #type()} attribute implements the {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver},
48+
* the MyBatis use a method that returned by it
49+
* </li>
50+
* <li>
51+
* If cannot resolve a method by {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver}(= not implement it or it was returned {@code null}),
52+
* the MyBatis will search and use a fallback method that named {@code provideSql} from specified type
53+
* </li>
54+
* </ul>
55+
*
56+
* @return a method name of method for providing an SQL
57+
*/
58+
String method() default "";
59+
3460
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* Copyright 2009-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+
17+
package org.apache.ibatis.builder.annotation;
18+
19+
import java.lang.reflect.Method;
20+
import java.util.Arrays;
21+
import java.util.List;
22+
import java.util.stream.Collectors;
23+
24+
import org.apache.ibatis.builder.BuilderException;
25+
26+
/**
27+
* The interface that resolve an SQL provider method via an SQL provider class.
28+
*
29+
* <p> This interface need to implements at an SQL provider class and
30+
* it need to define the default constructor for creating a new instance.
31+
*
32+
* @since 3.5.1
33+
* @author Kazuki Shimizu
34+
*/
35+
public interface ProviderMethodResolver {
36+
37+
/**
38+
* Resolve an SQL provider method.
39+
*
40+
* <p> The default implementation return a method that matches following conditions.
41+
* <ul>
42+
* <li>Method name matches with mapper method</li>
43+
* <li>Return type matches the {@link CharSequence}({@link String}, {@link StringBuilder}, etc...)</li>
44+
* </ul>
45+
* If matched method is zero or multiple, it throws a {@link BuilderException}.
46+
*
47+
* @param context a context for SQL provider
48+
* @return an SQL provider method
49+
* @throws BuilderException Throws when cannot resolve a target method
50+
*/
51+
default Method resolveMethod(ProviderContext context) {
52+
List<Method> sameNameMethods = Arrays.stream(getClass().getMethods())
53+
.filter(m -> m.getName().equals(context.getMapperMethod().getName()))
54+
.collect(Collectors.toList());
55+
if (sameNameMethods.isEmpty()) {
56+
throw new BuilderException("Cannot resolve the provider method because '"
57+
+ context.getMapperMethod().getName() + "' not found in SqlProvider '" + getClass().getName() + "'.");
58+
}
59+
List<Method> targetMethods = sameNameMethods.stream()
60+
.filter(m -> CharSequence.class.isAssignableFrom(m.getReturnType()))
61+
.collect(Collectors.toList());
62+
if (targetMethods.size() == 1) {
63+
return targetMethods.get(0);
64+
}
65+
if (targetMethods.isEmpty()) {
66+
throw new BuilderException("Cannot resolve the provider method because '"
67+
+ context.getMapperMethod().getName() + "' does not return the CharSequence or its subclass in SqlProvider '"
68+
+ getClass().getName() + "'.");
69+
} else {
70+
throw new BuilderException("Cannot resolve the provider method because '"
71+
+ context.getMapperMethod().getName() + "' is found multiple in SqlProvider '" + getClass().getName() + "'.");
72+
}
73+
}
74+
75+
}

src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,21 @@ public ProviderSqlSource(Configuration configuration, Object provider, Class<?>
6262
this.providerType = (Class<?>) provider.getClass().getMethod("type").invoke(provider);
6363
providerMethodName = (String) provider.getClass().getMethod("method").invoke(provider);
6464

65-
for (Method m : this.providerType.getMethods()) {
66-
if (providerMethodName.equals(m.getName()) && CharSequence.class.isAssignableFrom(m.getReturnType())) {
67-
if (providerMethod != null) {
68-
throw new BuilderException("Error creating SqlSource for SqlProvider. Method '"
69-
+ providerMethodName + "' is found multiple in SqlProvider '" + this.providerType.getName()
70-
+ "'. Sql provider method can not overload.");
65+
if (providerMethodName.length() == 0 && ProviderMethodResolver.class.isAssignableFrom(this.providerType)) {
66+
this.providerMethod = ((ProviderMethodResolver) this.providerType.getDeclaredConstructor().newInstance())
67+
.resolveMethod(new ProviderContext(mapperType, mapperMethod));
68+
}
69+
if (this.providerMethod == null) {
70+
providerMethodName = providerMethodName.length() == 0 ? "provideSql" : providerMethodName;
71+
for (Method m : this.providerType.getMethods()) {
72+
if (providerMethodName.equals(m.getName()) && CharSequence.class.isAssignableFrom(m.getReturnType())) {
73+
if (this.providerMethod != null) {
74+
throw new BuilderException("Error creating SqlSource for SqlProvider. Method '"
75+
+ providerMethodName + "' is found multiple in SqlProvider '" + this.providerType.getName()
76+
+ "'. Sql provider method can not overload.");
77+
}
78+
this.providerMethod = m;
7179
}
72-
this.providerMethod = m;
73-
this.providerMethodArgumentNames = new ParamNameResolver(configuration, m).getNames();
74-
this.providerMethodParameterTypes = m.getParameterTypes();
7580
}
7681
}
7782
} catch (BuilderException e) {
@@ -83,6 +88,8 @@ public ProviderSqlSource(Configuration configuration, Object provider, Class<?>
8388
throw new BuilderException("Error creating SqlSource for SqlProvider. Method '"
8489
+ providerMethodName + "' not found in SqlProvider '" + this.providerType.getName() + "'.");
8590
}
91+
this.providerMethodArgumentNames = new ParamNameResolver(configuration, this.providerMethod).getNames();
92+
this.providerMethodParameterTypes = this.providerMethod.getParameterTypes();
8693
for (int i = 0; i < this.providerMethodParameterTypes.length; i++) {
8794
Class<?> parameterType = this.providerMethodParameterTypes[i];
8895
if (parameterType == ProviderContext.class) {

src/site/es/xdoc/java-api.xml

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,13 @@ try (SqlSession session = sqlSessionFactory.openSession()) {
464464
</td>
465465
<td>Estas anotaciones SQL alternativas te permiten especificar un nombre de clases y un método que devolverán la SQL que debe ejecutarse (Since 3.4.6, you can specify the <code>CharSequence</code> instead of <code>String</code> as a method return type).
466466
Cuando se ejecute el método MyBatis instanciará la clase y ejecutará el método especificados en el provider. You can pass objects that passed to arguments of a mapper method, "Mapper interface type" and "Mapper method"
467-
via the <code>ProviderContext</code>(available since MyBatis 3.4.5 or later) as method argument.(In MyBatis 3.4 or later, it's allow multiple parameters) Atributos: type, method. El atributo type es el nombre completamente cualificado de una clase. El method es el nombre un método de dicha clase. Nota: A continuación hay una sección sobre la clase, que puede ayudar a construir SQL dinámico de una forma más clara y sencilla de leer.</td>
467+
via the <code>ProviderContext</code>(available since MyBatis 3.4.5 or later) as method argument.(In MyBatis 3.4 or later, it's allow multiple parameters)
468+
Atributos: type, method. El atributo type es el nombre completamente cualificado de una clase.
469+
El method es el nombre un método de dicha clase
470+
(Since 3.5.1, you can omit <code>method</code> attribute, the MyBatis will resolve a target method via the
471+
<code>ProviderMethodResolver</code> interface.
472+
If not resolve by it, the MyBatis use the reserved fallback method that named <code>provideSql</code>).
473+
Nota: A continuación hay una sección sobre la clase, que puede ayudar a construir SQL dinámico de una forma más clara y sencilla de leer.</td>
468474
</tr>
469475
<tr>
470476
<td><code>@Param</code></td>
@@ -583,6 +589,44 @@ class UserSqlBuilder {
583589
}
584590
}]]></source>
585591

592+
<p>This example shows usage the default implementation of <code>ProviderMethodResolver</code>(available since MyBatis 3.5.1 or later):</p>
593+
<source><![CDATA[@SelectProvider(type = UserSqlProvider.class)
594+
List<User> getUsersByName(String name);
595+
596+
// Implements the ProviderMethodResolver on your provider class
597+
class UserSqlProvider implements ProviderMethodResolver {
598+
// In default implementation, it will resolve a method that method name is matched with mapper method
599+
public static String getUsersByName(final String name) {
600+
return new SQL(){{
601+
SELECT("*");
602+
FROM("users");
603+
if (name != null) {
604+
WHERE("name like #{value} || '%'");
605+
}
606+
ORDER_BY("id");
607+
}}.toString();
608+
}
609+
}]]></source>
610+
611+
<p>This example shows usage the default implementation of <code>ProviderMethodResolver</code>(available since MyBatis 3.5.1 or later):</p>
612+
<source><![CDATA[@SelectProvider(type = UserSqlProvider.class)
613+
List<User> getUsersByName(String name);
614+
615+
// Implements the ProviderMethodResolver on your provider class
616+
class UserSqlProvider implements ProviderMethodResolver {
617+
// In default implementation, it will resolve a method that method name is matched with mapper method
618+
public static String getUsersByName(final String name) {
619+
return new SQL(){{
620+
SELECT("*");
621+
FROM("users");
622+
if (name != null) {
623+
WHERE("name like #{value} || '%'");
624+
}
625+
ORDER_BY("id");
626+
}}.toString();
627+
}
628+
}]]></source>
629+
586630
</subsection>
587631

588632
</section>

src/site/ja/xdoc/java-api.xml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,10 @@ try (SqlSession session = sqlSessionFactory.openSession()) {
477477
<td>これらのアノテーションは動的 SQL を生成するためのものです。実行時に指定されたメソッドが呼び出され、メソッドから返された SQL ステートメントが実行されます (MyBatis 3.4.6以降では、メソッドの返り値として <code>String</code> ではなく <code>CharSequence</code> を指定することができます)。
478478
マップドステートメントを実行する際、プロバイダーによって指定したクラスのインスタンスが作成され、指定されたメソッドが実行されます。
479479
なお、メソッド引数にはMapperメソッドの引数に渡したオブジェクトに加え、<code>ProviderContext</code>(MyBatis 3.4.5以降で利用可能)を介して「Mapperインタフェースの型」と「Mapperメソッド」を渡すことができます。(MyBatis 3.4以降では、複数の引数を渡すことができます)
480-
キー: <code>type</code>, <code>method</code>. <code>type</code> にはクラスオブジェクト、<code>method</code> にはメソッド名を指定します。 <span class="label important">NOTE</span> 次の章で、クリーンで可読性の高いコードで動的 SQL を構築するためのクラスについて説明します。
480+
キー: <code>type</code>, <code>method</code>. <code>type</code> にはクラスオブジェクト、<code>method</code> にはメソッド名を指定します
481+
(MyBatis 3.5.1以降では、<code>method</code> 属性を省略することができます。その際MyBatisは、<code>ProviderMethodResolver</code> インタフェースを介して対象メソッドの解決を試み、
482+
対象メソッドが解決できない場合は、<code>provideSql</code>という名前のメソッドを代替メソッドとして利用します)。
483+
<span class="label important">NOTE</span> 次の章で、クリーンで可読性の高いコードで動的 SQL を構築するためのクラスについて説明します。
481484
</td>
482485
</tr>
483486
<tr>
@@ -591,6 +594,27 @@ class UserSqlBuilder {
591594
ORDER_BY(orderByColumn);
592595
}}.toString();
593596
}
597+
}]]></source>
598+
599+
<p>次のコードは、<code>ProviderMethodResolver</code>(MyBatis 3.5.1以降で利用可能)のデフォルト実装の利用例です。</p>
600+
<source><![CDATA[@SelectProvider(type = UserSqlProvider.class)
601+
List<User> getUsersByName(String name);
602+
603+
// SQLプロバイダクラスにProviderMethodResolverを実装する
604+
class UserSqlProvider implements ProviderMethodResolver {
605+
606+
// デフォルト実装では、マッパーメソッドと同名のメソッドが対象メソッドとして扱われます。
607+
public static String getUsersByName(final String name) {
608+
return new SQL(){{
609+
SELECT("*");
610+
FROM("users");
611+
if (name != null) {
612+
WHERE("name like #{value} || '%'");
613+
}
614+
ORDER_BY("id");
615+
}}.toString();
616+
}
617+
594618
}]]></source>
595619

596620
</subsection>

0 commit comments

Comments
 (0)