seasarorg / dbflute-play

DBFlute for Play
0 stars 1 forks source link

複数スレッドに渡るTransactionへの対応 #6

Open manhole opened 10 years ago

manhole commented 10 years ago

http://www.playframework.com/documentation/2.2.x/JavaAsync にあるように1リクエストの間にF.Promiseでスレッドを切り替えても、トランザクションを保てるようにしている最中です。 (ThreadLocalを使ったトランザクション実装が多く、それではThreadが変わると別トランザクションになってしまう)

PR #5 までで、スレッドが変わってもトランザクションを保てるようにしたつもりです。

これから、DBFluteのAccessContextを対応しようと考えていますが、他の箇所でもThreadLocalを使っているようなのでまとめて相談しておきたいです。

dbflute-1.0.5D では10箇所(9ファイル)で使っているようです。

$ ack ThreadLocal
dbflute/embedded/templates/om/java/other/hibernate/allcommon/HibernateAccessContext.vm
33:    private static final ThreadLocal<${glAccessContext}> _threadLocal = new ThreadLocal<${glAccessContext}>();

dbflute/src/main/java/org/seasar/dbflute/helper/jdbc/context/DfDataSourceContext.java
26:    private static ThreadLocal<DataSource> _threadLocal = new ThreadLocal<DataSource>();

dbflute-runtime/src/main/java/org/seasar/dbflute/AccessContext.java
38:    private static final ThreadLocal<AccessContext> _threadLocal = new ThreadLocal<AccessContext>();

dbflute-runtime/src/main/java/org/seasar/dbflute/bhv/core/ContextStack.java
43:    private static ThreadLocal<Stack<ContextStack>> _threadLocal = new ThreadLocal<Stack<ContextStack>>();

dbflute-runtime/src/main/java/org/seasar/dbflute/CallbackContext.java
34:    private static final ThreadLocal<CallbackContext> _threadLocal = new ThreadLocal<CallbackContext>();

dbflute-runtime/src/main/java/org/seasar/dbflute/cbean/ConditionBeanContext.java
38:    private static final ThreadLocal<ConditionBean> _conditionBeanLocal = new ThreadLocal<ConditionBean>();
79:    private static final ThreadLocal<EntityRowHandler<? extends Entity>> _entityRowHandlerLocal = new ThreadLocal<EntityRowHandler<? extends Entity>>();

dbflute-runtime/src/main/java/org/seasar/dbflute/cbean/FetchAssistContext.java
29:    private static ThreadLocal<FetchBean> _threadLocal = new ThreadLocal<FetchBean>();

dbflute-runtime/src/main/java/org/seasar/dbflute/outsidesql/OutsideSqlContext.java
50:    private static final ThreadLocal<OutsideSqlContext> _threadLocal = new ThreadLocal<OutsideSqlContext>();

dbflute-runtime/src/main/java/org/seasar/dbflute/resource/InternalMapContext.java
34:    private static final ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<Map<String, Object>>();

dbflute-runtime/src/main/java/org/seasar/dbflute/resource/ManualThreadDataSourceHandler.java
36:    private static final ThreadLocal<ManualThreadDataSourceHandler> _handlerLocal = new ThreadLocal<ManualThreadDataSourceHandler>();

dbflute-runtime/src/main/java/org/seasar/dbflute/resource/ResourceContext.java
52:    private static final ThreadLocal<ResourceContext> threadLocal = new ThreadLocal<ResourceContext>();

少なくともAccessContextは対応する必要があると考えていますが、他の箇所はどうでしょうか?

について相談させていただけますか。

補足

PR #5までで対応したぶんは、対応が必要かどうかは次のように判断しました。

そして対応が必要な場合は、ThreadLocalではなくPlayのHttp.Contextを使うように変えました。 (Http.Contextは、1リクエストの間 生存しています。)

jflute commented 10 years ago

HibernateAccessContext は、Hibernate 自動生成のときのためだけなので無関係。 DfDataSourceContext は、自動生成ツールとしてのDBFluteのクラスなので無関係。

そして、AccessContext, CallbackContext 以外は、 「Behaviorのメソッド内で閉じられた利用しかされないスレッドローカル」 なので、ぼくのわかってる範囲での話では、対応は不要かなと思います。

