カスタムプラグインの実装手順
MyBatisプラグインを実装する際の基本的な手順は以下の通りです。
インターセプタの作成
まず、Interceptorインターフェースを実装するクラスを作成します。
public class PaginationInterceptor implements Interceptor {
// 実装内容
}
主要メソッドの実装
インターセプタクラスでは以下の3つのメソッドを実装します。
intercept(): 実際の処理が行われるコアメソッドplugin(): プロキシオブジェクトの生成setProperties(): 設定値の受け取り
@Override
public Object intercept(Invocation context) throws Throwable {
// 拡張処理の実装
return context.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties config) {
// 設定値の処理
}
インターセプト対象の指定
アノテーションを使用して、どのオブジェクトのどのメソッドをインターセプトするかを定義します。
@Intercepts({
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
プラグインの登録方法
MyBatisの設定ファイルにプラグインを登録します。
<plugins>
<plugin interceptor="com.example.PaginationInterceptor">
<property name="pageParam" value="pageNo"/>
<property name="sizeParam" value="pageSize"/>
<property name="enableCount" value="true"/>
</plugin>
</plugins>
プラグインの実行フロー
MyBatis起動時にXMLConfigBuilderがプラグイン設定を読み込み、InterceptorChainに登録します。
private void processPluginConfigurations(XNode pluginsNode) {
if (pluginsNode != null) {
for (XNode pluginNode : pluginsNode.getChildren()) {
String interceptorClass = pluginNode.getStringAttribute("interceptor");
Properties settings = pluginNode.getChildrenAsProperties();
Interceptor interceptor = (Interceptor)Class.forName(interceptorClass).newInstance();
interceptor.setProperties(settings);
configuration.registerPlugin(interceptor);
}
}
}
プロキシ生成の仕組み
MyBatisはプロキシパターンを活用して、プラグインによる処理を実現しています。
public Object createProxyChain(Object target) {
for (Interceptor interceptor : pluginRegistry) {
target = interceptor.wrap(target);
}
return target;
}
プロキシの生成処理はPluginクラスで行われます。
public static Object wrapTarget(Object original, Interceptor interceptor) {
Map<Class<?>, Set<Method>> methodSignatures = extractSignatures(interceptor);
Class<?> targetType = original.getClass();
Class<?>[] proxyInterfaces = determineProxyInterfaces(targetType, methodSignatures);
if (proxyInterfaces.length == 0) {
return original;
}
return Proxy.newProxyInstance(
targetType.getClassLoader(),
proxyInterfaces,
new PluginHandler(original, interceptor, methodSignatures)
);
}
PluginHandlerはInvocationHandlerを実装し、メソッド呼び出しを処理します。
public Object invoke(Object proxy, Method method, Object[] args) {
if (isTargetMethod(method)) {
return interceptor.intercept(new MethodInvocation(original, method, args));
}
return method.invoke(original, args);
}
ページネーションプラグインの動作原理
ページネーションプラグインはSQLを動的に書き換えることで機能します。
public String rewritePaginationSql(String originalSql, PageParams params) {
StringBuilder modifiedSql = new StringBuilder(originalSql);
modifiedSql.append(" LIMIT ").append(params.getOffset());
modifiedSql.append(", ").append(params.getLimit());
return modifiedSql.toString();
}
ページ情報はスレッドローカル変数に保持されます。
private static final ThreadLocal<PageParams> PAGE_CONTEXT = new ThreadLocal<>();
public static void setPageInfo(int pageNumber, int pageSize) {
PAGE_CONTEXT.set(new PageParams(pageNumber, pageSize));
}
public static PageParams getCurrentPage() {
return PAGE_CONTEXT.get();
}
Springとの統合メカニズム
MyBatisとSpringを統合する際の主要コンポーネントは以下の通りです。
SqlSessionFactoryBeanの役割
Springコンテナ内でSqlSessionFactoryを管理するためのBeanです。
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:mappers/**/*.xml"));
return factory;
}
}
SqlSessionFactoryBeanは以下のインターフェースを実装しています。
FactoryBean: カスタムインスタンス生成InitializingBean: 初期化処理ApplicationListener: アプリケーションイベントの監視
SqlSessionTemplateの重要性
SqlSessionTemplateはスレッドセーフなSqlSessionの実装です。
public class SqlSessionTemplate implements SqlSession {
private final SqlSession sqlSessionProxy;
public SqlSessionTemplate(SqlSessionFactory sessionFactory) {
this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
SqlSession.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionInterceptor(sessionFactory)
);
}
// データ操作メソッドはすべてsqlSessionProxy経由で呼び出し
}
インターセプタの実装:
private static class SqlSessionInterceptor implements InvocationHandler {
private final SqlSessionFactory sessionFactory;
public SqlSessionInterceptor(SqlSessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
try (SqlSession session = sessionFactory.openSession()) {
return method.invoke(session, args);
}
}
}
Mapperインターフェースの登録
SpringはMapperScannerConfigurerを使用して、MapperインターフェースをBeanとして登録します。
@Configuration
@MapperScan("com.example.repository")
public class MapperConfig {
// 設定なし
}
内部的にはMapperFactoryBeanが使用され、実際のBeanは動的プロキシとして生成されます。
public class MapperFactoryBean<T> extends SqlSessionSupport implements FactoryBean<T> {
private final Class<T> mapperInterface;
@Override
public T getObject() {
return getSqlSession().getMapper(mapperInterface);
}
// その他の実装
}