Skip to content

Commit

Permalink
Merge pull request mybatis#1055 from kazuki43zoo/gh-1013_sqlprovider
Browse files Browse the repository at this point in the history
Support ProviderContext on sql provider method
  • Loading branch information
kazuki43zoo authored Jul 27, 2017
2 parents e7ed37a + 749d2b6 commit 50cc787
Show file tree
Hide file tree
Showing 13 changed files with 356 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterT
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
} else if (sqlProviderAnnotationType != null) {
Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method);
}
return null;
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright 2009-2017 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;

/**
* The context object for sql provider method.
*
* @author Kazuki Shimizu
* @since 3.4.5
*/
public final class ProviderContext {

private final Class<?> mapperType;
private final Method mapperMethod;

/**
* Constructor.
*
* @param mapperType A mapper interface type that specified provider
* @param mapperMethod A mapper method that specified provider
*/
ProviderContext(Class<?> mapperType, Method mapperMethod) {
this.mapperType = mapperType;
this.mapperMethod = mapperMethod;
}

/**
* Get a mapper interface type that specified provider.
*
* @return A mapper interface type that specified provider
*/
public Class<?> getMapperType() {
return mapperType;
}

/**
* Get a mapper method that specified provider.
*
* @return A mapper method that specified provider
*/
public Method getMapperMethod() {
return mapperMethod;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,22 @@ public class ProviderSqlSource implements SqlSource {
private final Class<?> providerType;
private Method providerMethod;
private String[] providerMethodArgumentNames;
private Class<?>[] providerMethodParameterTypes;
private ProviderContext providerContext;
private Integer providerContextIndex;

/**
* @deprecated Please use the {@link #ProviderSqlSource(Configuration, Object, Class, Method)} instead of this.
*/
@Deprecated
public ProviderSqlSource(Configuration config, Object provider) {
this(config, provider, null, null);
}

/**
* @since 3.4.5
*/
public ProviderSqlSource(Configuration config, Object provider, Class<?> mapperType, Method mapperMethod) {
String providerMethodName;
try {
this.sqlSourceParser = new SqlSourceBuilder(config);
Expand All @@ -54,6 +68,7 @@ public ProviderSqlSource(Configuration config, Object provider) {
}
this.providerMethod = m;
this.providerMethodArgumentNames = new ParamNameResolver(config, m).getNames();
this.providerMethodParameterTypes = m.getParameterTypes();
}
}
}
Expand All @@ -66,6 +81,18 @@ public ProviderSqlSource(Configuration config, Object provider) {
throw new BuilderException("Error creating SqlSource for SqlProvider. Method '"
+ providerMethodName + "' not found in SqlProvider '" + this.providerType.getName() + "'.");
}
for (int i = 0; i< this.providerMethodParameterTypes.length; i++) {
Class<?> parameterType = this.providerMethodParameterTypes[i];
if (parameterType == ProviderContext.class) {
if (this.providerContext != null){
throw new BuilderException("Error creating SqlSource for SqlProvider. ProviderContext found multiple in SqlProvider method ("
+ this.providerType.getName() + "." + providerMethod.getName()
+ "). ProviderContext can not define multiple in SqlProvider method argument.");
}
this.providerContext = new ProviderContext(mapperType, mapperMethod);
this.providerContextIndex = i;
}
}
}

@Override
Expand All @@ -77,12 +104,15 @@ public BoundSql getBoundSql(Object parameterObject) {
private SqlSource createSqlSource(Object parameterObject) {
try {
Class<?>[] parameterTypes = providerMethod.getParameterTypes();
int bindParameterCount = parameterTypes.length - (providerContext == null ? 0 : 1);
String sql;
if (parameterTypes.length == 0) {
sql = (String) providerMethod.invoke(providerType.newInstance());
} else if (parameterTypes.length == 1 &&
(parameterObject == null || parameterTypes[0].isAssignableFrom(parameterObject.getClass()))) {
sql = (String) providerMethod.invoke(providerType.newInstance(), parameterObject);
} else if (bindParameterCount == 0) {
sql = (String) providerMethod.invoke(providerType.newInstance(), providerContext);
} else if (bindParameterCount == 1 &&
(parameterObject == null || parameterTypes[(providerContextIndex == null || providerContextIndex == 1) ? 0 : 1].isAssignableFrom(parameterObject.getClass()))) {
sql = (String) providerMethod.invoke(providerType.newInstance(), extractProviderMethodArguments(parameterObject));
} else if (parameterObject instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> params = (Map<String, Object>) parameterObject;
Expand All @@ -91,7 +121,7 @@ private SqlSource createSqlSource(Object parameterObject) {
throw new BuilderException("Error invoking SqlProvider method ("
+ providerType.getName() + "." + providerMethod.getName()
+ "). Cannot invoke a method that holds "
+ (parameterTypes.length == 1 ? "named argument(@Param)": "multiple arguments")
+ (bindParameterCount == 1 ? "named argument(@Param)": "multiple arguments")
+ " using a specifying parameterObject. In this case, please specify a 'java.util.Map' object.");
}
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
Expand All @@ -105,10 +135,25 @@ private SqlSource createSqlSource(Object parameterObject) {
}
}

private Object[] extractProviderMethodArguments(Object parameterObject) {
if (providerContext != null) {
Object[] args = new Object[2];
args[providerContextIndex == 0 ? 1 : 0] = parameterObject;
args[providerContextIndex] = providerContext;
return args;
} else {
return new Object[] { parameterObject };
}
}

private Object[] extractProviderMethodArguments(Map<String, Object> params, String[] argumentNames) {
Object[] args = new Object[argumentNames.length];
for (int i = 0; i < args.length; i++) {
args[i] = params.get(argumentNames[i]);
if (providerContextIndex != null && providerContextIndex == i) {
args[i] = providerContext;
} else {
args[i] = params.get(argumentNames[i]);
}
}
return args;
}
Expand Down
5 changes: 3 additions & 2 deletions src/site/es/xdoc/java-api.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2009-2016 the original author or authors.
Copyright 2009-2017 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 @@ -445,7 +445,8 @@ try (SqlSession session = sqlSessionFactory.openSession()) {
<li><code>&lt;select&gt;</code></li>
</ul>
</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. Cuando se ejecute el método MyBatis instanciará la clase y ejecutará el método especificados en el provider. El método puede opcionalmente recibir el objeto parámetro.(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>
<td>Estas anotaciones SQL alternativas te permiten especificar un nombre de clases y un método que devolverán la SQL que debe ejecutarse. 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>
</tr>
<tr>
<td><code>@Param</code></td>
Expand Down
4 changes: 2 additions & 2 deletions src/site/ja/xdoc/java-api.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2009-2016 the original author or authors.
Copyright 2009-2017 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 @@ -465,7 +465,7 @@ try (SqlSession session = sqlSessionFactory.openSession()) {
</ul>
</td>
<td>これらのアノテーションは動的 SQL を生成するためのものです。実行時に指定されたメソッドが呼び出され、メソッドから返された SQL ステートメントが実行されます。マップドステートメントを実行する際、プロバイダーによって指定したクラスのインスタンスが作成され、指定されたメソッドが実行されます。
メソッドに引数を渡すことができます。(MyBatis 3.4以降では、複数の引数を渡すことができます)
なお、メソッド引数には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 を構築するためのクラスについて説明します。
</td>
</tr>
Expand Down
7 changes: 4 additions & 3 deletions src/site/ko/xdoc/java-api.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2009-2016 the original author or authors.
Copyright 2009-2017 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 @@ -580,8 +580,9 @@ try (SqlSession session = sqlSessionFactory.openSession()) {
</ul>
</td>
<td>실행시 SQL 을 리턴할 클래스 과 메소드명을 명시하도록 해주는 대체수단의 애노테이션이다.
매핑된 구문을 실행할 때 마이바티스는 클래스의 인스턴스를 만들고 메소드를 실행한다.
메소드는 파라미터 객체를 받을 수도 있다.(마이바티스 3.4이상에서는 복수 파라미터를 허용한다.)
매핑된 구문을 실행할 때 마이바티스는 클래스의 인스턴스를 만들고 메소드를 실행한다.
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.(마이바티스 3.4이상에서는 복수 파라미터를 허용한다.)
사용가능한 속성들 : type, method.
type 속성은 클래스.
method 속성은 메소드명이다.
Expand Down
6 changes: 4 additions & 2 deletions src/site/xdoc/java-api.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2009-2016 the original author or authors.
Copyright 2009-2017 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 @@ -498,7 +498,9 @@ try (SqlSession session = sqlSessionFactory.openSession()) {
<td>Allows for creation of dynamic SQL. These alternative SQL annotations allow you to specify a class and
a method name that will return the SQL to run at execution time. Upon executing the mapped statement, MyBatis will
instantiate the class, and execute the method, as specified by the provider.
The method can optionally accept parameter objects.(In MyBatis 3.4 or later, it's allow multiple parameters)
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)
Attributes: <code>type</code>, <code>method</code>. The <code>type</code> attribute is a class.
The <code>method</code> is the name of the method on that class. <span class="label important">NOTE</span>
Following this section is a discussion about the class, which can help build dynamic SQL in a cleaner, easier to read way.</td>
Expand Down
6 changes: 4 additions & 2 deletions src/site/zh/xdoc/java-api.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2009-2016 the original author or authors.
Copyright 2009-2017 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 @@ -752,7 +752,9 @@ resultSets=””。
MyBatis
会实例化这个类,然后执行由 provider
指定的方法.
该方法可以有选择地接受参数对象.(In MyBatis 3.4 or later, it's allow multiple parameters)
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)
属性: type,method。type 属性是类。method 属性是方法名。
注意:
这节之后是对 类的
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* Copyright 2009-2017 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 org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.SelectProvider;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;

public interface BaseMapper<T> {

@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdProviderContextOnly")
@ContainsLogicalDelete
T selectById(Integer id);

@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdProviderContextOnly")
T selectActiveById(Integer id);

@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByNameOneParamAndProviderContext")
@ContainsLogicalDelete
List<T> selectByName(String name);

@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByNameOneParamAndProviderContext")
List<T> selectActiveByName(String name);

@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdAndNameMultipleParamAndProviderContextWithAtParam")
@ContainsLogicalDelete
List<T> selectByIdAndNameWithAtParam(@Param("id") Integer id, @Param("name") String name);

@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdAndNameMultipleParamAndProviderContextWithAtParam")
List<T> selectActiveByIdAndNameWithAtParam(@Param("id") Integer id, @Param("name") String name);

@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdAndNameMultipleParamAndProviderContext")
@ContainsLogicalDelete
List<T> selectByIdAndName(Integer id, String name);

@SelectProvider(type= OurSqlBuilder.class, method= "buildSelectByIdAndNameMultipleParamAndProviderContext")
List<T> selectActiveByIdAndName(Integer id, String name);

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface ContainsLogicalDelete {
boolean value() default false;
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Meta {
String tableName();
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--
-- Copyright 2009-2016 the original author or authors.
-- Copyright 2009-2017 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 All @@ -18,11 +18,12 @@ drop table users if exists;

create table users (
id int,
name varchar(20)
name varchar(20),
logical_delete boolean default false
);

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) values(4, 'User4');
insert into users (id, name, logical_delete) values(4, 'User4', true);

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2009-2016 the original author or authors.
* Copyright 2009-2017 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 All @@ -24,7 +24,8 @@
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.UpdateProvider;

public interface Mapper {
@BaseMapper.Meta(tableName = "users")
public interface Mapper extends BaseMapper<User> {
@SelectProvider(type = OurSqlBuilder.class, method = "buildGetUsersQuery")
List<User> getUsers(List<Integer> allFilterIds);

Expand Down
Loading

0 comments on commit 50cc787

Please sign in to comment.