CallbackContext は、特殊要件のものなので、とりあえず優先度は低いかなと。 http://dbflute.seasar.org/ja/manual/function/genbafit/runtimefit/sqlloghandler/

AccessContextは対応しないと。。。ちょっとお待ちを

manhole commented 10 years ago

ありがとうございます、Behaviorに閉じているものはThreadLocalのままで良さそうですね。 (その対応が必要になるのは、非同期なJDBCドライバが登場するときになるのかな...)

ちょっと考慮が必要なのは、全部をHttp.Contextへ置換すれば良いわけではなさそうな点です。 例えばrequestと独立したタスクを動かしてるケースは、Http.Contextがいないと思います。 (#5 での対応では不十分ということですね。このコメントを書いていて気づきました...)

なので、単純な置換ではなくて、ThreadLocalっぽいものを代替するインタフェースをアプリケーションに要求するふうになるのでしょうか。 AccessContextに限れば、AccessContextResolverのようなインタフェースもアリかと。

jflute commented 10 years ago

確かに、バッチのときはHttpがいないから、 要は利用するスレッドローカルをアプリで指定できるようにしないとねってとこだね。

いったん、Runtimeをいじらないでも動くような形で暫定対応してみて、 その後 Runtime を修正するときに根本対応してみたいと思います。 (GW中になんとかやるぞー!(></)

jflute commented 10 years ago

ひとまず、スーパーミラクルワンダフルべたべたの方法を思い付きました。

commonColumnMap.dfpropにて、 (($$AccessContext$$)play.mvc.Http.Context.current.get().args.get("df:AccessContext")).getAccessTimestamp() という風に書けば、Http.ContextからAccessContextを取得できます。 もちろん、バッチアプリじゃダメだけど。 (これは単にローカルで試しただけ)

次は、ちょっとだけDBFluteのテンプレートをいじって、 メソッドのオーバーライドで対応できるようにしたいなと。 その次は、Runtimeをいじって、AccessContext自体で対応できるように。

ちなみに、自動生成を試してみて気付いたdfpropの修正ポイントを直しておきました。 (こちらはコミット済みです)

<< basicInfoMap.dfprop >> generateOutputDirectory -> ../app resourceOutputDirectory -> ../conf ※自動生成されるクラスやdiconの出力先を調整

<< databaseInfoMap.dfprop >> url -> jdbc:h2:file:../conf/exampledb/exampledb ※参照するH2データベースの位置を調整

<< dependencyInjectionMap.dfprop >> j2eeDiconResourceName -> app-j2ee.dicon ※dbflute.diconがincludeするj2ee.diconを差し替え

<< replace-schema.sh >> SAStrutsExample専用の記述が残っていたのを削除

jflute commented 10 years ago

次のバージョン(1.0.5F)で、AccessContextならびにCallbackContextにて、 スレッドローカルを差し替えるようにできるようにしました。

AccessContext.unlock(); AccessContext.useThreadLocalProvider(new AccessContextThreadLocalProvider() { public ThreadLocal provide() { return Http.Context.current; } });

アプリケーションの起動時に一度だけ呼び出すことを想定しています。 SNAPSHOTは既にありますが、正式版をそのうち出します。

manhole commented 10 years ago

*.dfpropの修正ありがとうございます、生成は今後のタスクだったので助かりました。

AccessContextですが、Http.Context.currentをそのまま使うのはまずいと思います。 これはplayのHttp.Contextインスタンス用ですから、AccessContextを入れることはできません。 https://github.com/playframework/playframework/blob/2.2.2/framework/src/play/src/main/java/play/mvc/Http.java#L22

Threadが変わってもアプリからのアクセスが可能になっているのが、 Http.Contextのargsプロパティです。 https://github.com/playframework/playframework/blob/2.2.2/framework/src/play/src/main/java/play/mvc/Http.java#L150

argsにAccessContextを持たせることになると思います。

おそらくこんなふうになるのではと思いますがどうでしょうか。やりすぎ?

// ThreadLocalを抽象化して、
interface AccessContextHolder {
    AccessContext getAccessContext();
    void setAccessContext(AccessContext ac);
}

// デフォルトはThreadLocalで、
class ThreadLocalAccessContextHolder implements AccessContextHolder {
    private static final ThreadLocal<AccessContext> _threadLocal = new ThreadLocal<AccessContext>();
    AccessContext getAccessContext() {
        return _threadLocal.get();
    }
    void setAccessContext(AccessContext ac) {
        _threadLocal.set(ac);
    }
}

// play用の実装を別途作る
class PlayAccessContextHolder implements AccessContextHolder {
    private final String _key;
    public PlayAccessContextHolder(String key) { _key = key }
    public AccessContext getAccessContext() {
        // TODO バッチのことも考慮すべき
        Http.Context context = Http.Context.current.get();
        Map<String, Object> args = context.args;
        AccessContext ac = args.get(_key);
        return ac;
    }
    public void setAccessContext(AccessContext ac) {
        Http.Context context = Http.Context.current.get();
        Map<String, Object> args = context.args;
        args.put(_key, ac);
    }
}

// ThreadLocal以外を使いたかったらアプリで1度だけ設定する
AccessContext.useAccessContextHolder(new PlayAccessContextHolder("some application unique key"));
jflute commented 10 years ago

ああぁ、そういうことかわかった。 currentのThreadLocal自体は差し代わってるんだけど、 argsの中身が新しいスレッドに引き継がれてるってことね。

たし、current (ThreadLocal) 自体は、Http.Contextを保持してるものなので、 いまのおれの実装だと ClassCastException になっちゃうか。

すると、もうDBFlute側からすると、スレッドローカルじゃなくて、 「AccessContextを提供してくれる何か」を受け取って、 そこからもらうって感じになるわけだね。 まあ、すると厳密には AccessContext のstatic領域を利用する必要もないかもだけど、 単に AccessContext を使った方がDBFluteで設定がしやすいってところだね。

ちょと修正しちゃいますー

jflute commented 10 years ago

Holder頂きました。 デフォルトのHolderではなく「代理のHolderを使う」というニュアンスに。 DBFluteからすれば、スレッドローカルなのかどうかすらわからないで、 とにかく「Holderから提供してもらう、Holderに保存する」って感じで。

AccessContext.useSurrogateHolder(new AccessContextHolder() {

public AccessContext provide() {
    return (AccessContext)Http.Context.current.get().args.get("foo");
}

public void save(AccessContext accessContext) {
    Http.Context.current.get().args.put("foo", accessContext)
}

});

ExampleにSNAPSHOTを反映させちゃおうかと思ったけど、 build.sbt を修正して、eclipse with-source=true しても変わらなくって、 すぐ忘れちゃいますねぇこれ。他の仕組みと違い過ぎて...(><。

とりあえず、Java8のOptional的な対応をやっているので、 そちらがひと段落したら正式版を出そうかなと。

manhole commented 10 years ago

修正ありがとうございます。

build.sbt を修正して、eclipse with-source=true しても変わらなくって、

build.sbtのlibraryDependenciesを変更すれば、IDEA (community版 + scala plugin)なら勝手に反映してくれると思います。 eclipse向けの手順を書いておきながら、最近はIDEAに傾倒しつつあります...

DBFluteからすれば、スレッドローカルなのかどうかすらわからないで、 とにかく「Holderから提供してもらう、Holderに保存する」って感じで。

はい、そのイメージでいます。 ところでHolderのAccessContext部分をにすれば、ThreadLocal部分を抽象化するのにちょうど良いかなと思っています。

コード部分は行頭をハードタブでインデントすると、いい感じの見栄えになってくれる記法のようです。

jflute commented 10 years ago

今週リリース予定のDBFlute-1.0.5Fを、DBFluteモジュールだけ反映しました。 まだ自動生成もせず、DBFluteランタイムの方は何も変えていないので、

そちらで、 「自動生成 (manage.sh 叩いて、1番を選択)」 と 「DBFluteランタイムを1.0.5Fにアップ (sbtのファイルを修正!?)」 をしてくれるとうれしいです。

すると、AccessContext で Holder の差し替えができます。

manhole commented 10 years ago

アイアイサー!