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
1
2
3
4
5
package com.example;

public enum Code {
    A, B, C
}
  • Repository(Dao)
1
2
3
4
5
6
@Dao
@ConfigAutowireable
public interface EmployeeRepository {
    @Select
    List<Employee> selectAll(Code code);
}
  • SQLファイル
1
2
3
4
5
6
7
8
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を使うようになります)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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のサンプルコードを修正したものをここに置いておきました

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
      }
  };
}

参考URL