diff --git a/src/main/java/org/apache/ibatis/annotations/DeleteProvider.java b/src/main/java/org/apache/ibatis/annotations/DeleteProvider.java
index fb315fc0aa7..b4856476c7b 100644
--- a/src/main/java/org/apache/ibatis/annotations/DeleteProvider.java
+++ b/src/main/java/org/apache/ibatis/annotations/DeleteProvider.java
@@ -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.
@@ -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.
+ *
+ *
+ * Since 3.5.1, this attribute can omit.
+ * If this attribute omit, the MyBatis will call a method that decide by following rules.
+ *
+ *
+ * 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
+ *
+ *
+ * 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
+ *
+ *
+ *
+ * @return a method name of method for providing an SQL
+ */
+ String method() default "";
+
}
diff --git a/src/main/java/org/apache/ibatis/annotations/InsertProvider.java b/src/main/java/org/apache/ibatis/annotations/InsertProvider.java
index bb38bcdc6fc..de5f395185d 100644
--- a/src/main/java/org/apache/ibatis/annotations/InsertProvider.java
+++ b/src/main/java/org/apache/ibatis/annotations/InsertProvider.java
@@ -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.
@@ -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.
+ *
+ *
+ * Since 3.5.1, this attribute can omit.
+ * If this attribute omit, the MyBatis will call a method that decide by following rules.
+ *
+ *
+ * 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
+ *
+ *
+ * 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
+ *
+ *
+ *
+ * @return a method name of method for providing an SQL
+ */
+ String method() default "";
+
}
diff --git a/src/main/java/org/apache/ibatis/annotations/SelectProvider.java b/src/main/java/org/apache/ibatis/annotations/SelectProvider.java
index 68e95e566e4..d533c0bc677 100644
--- a/src/main/java/org/apache/ibatis/annotations/SelectProvider.java
+++ b/src/main/java/org/apache/ibatis/annotations/SelectProvider.java
@@ -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.
@@ -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.
+ *
+ *
+ * Since 3.5.1, this attribute can omit.
+ * If this attribute omit, the MyBatis will call a method that decide by following rules.
+ *
+ *
+ * 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
+ *
+ *
+ * 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
+ *
+ *
+ *
+ * @return a method name of method for providing an SQL
+ */
+ String method() default "";
+
}
diff --git a/src/main/java/org/apache/ibatis/annotations/UpdateProvider.java b/src/main/java/org/apache/ibatis/annotations/UpdateProvider.java
index 695bfa7819b..bc765336fed 100644
--- a/src/main/java/org/apache/ibatis/annotations/UpdateProvider.java
+++ b/src/main/java/org/apache/ibatis/annotations/UpdateProvider.java
@@ -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.
@@ -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.
+ *
+ *
+ * Since 3.5.1, this attribute can omit.
+ * If this attribute omit, the MyBatis will call a method that decide by following rules.
+ *
+ *
+ * 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
+ *
+ *
+ * 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
+ *
+ *
+ *
+ * @return a method name of method for providing an SQL
+ */
+ String method() default "";
+
}
diff --git a/src/main/java/org/apache/ibatis/builder/annotation/ProviderMethodResolver.java b/src/main/java/org/apache/ibatis/builder/annotation/ProviderMethodResolver.java
new file mode 100644
index 00000000000..3bd775e5f53
--- /dev/null
+++ b/src/main/java/org/apache/ibatis/builder/annotation/ProviderMethodResolver.java
@@ -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.
+ *
+ *
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.
+ *
+ *
The default implementation return a method that matches following conditions.
+ *
+ *
Method name matches with mapper method
+ *
Return type matches the {@link CharSequence}({@link String}, {@link StringBuilder}, etc...)
+ *
+ * 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 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 targetMethods = sameNameMethods.stream()
+ .filter(m -> CharSequence.class.isAssignableFrom(m.getReturnType()))
+ .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() + "'.");
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java b/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java
index 48d2a697276..cc047832d05 100644
--- a/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java
+++ b/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java
@@ -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) {
@@ -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) {
diff --git a/src/site/es/xdoc/java-api.xml b/src/site/es/xdoc/java-api.xml
index 829f0741269..331399f3d25 100644
--- a/src/site/es/xdoc/java-api.xml
+++ b/src/site/es/xdoc/java-api.xml
@@ -464,7 +464,13 @@ try (SqlSession session = sqlSessionFactory.openSession()) {
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 CharSequence instead of String 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 ProviderContext(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.
+ via the ProviderContext(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 method attribute, the MyBatis will resolve a target method via the
+ ProviderMethodResolver interface.
+ If not resolve by it, the MyBatis use the reserved fallback method that named provideSql).
+ 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.
@Param
@@ -583,6 +589,44 @@ class UserSqlBuilder {
}
}]]>
+
This example shows usage the default implementation of ProviderMethodResolver(available since MyBatis 3.5.1 or later):
+ 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();
+ }
+}]]>
+
+
This example shows usage the default implementation of ProviderMethodResolver(available since MyBatis 3.5.1 or later):
+ 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();
+ }
+}]]>
+
diff --git a/src/site/ja/xdoc/java-api.xml b/src/site/ja/xdoc/java-api.xml
index df8633766e7..52a4b89e4e5 100644
--- a/src/site/ja/xdoc/java-api.xml
+++ b/src/site/ja/xdoc/java-api.xml
@@ -477,7 +477,10 @@ try (SqlSession session = sqlSessionFactory.openSession()) {
+ 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();
+ }
+
}]]>
diff --git a/src/site/ko/xdoc/java-api.xml b/src/site/ko/xdoc/java-api.xml
index 2d612f921e9..dd5aad87e0d 100644
--- a/src/site/ko/xdoc/java-api.xml
+++ b/src/site/ko/xdoc/java-api.xml
@@ -601,7 +601,10 @@ try (SqlSession session = sqlSessionFactory.openSession()) {
Mapper 메서드의 인수인 "Mapper interface type" 과 ProviderContext(Mybatis 3.4.5 부터) 를 이용한 "Mapper method" 로 전달 된 객체를 메서드 매개변수로 전달할 수 있다.(마이바티스 3.4이상에서는 복수 파라미터를 허용한다.)
사용가능한 속성들 : type, method.
type 속성은 클래스.
- method 속성은 메소드명이다.
+ method 속성은 메소드명이다
+ (Since 3.5.1, you can omit method attribute, the MyBatis will resolve a target method via the
+ ProviderMethodResolver interface.
+ If not resolve by it, the MyBatis use the reserved fallback method that named provideSql).
Note: 이 섹션은 클래스에 대한 설명으로 동적 SQL 을 좀더 깔끔하고 읽기 쉽게 만드는데 도움이 될 수 있다.
@@ -734,6 +737,25 @@ class UserSqlBuilder {
}
}]]>
+
This example shows usage the default implementation of ProviderMethodResolver(available since MyBatis 3.5.1 or later):
+ 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();
+ }
+}]]>
+
diff --git a/src/site/xdoc/java-api.xml b/src/site/xdoc/java-api.xml
index 74e53617053..55f9ecb04e8 100644
--- a/src/site/xdoc/java-api.xml
+++ b/src/site/xdoc/java-api.xml
@@ -515,7 +515,11 @@ try (SqlSession session = sqlSessionFactory.openSession()) {
via the ProviderContext(available since MyBatis 3.4.5 or later) as method argument.
(In MyBatis 3.4 or later, it's allow multiple parameters)
Attributes: type, method. The type attribute is a class.
- The method is the name of the method on that class. NOTE
+ The method is the name of the method on that class
+ (Since 3.5.1, you can omit method attribute, the MyBatis will resolve a target method via the
+ ProviderMethodResolver interface.
+ If not resolve by it, the MyBatis use the reserved fallback method that named provideSql).
+ NOTE
Following this section is a discussion about the class, which can help build dynamic SQL in a cleaner, easier to read way.
允许构建动态 SQL。这些备选的 SQL 注解允许你指定类名和返回在运行时执行的 SQL 语句的方法。(自从MyBatis 3.4.6开始,你可以用 CharSequence 代替 String 来返回类型返回值了。)当执行映射语句的时候,MyBatis 会实例化类并执行方法,类和方法就是填入了注解的值。你可以把已经传递给映射方法了的对象作为参数,"Mapper interface type" 和 "Mapper method" 会经过 ProviderContext (仅在MyBatis 3.4.5及以上支持)作为参数值。(MyBatis 3.4及以上的版本,支持多参数传入)
+ 属性有: type, method。
+ type 属性需填入类。
+ method 需填入该类定义了的方法名
+ (Since 3.5.1, you can omit method attribute, the MyBatis will resolve a target method via the
+ ProviderMethodResolver interface.
+ If not resolve by it, the MyBatis use the reserved fallback method that named provideSql)。
+ 注意 接下来的小节将会讨论类,能帮助你更轻松地构建动态 SQL。
@Param
@@ -578,6 +585,25 @@ class UserSqlBuilder {
}
}]]>
+
This example shows usage the default implementation of ProviderMethodResolver(available since MyBatis 3.5.1 or later):
+ 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();
+ }
+}]]>
+
diff --git a/src/test/java/org/apache/ibatis/submitted/sqlprovider/CreateDB.sql b/src/test/java/org/apache/ibatis/submitted/sqlprovider/CreateDB.sql
index 5b9447bc93b..473ef879db3 100644
--- a/src/test/java/org/apache/ibatis/submitted/sqlprovider/CreateDB.sql
+++ b/src/test/java/org/apache/ibatis/submitted/sqlprovider/CreateDB.sql
@@ -1,5 +1,5 @@
--
--- Copyright 2009-2017 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.
@@ -15,6 +15,7 @@
--
drop table users if exists;
+drop table memos if exists;
create table users (
id int,
@@ -22,8 +23,14 @@ create table users (
logical_delete boolean default false
);
+create table memos (
+ id int,
+ memo varchar(1024),
+);
+
insert into users (id, name) values(1, 'User1');
insert into users (id, name) values(2, 'User2');
insert into users (id, name) values(3, 'User3');
insert into users (id, name, logical_delete) values(4, 'User4', true);
+insert into memos (id, memo) values(1, 'memo1');
diff --git a/src/test/java/org/apache/ibatis/submitted/sqlprovider/ProviderMethodResolutionTest.java b/src/test/java/org/apache/ibatis/submitted/sqlprovider/ProviderMethodResolutionTest.java
new file mode 100644
index 00000000000..98ef91e57b3
--- /dev/null
+++ b/src/test/java/org/apache/ibatis/submitted/sqlprovider/ProviderMethodResolutionTest.java
@@ -0,0 +1,255 @@
+/**
+ * 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.submitted.sqlprovider;
+
+import java.io.Reader;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.ibatis.BaseDataTest;
+import org.apache.ibatis.annotations.DeleteProvider;
+import org.apache.ibatis.annotations.InsertProvider;
+import org.apache.ibatis.annotations.SelectProvider;
+import org.apache.ibatis.annotations.UpdateProvider;
+import org.apache.ibatis.builder.BuilderException;
+import org.apache.ibatis.builder.annotation.ProviderContext;
+import org.apache.ibatis.builder.annotation.ProviderMethodResolver;
+import org.apache.ibatis.io.Resources;
+import org.apache.ibatis.session.SqlSession;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.apache.ibatis.session.SqlSessionFactoryBuilder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Test for https://github.com/mybatis/mybatis-3/issues/1279
+ */
+class ProviderMethodResolutionTest {
+
+ private static SqlSessionFactory sqlSessionFactory;
+
+ @BeforeAll
+ static void setUp() throws Exception {
+ try (Reader reader = Resources
+ .getResourceAsReader("org/apache/ibatis/submitted/sqlprovider/mybatis-config.xml")) {
+ sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
+ sqlSessionFactory.getConfiguration().addMapper(ProvideMethodResolverMapper.class);
+ }
+ BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
+ "org/apache/ibatis/submitted/sqlprovider/CreateDB.sql");
+ }
+
+ @Test
+ void shouldResolveWhenDefaultResolverMatchedMethodIsOne() {
+ try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
+ ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
+ assertEquals(1, mapper.select());
+ }
+ }
+
+ @Test
+ void shouldErrorWhenDefaultResolverMethodNameMatchedMethodIsNone() {
+ BuilderException e = Assertions.assertThrows(BuilderException.class,
+ () -> sqlSessionFactory.getConfiguration().addMapper(DefaultProvideMethodResolverMethodNameMatchedMethodIsNoneMapper.class));
+ assertEquals(
+ "Cannot resolve the provider method because 'insert' not found in SqlProvider 'org.apache.ibatis.submitted.sqlprovider.ProviderMethodResolutionTest$DefaultProvideMethodResolverMethodNameMatchedMethodIsNoneMapper$MethodResolverBasedSqlProvider'.",
+ e.getCause().getMessage());
+ }
+
+ @Test
+ void shouldErrorWhenDefaultResolverReturnTypeMatchedMethodIsNone() {
+ BuilderException e = Assertions.assertThrows(BuilderException.class,
+ () -> sqlSessionFactory.getConfiguration().addMapper(DefaultProvideMethodResolverReturnTypeMatchedMethodIsNoneMapper.class));
+ assertEquals(
+ "Cannot resolve the provider method because 'insert' does not return the CharSequence or its subclass in SqlProvider 'org.apache.ibatis.submitted.sqlprovider.ProviderMethodResolutionTest$DefaultProvideMethodResolverReturnTypeMatchedMethodIsNoneMapper$MethodResolverBasedSqlProvider'.",
+ e.getCause().getMessage());
+ }
+
+ @Test
+ void shouldErrorWhenDefaultResolverMatchedMethodIsMultiple() {
+ BuilderException e = Assertions.assertThrows(BuilderException.class,
+ () -> sqlSessionFactory.getConfiguration().addMapper(DefaultProvideMethodResolverMatchedMethodIsMultipleMapper.class));
+ assertEquals(
+ "Cannot resolve the provider method because 'update' is found multiple in SqlProvider 'org.apache.ibatis.submitted.sqlprovider.ProviderMethodResolutionTest$DefaultProvideMethodResolverMatchedMethodIsMultipleMapper$MethodResolverBasedSqlProvider'.",
+ e.getCause().getMessage());
+ }
+
+ @Test
+ void shouldResolveReservedMethod() {
+ try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
+ ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
+ assertEquals(1, mapper.delete());
+ }
+ }
+
+ @Test
+ void shouldUseSpecifiedMethodOnSqlProviderAnnotation() {
+ try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
+ ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
+ assertEquals(2, mapper.select2());
+ }
+ }
+
+ @Test
+ void shouldResolveMethodUsingCustomResolver() {
+ try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
+ ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
+ assertEquals(3, mapper.select3());
+ }
+ }
+
+ @Test
+ void shouldResolveReservedNameMethodWhenCustomResolverReturnNull() {
+ try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
+ ProvideMethodResolverMapper mapper = sqlSession.getMapper(ProvideMethodResolverMapper.class);
+ assertEquals(99, mapper.select4());
+ }
+ }
+
+ @Test
+ void shouldErrorWhenCannotDetectsReservedNameMethod() {
+ BuilderException e = Assertions.assertThrows(BuilderException.class,
+ () -> sqlSessionFactory.getConfiguration().addMapper(ReservedNameMethodIsNoneMapper.class));
+ assertEquals(
+ "Error creating SqlSource for SqlProvider. Method 'provideSql' not found in SqlProvider 'org.apache.ibatis.submitted.sqlprovider.ProviderMethodResolutionTest$ReservedNameMethodIsNoneMapper$SqlProvider'.",
+ e.getCause().getMessage());
+ }
+
+ interface ProvideMethodResolverMapper {
+
+ @SelectProvider(type = MethodResolverBasedSqlProvider.class)
+ int select();
+
+ @SelectProvider(type = MethodResolverBasedSqlProvider.class, method = "provideSelect2Sql")
+ int select2();
+
+ @SelectProvider(type = CustomMethodResolverBasedSqlProvider.class)
+ int select3();
+
+ @SelectProvider(type = CustomMethodResolverBasedSqlProvider.class)
+ int select4();
+
+ @DeleteProvider(type = ReservedMethodNameBasedSqlProvider.class)
+ int delete();
+
+ class MethodResolverBasedSqlProvider implements ProviderMethodResolver {
+ public static String select() {
+ return "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
+ }
+
+ public static String select2() {
+ throw new IllegalStateException("This method should not called when specify `method` attribute on @SelectProvider.");
+ }
+
+ public static String provideSelect2Sql() {
+ return "SELECT 2 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
+ }
+ }
+
+ class ReservedMethodNameBasedSqlProvider {
+ public static String provideSql() {
+ return "DELETE FROM memos WHERE id = 1";
+ }
+ }
+
+ class CustomMethodResolverBasedSqlProvider implements CustomProviderMethodResolver {
+ public static String select3Sql() {
+ return "SELECT 3 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
+ }
+
+ public static String provideSql() {
+ return "SELECT 99 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
+ }
+ }
+
+ }
+
+ interface CustomProviderMethodResolver extends ProviderMethodResolver {
+ @Override
+ default Method resolveMethod(ProviderContext context) {
+ List targetMethods = Arrays.stream(getClass().getMethods())
+ .filter(m -> m.getName().equals(context.getMapperMethod().getName() + "Sql"))
+ .filter(m -> CharSequence.class.isAssignableFrom(m.getReturnType()))
+ .collect(Collectors.toList());
+ if (targetMethods.size() == 1) {
+ return targetMethods.get(0);
+ }
+ return null;
+ }
+ }
+
+ interface DefaultProvideMethodResolverMethodNameMatchedMethodIsNoneMapper {
+
+ @InsertProvider(type = MethodResolverBasedSqlProvider.class)
+ int insert();
+
+ class MethodResolverBasedSqlProvider implements ProviderMethodResolver {
+ public static String provideInsertSql() {
+ return "INSERT INTO foo (name) VALUES(#{name})";
+ }
+ }
+
+ }
+
+ interface DefaultProvideMethodResolverReturnTypeMatchedMethodIsNoneMapper {
+
+ @InsertProvider(type = MethodResolverBasedSqlProvider.class)
+ int insert();
+
+ class MethodResolverBasedSqlProvider implements ProviderMethodResolver {
+ public static int insert() {
+ return 1;
+ }
+ }
+
+ }
+
+ interface DefaultProvideMethodResolverMatchedMethodIsMultipleMapper {
+
+ @UpdateProvider(type = MethodResolverBasedSqlProvider.class)
+ int update();
+
+ class MethodResolverBasedSqlProvider implements ProviderMethodResolver {
+ public static String update() {
+ return "UPDATE foo SET name = #{name} WHERE id = #{id}";
+ }
+
+ public static StringBuilder update(ProviderContext context) {
+ return new StringBuilder("UPDATE foo SET name = #{name} WHERE id = #{id}");
+ }
+ }
+
+ }
+
+ interface ReservedNameMethodIsNoneMapper {
+
+ @UpdateProvider(type = SqlProvider.class)
+ int update();
+
+ class SqlProvider {
+ public static String select() {
+ return "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS";
+ }
+ }
+
+ }
+
+}