Mybatisブロッキングの原理とページングの実現
転入先http://blog.csdn.net/u012089657/article/details/49763631
Mybatis ブロッキングの紹介
1.1目次
1.2はじめに
1.3 Interceptorインタフェース
1.4ブロッカーの登録
1.5 Mybatisでブロックできる方法
1.6ブロッキングによるページング
ブロックの1つの役割は、いくつかのメソッドの呼び出しをブロックすることができ、ブロックされたメソッドの実行前後にいくつかのロジックを追加するか、ブロックされたメソッドを実行するときにブロックされたメソッドを実行せずに自分のロジックを実行するかを選択することができます.Mybatisブロッカーの設計の目的の一つは、Mybatis固有の論理を動かすことなく、ユーザーがいつか自分の論理を実現できるようにすることです.たとえば、Executorの場合、Mybatisにはいくつかの実装があります.BatchExecutor、ReuseExecutor、SimpleExecutor、CachingExecutorです.このとき、Executorインタフェースに対するqueryメソッドがあなたの要求を満たすことができないと思ったら、どうしますか?ソースコードを変更しますか?もちろんいいえ.ExecutorインタフェースをブロックするためのMybatisブロッカーのqueryメソッドを確立し、ブロッキング後に独自のqueryメソッドロジックを実現し、その後、元のqueryメソッドを実行し続けるかどうかを選択することができます.
インターセプタMybatisにはInterceptorインタフェースが用意されており、このインタフェースを実現することで独自のインターセプタを定義することができます.まず、このインタフェースの定義を見てみましょう.
このインタフェースでは、intercept、plugin、setPropertiesの3つのメソッドが定義されていることがわかります.pluginメソッドは、ターゲットオブジェクトをカプセル化するためにブロックされます.このメソッドでは、ターゲットオブジェクト自体に戻ることも、エージェントに戻ることもできます.エージェントが返されると、そのメソッドをブロックしてinterceptメソッドを呼び出すことができます.もちろん、他のメソッドを呼び出すこともできます.この点は後述します.setPropertiesメソッドは、Mybatisプロファイルでプロパティを指定するために使用されます.
自分のInterceptorを定義する上で最も重要なのはpluginメソッドとinterceptメソッドを実現することであり、pluginメソッドではブロックするかどうかを決定し、どのようなターゲットオブジェクトを返すかを決定することができます.interceptメソッドは,ブロックするときに実行するメソッドである.
plugin法では,Mybatisはすでに実装を提供している.MybatisにはPluginというクラスがあり、その中には静的メソッドwrap(Object target,Interceptor interceptor)があり、このメソッドで戻るオブジェクトがターゲットオブジェクトか対応するエージェントかを決定できます.ここではまずPluginのソースコードを見てみましょう.
まずPluginのwrapメソッドを見てみましょう.現在のInterceptorの上の注釈に基づいて、どのインタフェースがブロックする必要があるかを定義し、現在のターゲットオブジェクトに対応するブロックが必要なインタフェースがあるかどうかを判断し、なければターゲットオブジェクト自体に戻り、あればエージェントオブジェクトに戻ります.このエージェントオブジェクトのInvocationHandlerはPluginです.したがって,ターゲットオブジェクトがインタフェースメソッドを実行している間にエージェントオブジェクトによって実行されている場合,対応するInvocationHandlerのinvokeメソッド,すなわちPluginのinvokeメソッドが呼び出される.次に、このinvokeメソッドの内容を見てみましょう.ここでinvokeメソッドの論理は,現在実行されているメソッドがブロックを必要とするメソッドを定義している場合,ターゲットオブジェクト,実行するメソッドおよびメソッドパラメータを1つのInvocationオブジェクトにカプセル化し,カプセル化されたInvocationをパラメータとして現在のブロックのinterceptメソッドに渡すことである.ブロックする必要がない場合は、現在のメソッドを直接呼び出します.Invocationでは、現在のメソッドを呼び出す論理を持つproceedメソッドが定義されているので、interceptで現在のメソッドを呼び出し続ける必要がある場合はinvocationのproccedメソッドを呼び出すことができます.
これがMybatisでInterceptorブロックを実現する一つの考え方であり,ユーザがこの考えに問題があるか,あるいはあなたの要求を完全に満たすことができないと感じたら,自分のPluginを実現することによって,エージェントがいつブロックする必要があるかを決定することができる.以下の説明は、Mybatisのデフォルト実装、すなわちPluginによるInterceptorの管理に基づいて説明します.
独自のInterceptorを実装するには2つの重要な注記があります.1つは@Interceptsで、その値は@Signature配列です.@Interceptsは、現在のオブジェクトがInterceptorであることを示し、@Signatureは、ブロックするインタフェース、メソッド、および対応するパラメータタイプを示します.カスタムの簡単なInterceptorを見てみましょう.
まずsetPropertiesメソッドを見てみましょう.このメソッドはコンフィギュレーションが現在のInterceptorを初期化するときに実行されます.ここでは簡単に2つの属性を取って印刷するだけです.
次にplugin法ではPluginの論理を用いてMybatisの論理を実現している.
次にMyInterceptorクラスで@InterceptsでこれがInterceptorであることをマークし、@Interceptsで2つの@Signature、すなわち2つのブロックポイントを定義します.最初の@Signatureでは、このInterceptorがExecutorインタフェースのパラメータタイプがMappedStatement、Object、RowBounds、ResultHandlerであることをブロックするqueryメソッドを定義しました.2番目の@Signatureでは、このInterceptorがStatementHandlerのパラメータタイプがConnectionであるprepareメソッドをブロックすることを定義します.
最後にinterceptメソッドを見てみましょう.ここでは簡単に一言印刷してinvocationのproceedメソッドを呼び出し、現在のメソッドを正常に呼び出すだけです.
このブロッキングに対して、Mybatisは、ブロッキングを登録する際に定義されたn個のpropertyをパラメータとしてブロッキングのsetPropertiesメソッドを呼び出す.その後、ブロッキング可能なオブジェクトを新規作成するときに、ターゲットオブジェクト自体を返すかプロキシオブジェクトを返すかを決定するために、ブロッキングのpluginメソッドが呼び出されます.このブロッキングでは、MybatisがExecutorオブジェクトまたはStatementHandlerオブジェクトである場合、元のターゲットオブジェクト自体が返されます.次に、Executorエージェントオブジェクトが、パラメータタイプがMappedStatement、Object、RowBounds、ResultHandlerのqueryメソッド、またはStatementHandlerエージェントオブジェクトが、パラメータタイプがConnectionのprepareメソッドを実行すると、現在のブロッキングのinterceptメソッドがトリガーされます.この2つのインタフェースオブジェクトを実行する他の方法は、単純なエージェントにすぎません.
登録ブロッカーは、Mybatisプロファイルのplugins要素の下にあるplugin要素によって行われます.1つのpluginはブロックに対応し、plugin要素の下にいくつかのpropertyサブ要素を指定できます.Mybatisは、定義されたブロッキングを登録する際に、対応するブロッキングの下にあるすべてのpropertyをInterceptorのsetPropertiesメソッドで対応するブロッキングに注入します.前に定義したMyInterceptorを登録することができます
Mybatisブロッカーは、Executor、StatementHandler、ParameterHandler、ResultSet Handlerの4種類のインタフェースしかブロックできません.これはMybatisのコンフィギュレーションで死んだもので、他のインタフェースをブロックするのをサポートするにはMybatisのコンフィギュレーションを書き換える必要があります.Mybatisは、この4つのインタフェースのすべての方法をブロックすることができます.
Mybatisブロッキングの実際の応用について説明します.Mybatisブロッキングは、ページング処理に使用されることが多い.JDBCを使用してデータベースを操作するには、対応するStatementオブジェクトが必要であることを知っています.Mybatisは、Sql文を実行する前に、Sql文を含むStatementオブジェクトを生成し、対応するSql文はStatementの前に生成されるので、Statementになる前にStatementを生成するためのSql文に手をつけることができます.MybatisではStatement文はRoutingStatementHandlerオブジェクトのprepareメソッドによって生成されます.したがって、インターセプタを用いてMybatisページングを実現する一つの考え方は、StatementHandlerインタフェースのprepareメソッドをブロックし、インターセプタメソッドでSql文を対応するページングクエリSql文に変更した後、StatementHandlerオブジェクトのprepareメソッド、すなわちinvocation.proceed()を呼び出すことである.Sql文を変更するのは簡単そうに見えますが、実際にはそれほど直感的ではありません.sqlなどの他の属性を含む複数の属性は対応する方法がなく、外部に対して閉じられており、オブジェクトのプライベート属性であるため、ここでは反射メカニズムを導入してオブジェクトのプライベート属性の値を取得または変更する必要があります.ページングでは、ブロッカーの中で私たちが常にしなければならない操作の一つは、現在の条件を満たす記録が全部でどれだけあるかを統計することです.これは、元のSql文を取得した後、対応する統計文に変更してMybatisでカプセル化されたパラメータと設定パラメータの機能を利用してSql文のパラメータを置き換えることです.その後,クエリレコード数のSql文を実行して合計レコード数の統計を行う.まず、ページング操作をカプセル化したエンティティクラスPageを見てみましょう.
ページングが必要なMapperマッピングについては、パラメータとしてPageオブジェクトを渡します.Pageオブジェクトには、ブロッカーで使用できるページングの基本情報が含まれていることがわかります.次に、ページングの基本情報以外のパラメータを1つのMapオブジェクトでパッケージします.これにより、Mapperマッピング文の他のパラメータはMapから値を取ることができます.次に私たちのPageInterceptorの定義を見てみましょう.PageInterceptorについてはあまり説明しません.コードには詳細な注釈情報が付いています.
次にMybatisのプロファイルにブロッカーを登録します.
これでブロックが定義され、構成されました.次にテストします.我々のUserMapper.xmlには次のようなMapperマッピング情報があると仮定します.
では、このようにしてテストすることができます.
Mybatis ブロッキングの紹介
1.1目次
1.2はじめに
1.3 Interceptorインタフェース
1.4ブロッカーの登録
1.5 Mybatisでブロックできる方法
1.6ブロッキングによるページング
ブロックの1つの役割は、いくつかのメソッドの呼び出しをブロックすることができ、ブロックされたメソッドの実行前後にいくつかのロジックを追加するか、ブロックされたメソッドを実行するときにブロックされたメソッドを実行せずに自分のロジックを実行するかを選択することができます.Mybatisブロッカーの設計の目的の一つは、Mybatis固有の論理を動かすことなく、ユーザーがいつか自分の論理を実現できるようにすることです.たとえば、Executorの場合、Mybatisにはいくつかの実装があります.BatchExecutor、ReuseExecutor、SimpleExecutor、CachingExecutorです.このとき、Executorインタフェースに対するqueryメソッドがあなたの要求を満たすことができないと思ったら、どうしますか?ソースコードを変更しますか?もちろんいいえ.ExecutorインタフェースをブロックするためのMybatisブロッカーのqueryメソッドを確立し、ブロッキング後に独自のqueryメソッドロジックを実現し、その後、元のqueryメソッドを実行し続けるかどうかを選択することができます.
インターセプタMybatisにはInterceptorインタフェースが用意されており、このインタフェースを実現することで独自のインターセプタを定義することができます.まず、このインタフェースの定義を見てみましょう.
package org.apache.ibatis.plugin;
import java.util.Properties;
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
このインタフェースでは、intercept、plugin、setPropertiesの3つのメソッドが定義されていることがわかります.pluginメソッドは、ターゲットオブジェクトをカプセル化するためにブロックされます.このメソッドでは、ターゲットオブジェクト自体に戻ることも、エージェントに戻ることもできます.エージェントが返されると、そのメソッドをブロックしてinterceptメソッドを呼び出すことができます.もちろん、他のメソッドを呼び出すこともできます.この点は後述します.setPropertiesメソッドは、Mybatisプロファイルでプロパティを指定するために使用されます.
自分のInterceptorを定義する上で最も重要なのはpluginメソッドとinterceptメソッドを実現することであり、pluginメソッドではブロックするかどうかを決定し、どのようなターゲットオブジェクトを返すかを決定することができます.interceptメソッドは,ブロックするときに実行するメソッドである.
plugin法では,Mybatisはすでに実装を提供している.MybatisにはPluginというクラスがあり、その中には静的メソッドwrap(Object target,Interceptor interceptor)があり、このメソッドで戻るオブジェクトがターゲットオブジェクトか対応するエージェントかを決定できます.ここではまずPluginのソースコードを見てみましょう.
package org.apache.ibatis.plugin;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.reflection.ExceptionUtil;
public class Plugin implements InvocationHandler {
private Object target;
private Interceptor interceptor;
private Map, Set> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map, Set> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
public static Object wrap(Object target, Interceptor interceptor) {
Map, Set> signatureMap = getSignatureMap(interceptor);
Class> type = target.getClass();
Class>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
private static Map, Set> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
if (interceptsAnnotation == null) { // issue #251
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map, Set> signatureMap = new HashMap, Set>();
for (Signature sig : sigs) {
Set methods = signatureMap.get(sig.type());
if (methods == null) {
methods = new HashSet();
signatureMap.put(sig.type(), methods);
}
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
private static Class>[] getAllInterfaces(Class> type, Map, Set> signatureMap) {
Set> interfaces = new HashSet>();
while (type != null) {
for (Class> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class>[interfaces.size()]);
}
}
まずPluginのwrapメソッドを見てみましょう.現在のInterceptorの上の注釈に基づいて、どのインタフェースがブロックする必要があるかを定義し、現在のターゲットオブジェクトに対応するブロックが必要なインタフェースがあるかどうかを判断し、なければターゲットオブジェクト自体に戻り、あればエージェントオブジェクトに戻ります.このエージェントオブジェクトのInvocationHandlerはPluginです.したがって,ターゲットオブジェクトがインタフェースメソッドを実行している間にエージェントオブジェクトによって実行されている場合,対応するInvocationHandlerのinvokeメソッド,すなわちPluginのinvokeメソッドが呼び出される.次に、このinvokeメソッドの内容を見てみましょう.ここでinvokeメソッドの論理は,現在実行されているメソッドがブロックを必要とするメソッドを定義している場合,ターゲットオブジェクト,実行するメソッドおよびメソッドパラメータを1つのInvocationオブジェクトにカプセル化し,カプセル化されたInvocationをパラメータとして現在のブロックのinterceptメソッドに渡すことである.ブロックする必要がない場合は、現在のメソッドを直接呼び出します.Invocationでは、現在のメソッドを呼び出す論理を持つproceedメソッドが定義されているので、interceptで現在のメソッドを呼び出し続ける必要がある場合はinvocationのproccedメソッドを呼び出すことができます.
これがMybatisでInterceptorブロックを実現する一つの考え方であり,ユーザがこの考えに問題があるか,あるいはあなたの要求を完全に満たすことができないと感じたら,自分のPluginを実現することによって,エージェントがいつブロックする必要があるかを決定することができる.以下の説明は、Mybatisのデフォルト実装、すなわちPluginによるInterceptorの管理に基づいて説明します.
独自のInterceptorを実装するには2つの重要な注記があります.1つは@Interceptsで、その値は@Signature配列です.@Interceptsは、現在のオブジェクトがInterceptorであることを示し、@Signatureは、ブロックするインタフェース、メソッド、および対応するパラメータタイプを示します.カスタムの簡単なInterceptorを見てみましょう.
package com.tiantian.mybatis.interceptor;
import java.sql.Connection;import java.util.Properties;
import org.apache.ibatis.executor.Executor;import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.plugin.Interceptor;import org.apache.ibatis.plugin.Intercepts;import org.apache.ibatis.plugin.Invocation;import org.apache.ibatis.plugin.Plugin;import org.apache.ibatis.plugin.Signature;import org.apache.ibatis.session.ResultHandler;import org.apache.ibatis.session.RowBounds;
@Intercepts( { @Signature(method = "query", type = Executor.class, args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class }),
@Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })public class MyInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
Object result = invocation.proceed();
System.out.println("Invocation.proceed()"); return result;
}
public Object plugin(Object target) { return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
String prop1 = properties.getProperty("prop1");
String prop2 = properties.getProperty("prop2");
System.out.println(prop1 + "------" + prop2);
}
}
まずsetPropertiesメソッドを見てみましょう.このメソッドはコンフィギュレーションが現在のInterceptorを初期化するときに実行されます.ここでは簡単に2つの属性を取って印刷するだけです.
次にplugin法ではPluginの論理を用いてMybatisの論理を実現している.
次にMyInterceptorクラスで@InterceptsでこれがInterceptorであることをマークし、@Interceptsで2つの@Signature、すなわち2つのブロックポイントを定義します.最初の@Signatureでは、このInterceptorがExecutorインタフェースのパラメータタイプがMappedStatement、Object、RowBounds、ResultHandlerであることをブロックするqueryメソッドを定義しました.2番目の@Signatureでは、このInterceptorがStatementHandlerのパラメータタイプがConnectionであるprepareメソッドをブロックすることを定義します.
最後にinterceptメソッドを見てみましょう.ここでは簡単に一言印刷してinvocationのproceedメソッドを呼び出し、現在のメソッドを正常に呼び出すだけです.
このブロッキングに対して、Mybatisは、ブロッキングを登録する際に定義されたn個のpropertyをパラメータとしてブロッキングのsetPropertiesメソッドを呼び出す.その後、ブロッキング可能なオブジェクトを新規作成するときに、ターゲットオブジェクト自体を返すかプロキシオブジェクトを返すかを決定するために、ブロッキングのpluginメソッドが呼び出されます.このブロッキングでは、MybatisがExecutorオブジェクトまたはStatementHandlerオブジェクトである場合、元のターゲットオブジェクト自体が返されます.次に、Executorエージェントオブジェクトが、パラメータタイプがMappedStatement、Object、RowBounds、ResultHandlerのqueryメソッド、またはStatementHandlerエージェントオブジェクトが、パラメータタイプがConnectionのprepareメソッドを実行すると、現在のブロッキングのinterceptメソッドがトリガーされます.この2つのインタフェースオブジェクトを実行する他の方法は、単純なエージェントにすぎません.
登録ブロッカーは、Mybatisプロファイルのplugins要素の下にあるplugin要素によって行われます.1つのpluginはブロックに対応し、plugin要素の下にいくつかのpropertyサブ要素を指定できます.Mybatisは、定義されたブロッキングを登録する際に、対応するブロッキングの下にあるすべてのpropertyをInterceptorのsetPropertiesメソッドで対応するブロッキングに注入します.前に定義したMyInterceptorを登録することができます
configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
Mybatisブロッカーは、Executor、StatementHandler、ParameterHandler、ResultSet Handlerの4種類のインタフェースしかブロックできません.これはMybatisのコンフィギュレーションで死んだもので、他のインタフェースをブロックするのをサポートするにはMybatisのコンフィギュレーションを書き換える必要があります.Mybatisは、この4つのインタフェースのすべての方法をブロックすることができます.
Mybatisブロッキングの実際の応用について説明します.Mybatisブロッキングは、ページング処理に使用されることが多い.JDBCを使用してデータベースを操作するには、対応するStatementオブジェクトが必要であることを知っています.Mybatisは、Sql文を実行する前に、Sql文を含むStatementオブジェクトを生成し、対応するSql文はStatementの前に生成されるので、Statementになる前にStatementを生成するためのSql文に手をつけることができます.MybatisではStatement文はRoutingStatementHandlerオブジェクトのprepareメソッドによって生成されます.したがって、インターセプタを用いてMybatisページングを実現する一つの考え方は、StatementHandlerインタフェースのprepareメソッドをブロックし、インターセプタメソッドでSql文を対応するページングクエリSql文に変更した後、StatementHandlerオブジェクトのprepareメソッド、すなわちinvocation.proceed()を呼び出すことである.Sql文を変更するのは簡単そうに見えますが、実際にはそれほど直感的ではありません.sqlなどの他の属性を含む複数の属性は対応する方法がなく、外部に対して閉じられており、オブジェクトのプライベート属性であるため、ここでは反射メカニズムを導入してオブジェクトのプライベート属性の値を取得または変更する必要があります.ページングでは、ブロッカーの中で私たちが常にしなければならない操作の一つは、現在の条件を満たす記録が全部でどれだけあるかを統計することです.これは、元のSql文を取得した後、対応する統計文に変更してMybatisでカプセル化されたパラメータと設定パラメータの機能を利用してSql文のパラメータを置き換えることです.その後,クエリレコード数のSql文を実行して合計レコード数の統計を行う.まず、ページング操作をカプセル化したエンティティクラスPageを見てみましょう.
import java.util.HashMap;import java.util.List;import java.util.Map;
/**
*
*/public class Page {
private int pageNo = 1;// ,
private int pageSize = 15;// , 15
private int totalRecord;//
private int totalPage;//
private List results;//
private Map params = new HashMap();// Map
public int getPageNo() { return pageNo;
}
public void setPageNo(int pageNo) { this.pageNo = pageNo;
}
public int getPageSize() { return pageSize;
}
public void setPageSize(int pageSize) { this.pageSize = pageSize;
}
public int getTotalRecord() { return totalRecord;
}
public void setTotalRecord(int totalRecord) { this.totalRecord = totalRecord; // , , 。
int totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : totalRecord/pageSize + 1; this.setTotalPage(totalPage);
}
public int getTotalPage() { return totalPage;
}
public void setTotalPage(int totalPage) { this.totalPage = totalPage;
}
public List getResults() { return results;
}
public void setResults(List results) { this.results = results;
}
public Map getParams() { return params;
}
public void setParams(Map params) { this.params = params;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Page [pageNo=").append(pageNo).append(", pageSize=")
.append(pageSize).append(", results=").append(results).append( ", totalPage=").append(totalPage).append( ", totalRecord=").append(totalRecord).append("]"); return builder.toString();
}
}
ページングが必要なMapperマッピングについては、パラメータとしてPageオブジェクトを渡します.Pageオブジェクトには、ブロッカーで使用できるページングの基本情報が含まれていることがわかります.次に、ページングの基本情報以外のパラメータを1つのMapオブジェクトでパッケージします.これにより、Mapperマッピング文の他のパラメータはMapから値を取ることができます.次に私たちのPageInterceptorの定義を見てみましょう.PageInterceptorについてはあまり説明しません.コードには詳細な注釈情報が付いています.
package com.tiantian.mybatis.interceptor;
import java.lang.reflect.Field;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import java.util.List;import java.util.Properties;
import org.apache.ibatis.executor.parameter.ParameterHandler;import org.apache.ibatis.executor.statement.RoutingStatementHandler;import org.apache.ibatis.executor.statement.StatementHandler;import org.apache.ibatis.mapping.BoundSql;import org.apache.ibatis.mapping.MappedStatement;import org.apache.ibatis.mapping.ParameterMapping;import org.apache.ibatis.plugin.Interceptor;import org.apache.ibatis.plugin.Intercepts;import org.apache.ibatis.plugin.Invocation;import org.apache.ibatis.plugin.Plugin;import org.apache.ibatis.plugin.Signature;import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import com.tiantian.mybatis.model.Page;
/**
*
* , , 。
* Mybatis :
* JDBC Statement ,Mybatis Sql Sql Statement , Sql
* Statement , Statement Statement Sql 。 Mybatis Statement RoutingStatementHandler
* prepare 。 Mybatis StatementHandler prepare , Sql Sql ,
* StatementHandler prepare , invocation.proceed()。
* , , Sql , Mybatis
* Sql , Sql 。
*
*/@Intercepts( { @Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class}) })public class PageInterceptor implements Interceptor {
private String databaseType;// ,
/**
*
*/
public Object intercept(Invocation invocation) throws Throwable { // StatementHandler , RoutingStatementHandler, BaseStatementHandler,
//BaseStatementHandler , SimpleStatementHandler,PreparedStatementHandler CallableStatementHandler,
//SimpleStatementHandler Statement ,PreparedStatementHandler PreparedStatement , CallableStatementHandler
// CallableStatement 。Mybatis Sql RoutingStatementHandler, RoutingStatementHandler
//StatementHandler delegate ,RoutingStatementHandler Statement BaseStatementHandler, SimpleStatementHandler、
//PreparedStatementHandler CallableStatementHandler, RoutingStatementHandler StatementHandler delegate 。
// PageInterceptor @Signature Interceptor StatementHandler prepare , Mybatis RoutingStatementHandler
// Interceptor plugin , RoutingStatementHandler 。
RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget(); // RoutingStatementHandler delegate
StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate"); // StatementHandler boundSql, handler.getBoundSql() delegate.getBoundSql() ,
//RoutingStatementHandler StatementHandler delegate 。
BoundSql boundSql = delegate.getBoundSql(); // Sql , Mapper
Object obj = boundSql.getParameterObject(); // Page 。
if (obj instanceof Page>) {
Page> page = (Page>) obj; // delegate BaseStatementHandler mappedStatement
MappedStatement mappedStatement = (MappedStatement)ReflectUtil.getFieldValue(delegate, "mappedStatement"); // prepare Connection
Connection connection = (Connection)invocation.getArgs()[0]; // Sql , Mapper Sql
String sql = boundSql.getSql(); // page
this.setTotalRecord(page,
mappedStatement, connection); // Sql
String pageSql = this.getPageSql(page, sql); // BoundSql sql Sql
ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
} return invocation.proceed();
}
/**
*
*/
public Object plugin(Object target) { return Plugin.wrap(target, this);
}
/**
*
*/
public void setProperties(Properties properties) { this.databaseType = properties.getProperty("databaseType");
}
/**
* page Sql , ,Mysql Oracle
*
*
* @param page
* @param sql sql
* @return
*/
private String getPageSql(Page> page, String sql) {
StringBuffer sqlBuffer = new StringBuffer(sql); if ("mysql".equalsIgnoreCase(databaseType)) { return getMysqlPageSql(page, sqlBuffer);
} else if ("oracle".equalsIgnoreCase(databaseType)) { return getOraclePageSql(page, sqlBuffer);
} return sqlBuffer.toString();
}
/**
* Mysql
* @param page
* @param sqlBuffer sql StringBuffer
* @return Mysql
*/
private String getMysqlPageSql(Page> page, StringBuffer sqlBuffer) { // ,Mysql 0 。
int offset = (page.getPageNo() - 1) * page.getPageSize();
sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize()); return sqlBuffer.toString();
}
/**
* Oracle
* @param page
* @param sqlBuffer sql StringBuffer
* @return Oracle
*/
private String getOraclePageSql(Page> page, StringBuffer sqlBuffer) { // ,Oracle rownum , rownum 1
int offset = (page.getPageNo() - 1) * page.getPageSize() + 1;
sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum = ").append(offset); // Sql :
//select * from (select u.*, rownum r from (select * from t_user) u where rownum = 16
return sqlBuffer.toString();
}
/**
* page
*
* @param page Mapper
* @param mappedStatement Mapper
* @param connection
*/
private void setTotalRecord(Page> page,
MappedStatement mappedStatement, Connection connection) { // BoundSql, BoundSql StatementHandler BoundSql 。
//delegate boundSql mappedStatement.getBoundSql(paramObj) 。
BoundSql boundSql = mappedStatement.getBoundSql(page); // Mapper Sql
String sql = boundSql.getSql(); // Sql sql
String countSql = this.getCountSql(sql); // BoundSql
List parameterMappings = boundSql.getParameterMappings(); // Configuration、 Sql countSql、 parameterMappings page BoundSql 。
BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page); // mappedStatement、 page BoundSql countBoundSql ParameterHandler
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql); // connection countSql PreparedStatement 。
PreparedStatement pstmt = null;
ResultSet rs = null; try {
pstmt = connection.prepareStatement(countSql); // parameterHandler PreparedStatement
parameterHandler.setParameters(pstmt); // Sql 。
rs = pstmt.executeQuery(); if (rs.next()) { int totalRecord = rs.getInt(1); // page
page.setTotalRecord(totalRecord);
}
} catch (SQLException e) {
e.printStackTrace();
} finally { try { if (rs != null)
rs.close(); if (pstmt != null)
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* Sql Sql
* @param sql
* @return
*/
private String getCountSql(String sql) { int index = sql.indexOf("from"); return "select count(*) " + sql.substring(index);
}
/**
*
*
*/
private static class ReflectUtil {
/**
*
* @param obj
* @param fieldName
* @return
*/
public static Object getFieldValue(Object obj, String fieldName) {
Object result = null;
Field field = ReflectUtil.getField(obj, fieldName); if (field != null) {
field.setAccessible(true); try {
result = field.get(obj);
} catch (IllegalArgumentException e) { // TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) { // TODO Auto-generated catch block
e.printStackTrace();
}
} return result;
}
/**
*
* @param obj
* @param fieldName
* @return
*/
private static Field getField(Object obj, String fieldName) {
Field field = null; for (Class> clazz=obj.getClass(); clazz != Object.class; clazz=clazz.getSuperclass()) { try {
field = clazz.getDeclaredField(fieldName); break;
} catch (NoSuchFieldException e) { // , , null。
}
} return field;
}
/**
*
* @param obj
* @param fieldName
* @param fieldValue
*/
public static void setFieldValue(Object obj, String fieldName,
String fieldValue) {
Field field = ReflectUtil.getField(obj, fieldName); if (field != null) { try {
field.setAccessible(true);
field.set(obj, fieldValue);
} catch (IllegalArgumentException e) { // TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) { // TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
次にMybatisのプロファイルにブロッカーを登録します.
configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
これでブロックが定義され、構成されました.次にテストします.我々のUserMapper.xmlには次のようなMapperマッピング情報があると仮定します.
では、このようにしてテストすることができます.
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Page page = new Page();
page.setPageNo(2);
List users = userMapper.findPage(page);
page.setResults(users);
System.out.println(page);
} finally {
sqlSession.close();
}