Skip to content
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
30 changes: 28 additions & 2 deletions src/main/java/org/apache/ibatis/annotations/DeleteProvider.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2009-2016 the original author or authors.
* Copyright 2009-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.
Expand Down Expand Up @@ -28,7 +28,33 @@
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DeleteProvider {

/**
* Specify a type that implements an SQL provider method.
*
* @return a type that implements an SQL provider method
*/
Class<?> type();

String method();
/**
* Specify a method for providing an SQL.
*
* <p>
* Since 3.5.1, this attribute can omit.
* If this attribute omit, the MyBatis will call a method that decide by following rules.
* <ul>
* <li>
* If class that specified the {@link #type()} attribute implements the {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver},
* the MyBatis use a method that returned by it
* </li>
* <li>
* If cannot resolve a method by {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver}(= not implement it or it was returned {@code null}),
* the MyBatis will search and use a fallback method that named {@code provideSql} from specified type
* </li>
* </ul>
*
* @return a method name of method for providing an SQL
*/
String method() default "";

}
30 changes: 28 additions & 2 deletions src/main/java/org/apache/ibatis/annotations/InsertProvider.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2009-2016 the original author or authors.
* Copyright 2009-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.
Expand Down Expand Up @@ -28,7 +28,33 @@
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InsertProvider {

/**
* Specify a type that implements an SQL provider method.
*
* @return a type that implements an SQL provider method
*/
Class<?> type();

String method();
/**
* Specify a method for providing an SQL.
*
* <p>
* Since 3.5.1, this attribute can omit.
* If this attribute omit, the MyBatis will call a method that decide by following rules.
* <ul>
* <li>
* If class that specified the {@link #type()} attribute implements the {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver},
* the MyBatis use a method that returned by it
* </li>
* <li>
* If cannot resolve a method by {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver}(= not implement it or it was returned {@code null}),
* the MyBatis will search and use a fallback method that named {@code provideSql} from specified type
* </li>
* </ul>
*
* @return a method name of method for providing an SQL
*/
String method() default "";

}
30 changes: 28 additions & 2 deletions src/main/java/org/apache/ibatis/annotations/SelectProvider.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2009-2016 the original author or authors.
* Copyright 2009-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.
Expand Down Expand Up @@ -28,7 +28,33 @@
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SelectProvider {

/**
* Specify a type that implements an SQL provider method.
*
* @return a type that implements an SQL provider method
*/
Class<?> type();

String method();
/**
* Specify a method for providing an SQL.
*
* <p>
* Since 3.5.1, this attribute can omit.
* If this attribute omit, the MyBatis will call a method that decide by following rules.
* <ul>
* <li>
* If class that specified the {@link #type()} attribute implements the {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver},
* the MyBatis use a method that returned by it
* </li>
* <li>
* If cannot resolve a method by {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver}(= not implement it or it was returned {@code null}),
* the MyBatis will search and use a fallback method that named {@code provideSql} from specified type
* </li>
* </ul>
*
* @return a method name of method for providing an SQL
*/
String method() default "";

}
30 changes: 28 additions & 2 deletions src/main/java/org/apache/ibatis/annotations/UpdateProvider.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2009-2016 the original author or authors.
* Copyright 2009-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.
Expand Down Expand Up @@ -28,7 +28,33 @@
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface UpdateProvider {

/**
* Specify a type that implements an SQL provider method.
*
* @return a type that implements an SQL provider method
*/
Class<?> type();

String method();
/**
* Specify a method for providing an SQL.
*
* <p>
* Since 3.5.1, this attribute can omit.
* If this attribute omit, the MyBatis will call a method that decide by following rules.
* <ul>
* <li>
* If class that specified the {@link #type()} attribute implements the {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver},
* the MyBatis use a method that returned by it
* </li>
* <li>
* If cannot resolve a method by {@link org.apache.ibatis.builder.annotation.ProviderMethodResolver}(= not implement it or it was returned {@code null}),
* the MyBatis will search and use a fallback method that named {@code provideSql} from specified type
* </li>
* </ul>
*
* @return a method name of method for providing an SQL
*/
String method() default "";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Copyright 2009-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.apache.ibatis.builder.annotation;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.ibatis.builder.BuilderException;

/**
* The interface that resolve an SQL provider method via an SQL provider class.
*
* <p> This interface need to implements at an SQL provider class and
* it need to define the default constructor for creating a new instance.
*
* @since 3.5.1
* @author Kazuki Shimizu
*/
public interface ProviderMethodResolver {

/**
* Resolve an SQL provider method.
*
* <p> The default implementation return a method that matches following conditions.
* <ul>
* <li>Method name matches with mapper method</li>
* <li>Return type matches the {@link CharSequence}({@link String}, {@link StringBuilder}, etc...)</li>
* </ul>
* If matched method is zero or multiple, it throws a {@link BuilderException}.
*
* @param context a context for SQL provider
* @return an SQL provider method
* @throws BuilderException Throws when cannot resolve a target method
*/
default Method resolveMethod(ProviderContext context) {
List<Method> sameNameMethods = Arrays.stream(getClass().getMethods())
.filter(m -> m.getName().equals(context.getMapperMethod().getName()))
.collect(Collectors.toList());
if (sameNameMethods.isEmpty()) {
throw new BuilderException("Cannot resolve the provider method because '"
+ context.getMapperMethod().getName() + "' not found in SqlProvider '" + getClass().getName() + "'.");
}
List<Method> targetMethods = sameNameMethods.stream()
.filter(m -> CharSequence.class.isAssignableFrom(m.getReturnType()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be better to check the return type later to throw clearer error message e.g. 'the return type of the method must be CharSequence or its subclass'.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix via 877284a

.collect(Collectors.toList());
if (targetMethods.size() == 1) {
return targetMethods.get(0);
}
if (targetMethods.isEmpty()) {
throw new BuilderException("Cannot resolve the provider method because '"
+ context.getMapperMethod().getName() + "' does not return the CharSequence or its subclass in SqlProvider '"
+ getClass().getName() + "'.");
} else {
throw new BuilderException("Cannot resolve the provider method because '"
+ context.getMapperMethod().getName() + "' is found multiple in SqlProvider '" + getClass().getName() + "'.");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,21 @@ public ProviderSqlSource(Configuration configuration, Object provider, Class<?>
this.providerType = (Class<?>) provider.getClass().getMethod("type").invoke(provider);
providerMethodName = (String) provider.getClass().getMethod("method").invoke(provider);

for (Method m : this.providerType.getMethods()) {
if (providerMethodName.equals(m.getName()) && CharSequence.class.isAssignableFrom(m.getReturnType())) {
if (providerMethod != null) {
throw new BuilderException("Error creating SqlSource for SqlProvider. Method '"
+ providerMethodName + "' is found multiple in SqlProvider '" + this.providerType.getName()
+ "'. Sql provider method can not overload.");
if (providerMethodName.length() == 0 && ProviderMethodResolver.class.isAssignableFrom(this.providerType)) {
this.providerMethod = ((ProviderMethodResolver) this.providerType.getDeclaredConstructor().newInstance())
.resolveMethod(new ProviderContext(mapperType, mapperMethod));
}
if (this.providerMethod == null) {
providerMethodName = providerMethodName.length() == 0 ? "provideSql" : providerMethodName;
for (Method m : this.providerType.getMethods()) {
if (providerMethodName.equals(m.getName()) && CharSequence.class.isAssignableFrom(m.getReturnType())) {
if (this.providerMethod != null) {
throw new BuilderException("Error creating SqlSource for SqlProvider. Method '"
+ providerMethodName + "' is found multiple in SqlProvider '" + this.providerType.getName()
+ "'. Sql provider method can not overload.");
}
this.providerMethod = m;
}
this.providerMethod = m;
this.providerMethodArgumentNames = new ParamNameResolver(configuration, m).getNames();
this.providerMethodParameterTypes = m.getParameterTypes();
}
}
} catch (BuilderException e) {
Expand All @@ -83,6 +88,8 @@ public ProviderSqlSource(Configuration configuration, Object provider, Class<?>
throw new BuilderException("Error creating SqlSource for SqlProvider. Method '"
+ providerMethodName + "' not found in SqlProvider '" + this.providerType.getName() + "'.");
}
this.providerMethodArgumentNames = new ParamNameResolver(configuration, this.providerMethod).getNames();
this.providerMethodParameterTypes = this.providerMethod.getParameterTypes();
for (int i = 0; i < this.providerMethodParameterTypes.length; i++) {
Class<?> parameterType = this.providerMethodParameterTypes[i];
if (parameterType == ProviderContext.class) {
Expand Down
46 changes: 45 additions & 1 deletion src/site/es/xdoc/java-api.xml
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,13 @@ try (SqlSession session = sqlSessionFactory.openSession()) {
</td>
<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).
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"
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>
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
(Since 3.5.1, you can omit <code>method</code> attribute, the MyBatis will resolve a target method via the
<code>ProviderMethodResolver</code> interface.
If not resolve by it, the MyBatis use the reserved fallback method that named <code>provideSql</code>).
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>
</tr>
<tr>
<td><code>@Param</code></td>
Expand Down Expand Up @@ -583,6 +589,44 @@ class UserSqlBuilder {
}
}]]></source>

<p>This example shows usage the default implementation of <code>ProviderMethodResolver</code>(available since MyBatis 3.5.1 or later):</p>
<source><![CDATA[@SelectProvider(type = UserSqlProvider.class)
List<User> getUsersByName(String name);

// Implements the ProviderMethodResolver on your provider class
class UserSqlProvider implements ProviderMethodResolver {
// In default implementation, it will resolve a method that method name is matched with mapper method
public static String getUsersByName(final String name) {
return new SQL(){{
SELECT("*");
FROM("users");
if (name != null) {
WHERE("name like #{value} || '%'");
}
ORDER_BY("id");
}}.toString();
}
}]]></source>

<p>This example shows usage the default implementation of <code>ProviderMethodResolver</code>(available since MyBatis 3.5.1 or later):</p>
<source><![CDATA[@SelectProvider(type = UserSqlProvider.class)
List<User> getUsersByName(String name);

// Implements the ProviderMethodResolver on your provider class
class UserSqlProvider implements ProviderMethodResolver {
// In default implementation, it will resolve a method that method name is matched with mapper method
public static String getUsersByName(final String name) {
return new SQL(){{
SELECT("*");
FROM("users");
if (name != null) {
WHERE("name like #{value} || '%'");
}
ORDER_BY("id");
}}.toString();
}
}]]></source>

</subsection>

</section>
Expand Down
26 changes: 25 additions & 1 deletion src/site/ja/xdoc/java-api.xml
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,10 @@ try (SqlSession session = sqlSessionFactory.openSession()) {
<td>これらのアノテーションは動的 SQL を生成するためのものです。実行時に指定されたメソッドが呼び出され、メソッドから返された SQL ステートメントが実行されます (MyBatis 3.4.6以降では、メソッドの返り値として <code>String</code> ではなく <code>CharSequence</code> を指定することができます)。
マップドステートメントを実行する際、プロバイダーによって指定したクラスのインスタンスが作成され、指定されたメソッドが実行されます。
なお、メソッド引数にはMapperメソッドの引数に渡したオブジェクトに加え、<code>ProviderContext</code>(MyBatis 3.4.5以降で利用可能)を介して「Mapperインタフェースの型」と「Mapperメソッド」を渡すことができます。(MyBatis 3.4以降では、複数の引数を渡すことができます)
キー: <code>type</code>, <code>method</code>. <code>type</code> にはクラスオブジェクト、<code>method</code> にはメソッド名を指定します。 <span class="label important">NOTE</span> 次の章で、クリーンで可読性の高いコードで動的 SQL を構築するためのクラスについて説明します。
キー: <code>type</code>, <code>method</code>. <code>type</code> にはクラスオブジェクト、<code>method</code> にはメソッド名を指定します
(MyBatis 3.5.1以降では、<code>method</code> 属性を省略することができます。その際MyBatisは、<code>ProviderMethodResolver</code> インタフェースを介して対象メソッドの解決を試み、
対象メソッドが解決できない場合は、<code>provideSql</code>という名前のメソッドを代替メソッドとして利用します)。
<span class="label important">NOTE</span> 次の章で、クリーンで可読性の高いコードで動的 SQL を構築するためのクラスについて説明します。
</td>
</tr>
<tr>
Expand Down Expand Up @@ -591,6 +594,27 @@ class UserSqlBuilder {
ORDER_BY(orderByColumn);
}}.toString();
}
}]]></source>

<p>次のコードは、<code>ProviderMethodResolver</code>(MyBatis 3.5.1以降で利用可能)のデフォルト実装の利用例です。</p>
<source><![CDATA[@SelectProvider(type = UserSqlProvider.class)
List<User> getUsersByName(String name);

// SQLプロバイダクラスにProviderMethodResolverを実装する
class UserSqlProvider implements ProviderMethodResolver {

// デフォルト実装では、マッパーメソッドと同名のメソッドが対象メソッドとして扱われます。
public static String getUsersByName(final String name) {
return new SQL(){{
SELECT("*");
FROM("users");
if (name != null) {
WHERE("name like #{value} || '%'");
}
ORDER_BY("id");
}}.toString();
}

}]]></source>

</subsection>
Expand Down
Loading