Spring Boot DevTools + Doma2の場合のDomaConfig
Spring Boot 1.3で追加されたDevToolsを使うと、hot deploy(アプリを起動した状態でソースの修正を反映させる)が出来るようになります
DevToolsのhot deployは100%成功はしないのですが、有効にしておくと便利です
ですが、Doma2を組み合わせて使う場合は設定をカスタマイズしておかないとクラスローダーの問題でハマる事になります
DevToolsのhot deployのしくみ
DevToolsはクラスローダーを2つ用意します
- 開発しているアプリケーションのclassをロードするクラスローダー
- 依存ライブラリ(jarファイル)のclassをロードするクラスローダー
ソースが修正されたらアプリケーションのclassをロードするクラスローダーだけを破棄して作り直す事でhot deployを実現しています
(依存ライブラリはアプリ起動中に変更が入らないので作り直し不要)
Doma2を組み合わせて使う場合に何が問題になるのか?
Doma2ではSQLの中でclass/enumを参照出来る機能があります
例えば、この様にRepository(Dao)メソッドの引数にenumを渡して、SQLファイル内で比較が出来て便利です
- enum
package com.example;
public enum Code {
A, B, C
}
- Repository(Dao)
@Dao
@ConfigAutowireable
public interface EmployeeRepository {
@Select
List<Employee> selectAll(Code code);
}
- SQLファイル
SELECT id
/*%if code == @com.example.Code@A */
,'hoge' AS name
/*%else */
,'fuga' AS name
/*%end */
FROM employee
ORDER BY id
ですが、Repositoryの引数で渡したenumとSQLファイル内で参照しているenumが別のクラスローダーでロードされるため、Enum#compareToでClassCastExceptionが発生します
[DOMA2111] SQLの組み立てに失敗しました。([2]行目[45]番目の文字付近)。 原因は次のものです。org.seasar.doma.internal.expr.ExpressionException: [DOMA3008] 式[ code == @com.example.Code@A ]の評価に失敗しました([==]番目の文字付近)。 比較演算子[java.lang.ClassCastException]の実行に失敗しました。 被演算子のクラスがjava.lang.Comparableを実装していないか、2つの被演算子の型が比較不可能なのかもしれません。
具体的には以下のようにロードされます
- Repositoryの引数で渡したenum: DevToolsのRestartClassLoaderでロード
- SQLファイルの中でenum参照しているenum: Launcher$AppClassLoaderでロード
この問題は、以下のようにDoma2の設定をカスタマイズすると解決できます
(Domaがアプリと同じクラスローダーRestartClassLoaderを使うようになります)
public class DomaConfig implements Config {
private ClassHelper classHelper = new MyClassHelper();
private static class MyClassHelper implements ClassHelper {
@SuppressWarnings("unchecked")
@Override
public <T> Class<T> forName(String className) throws Exception {
return (Class<T>)this.getClass().getClassLoader().loadClass(className);
}
}
@Override
public ClassHelper getClassHelper() {
return classHelper;
}
}
doma-spring-bootを使っている場合は以下のようにしてください
doma-spring-bootのサンプルコードを修正したものをここに置いておきました
private ClassHelper classHelper = new MyClassHelper();
private static class MyClassHelper implements ClassHelper {
@SuppressWarnings("unchecked")
@Override
public <T> Class<T> forName(String className) throws Exception {
return (Class<T>)this.getClass().getClassLoader().loadClass(className);
}
}
@Bean
public DomaConfigBuilder domaConfigBuilder() {
return new DomaConfigBuilder() {
public ClassHelper classHelper() {
return classHelper;
}
};
}