    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.fukurou.db;
    017
    018import org.opengion.fukurou.system.Closer;
    019import org.opengion.fukurou.system.OgRuntimeException ;                 // 6.4.2.0 (2016/01/29)
    020import static org.opengion.fukurou.system.HybsConst.CR ;                // 6.3.6.1 (2015/08/28)
    021
    022import java.sql.Connection;
    023
    024import java.util.Locale;
    025import java.util.concurrent.ConcurrentMap;                                                      // 6.4.3.4 (2016/03/11)
    026import java.util.concurrent.ConcurrentHashMap;                                          // 6.4.3.4 (2016/03/11)
    027
    028/**
    029 * コネクションを共有して、トランザクションを実現します。
    030 *
    031 * 基本的には、TransactionTag で利用されますが、一部、このオブジェクトを
    032 * 渡して、直接、利用するケースもあります。
    033 *
    034 * トランザクションがすべて完了した後で、realClose() メソッドを呼び出します。
    035 * 一度でも、rollback が指定されていれば、ロールバックを行い、コネクションを
    036 * 破棄します。それ以外で、commit が指定されていれば、コミットを行い、
    037 * コネクションを、プールに戻します。どちらも指定されていなければ、
    038 * コネクションプールに戻すだけになります。
    039 *
    040 * 6.3.6.1 (2015/08/28)
    041 *   selectを実行した後で明示的にcommit,rollbackを行わないのはOracle位
    042 *   らしいので、検索終了時でも、commit か、rollback を行うようにします。
    043 *   つまり、commit されない(=途中で処理が打ち切られた)場合は、
    044 *   rollback するように仕様変更しますので、Transactionオブジェクトを
    045 *   呼び出した処理の最後には、検索であろうとなかろうと、commit()を入れてください。
    046 *   ただし、Transaction オブジェクトは、DBアクセス以外にも適用可能に
    047 *   作成しているため、Connection がある場合のみ、実際の commit/rollback が
    048 *   実行されます。
    049 *
    050 * 6.3.6.1 (2015/08/28)
    051 *   一度、finish() を実行すると、次回実行時にエラーにします。
    052 * 6.3.9.0 (2015/11/06)
    053 *   synchronized メソッドをsynchronizedブロックに変更。
    054 *
    055 * 考え方として、下記のような流れになります。
    056 * <pre>
    057 *   Transaction tran = new TransactionImpl/Real( appInfo ) ;
    058 *   try {
    059 *      ・・・・・
    060 *      tran.commit();
    061 *   }
    062 *   catch( final Exception ex ) {
    063 *      tran.rollback();
    064 *   }
    065 *   finally {
    066 *      tran.close();                           // TransactionReal の場合
    067 *      tran.finish();                          // TransactionImpl の場合
    068 *   }
    069 * </pre>
    070 *
    071 * 6.3.6.1 (2015/08/28)
    072 *    AutoCloseableを使用したtry-with-resources 構文を使用した場合。close/finish 不要。
    073 * <pre>
    074 *   try( Transaction tran = new TransactionImpl/Real( appInfo ) ) {
    075 *      ・・・・・
    076 *      tran.commit();
    077 *   }
    078 *   ただし、処理自体がアベンドしないケースでは、rollback() を、自分で呼ぶか、commit() しない(=rollback()される)ようにします。
    079 * </pre>
    080 *
    081 * @og.rev 5.1.9.0 (2010/08/01) 新規作成
    082 *
    083 * @version  5.0
    084 * @author       Kazuhiko Hasegawa
    085 * @since    JDK6.0,
    086 */
    087public class TransactionImpl implements Transaction {
    088        /** このプログラムのVERSION文字列を設定します。   {@value} */
    089        private static final String VERSION = "6.4.3.4 (2016/03/11)" ;
    090        private static final long serialVersionUID = 643420160311L ;
    091
    092        private static final String  DBID = "DEFAULT";
    093        private final ApplicationInfo appInfo ;
    094
    095        private Connection defconn ;                    // 最も利用率の高いDEFAULTだけ、別に変数を用意。
    096
    097        /** 6.4.3.4 (2016/03/11) ConcurrentHashMap で同期処理を行います。  */
    098        private final ConcurrentMap<String,Connection> dbidMap = new ConcurrentHashMap<>();             // 6.4.3.4 (2016/03/11)
    099        private boolean isCommit        ;                       // commit() されたかどうか。
    100        private boolean isEndCommit     ;                       // 6.4.3.3 (2016/03/04) doEndTag() が実行されたかどうか。
    101        private boolean isRollback      ;                       // rollback() されたかどうか。
    102        private boolean isFinish        ;                       // 処理の最後にセットします。
    103
    104        /**
    105         * ApplicationInfo を指定して作成する、コンストラクター
    106         *
    107         * このクラスは、基本的には、TransactionTag クラスから作成されます。
    108         *
    109         * @param       appInfo 内部統制用のアクセス情報
    110         */
    111        public TransactionImpl( final ApplicationInfo appInfo ) {
    112                this.appInfo = appInfo ;
    113        }
    114
    115        /**
    116         * 指定のDBID に対応した、Connection オブジェクトを返します。
    117         * 内部Mapに存在していれば、そのコネクションを、存在しなければ、
    118         * 新しく作成します。
    119         *
    120         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
    121         * @og.rev 6.4.3.4 (2016/03/11) Map#computeIfAbsent で対応する。
    122         *
    123         * @param       dbid  接続先ID
    124         *
    125         * @return      指定のDBID に対応した、Connectionオブジェクト
    126         */
    127        @Override       // Transaction
    128        public Connection getConnection( final String dbid ) {
    129                if( dbid == null || dbid.isEmpty() || DBID.equalsIgnoreCase( dbid ) ) {
    130                        if( defconn == null ) {
    131                                defconn = ConnectionFactory.connection( DBID,appInfo );
    132                        }
    133                        return defconn;
    134                }
    135
    136                final String udbid = dbid.toUpperCase( Locale.JAPAN );  // 大文字化
    137
    138                // Map#computeIfAbsent ： 戻り値は、既存の、または計算された値。追加有り、置換なし、削除なし
    139                return dbidMap.computeIfAbsent( udbid , k -> ConnectionFactory.connection( udbid,appInfo ) );
    140        }
    141
    142        /**
    143         * コミット処理が行われた場合に、内部フラグ(isCommit)を true にセットします。
    144         * １回でもコミットが行われており、ロールバックが行われていなければ、
    145         * コミットされます。
    146         *
    147         * 検索処理時でも、最後に commit() を実行してください。実行されていない場合は、
    148         * 自動的に、rollback() が、実行されます。
    149         *
    150         * @og.rev 6.3.6.1 (2015/08/28) AutoCloseable の close() メソッドに対応。return 不要。
    151         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
    152         *
    153         */
    154        @Override       // Transaction
    155        public void commit() {
    156                isCommit = true;
    157        }
    158
    159        /**
    160         * ロールバック処理が行われた場合に、内部フラグ(isRollback)を true にセットします。
    161         * １回でもロールバックが行われていれば、最終的にはロールバックされます。
    162         *
    163         * 一度も、ロールバックが行われていない場合でも、コミットが行われていない場合は、
    164         * rollback()を実行します。
    165         *
    166         *
    167         * @og.rev 6.3.6.1 (2015/08/28) AutoCloseable の close() メソッドに対応。return 不要。
    168         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
    169         *
    170         */
    171        @Override       // Transaction
    172        public void rollback() {
    173                isRollback = true;
    174        }
    175
    176        /**
    177         * トランザクションの、終了時処理を行います。
    178         *
    179         * それまでの処理は、すべて正常に処理できた場合に、使用します。
    180         *
    181         * @og.rev 6.3.6.1 (2015/08/28) AutoCloseable の close() メソッドに対応。return 不要。
    182         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
    183         *
    184         * @see AutoCloseable#close()
    185         */
    186        @Override       // AutoCloseable
    187        public void close() {
    188                // Impl は、finish() で、実質的な完了処理を行う。
    189        }
    190
    191        /**
    192         * 最終的なコミットが行われた場合に、内部フラグ(isEndCommit)を true にセットします。
    193         * 通常は、この処理は、１値度だけ実行されます。
    194         * 初期値が、false なので、途中で一度でも実行されると、true にセットされ、最後まで
    195         * 処理されたとみなされてしまうので、注意してください。
    196         *
    197         * 通常は、タグリブの、doEndTag() が実行された場合に、呼びます。
    198         * このフラグが、true でないと（つまり、一度でも呼ばれないと）最終的に、commit されません。
    199         *
    200         * なお、endCommit() が呼ばれると、自動的に、commit() も呼んでおきます。
    201         *
    202         * @og.rev 6.4.3.3 (2016/03/04) 一般的なタグで、SKIP_PAGE された場合、rollback するようにします。
    203         */
    204        @Override       // Transaction
    205        public void endCommit() {
    206                isEndCommit = true ;
    207                isCommit    = true ;
    208        }
    209
    210        /**
    211         * トランザクションとして、終了時処理を行います。
    212         *
    213         * トランザクションがすべて完了した後で、呼び出します。
    214         * 一度でも、Rollback が指定されていれば、ロールバックを行い、コネクションを
    215         * 破棄します。それ以外で、Commit が指定されていれば、コミットを行い、
    216         * コネクションを、プールに戻します。どちらも指定されていなければ、
    217         * コネクションプールに戻すだけになります。
    218         *
    219         * @og.rev 6.3.6.1 (2015/08/28) AutoCloseable の close() メソッドに対応。メソッド名変更。
    220         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
    221         * @og.rev 6.4.3.4 (2016/03/11) Map#computeIfAbsent で対応する。
    222         */
    223        public void finish() {
    224                if( isFinish ) {
    225                        final String errMsg = "すでに、finish() 実行済みです。"            + CR
    226                                                + "  新規に Transaction オブジェクトの作成から行ってください。"               + CR ;
    227                        throw new OgRuntimeException( errMsg );
    228                }
    229
    230                if( defconn != null ) {
    231                        connClose( defconn,DBID );
    232                        defconn = null;                 // 内部変数を初期化します。
    233                }
    234
    235                // Map#computeIfAbsent ： 戻り値は、既存の、または計算された値。追加有り、置換なし、削除なし
    236                dbidMap.forEach( (dbid,conn) -> connClose( conn,dbid ) );
    237                dbidMap.clear();
    238
    239                isFinish   = true;              // 次回実行時にエラーにします。
    240        }
    241
    242        /**
    243         * Connection オブジェクトをクローズします。
    244         *
    245         *
    246         * commit されており、rollback されていない場合のみ、commit します。
    247         * それ以外は、rollback します。
    248         * rollback された場合は、コネクションプールに戻さずに、破棄します。
    249         * 本来は、その、Connection に問題はないかもしれませんが、あえて、
    250         * キャッシュを継続させる必要もないと考えます。
    251         *
    252         * @og.rev 6.3.6.1 (2015/08/28) AutoCloseable の close() メソッドに対応。内部処理見直し。
    253         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
    254         * @og.rev 6.4.3.3 (2016/03/04) 一般的なタグで、SKIP_PAGE された場合、rollback するようにします。
    255         *
    256         * @param  conn クローズ処理を行う、Connectionオブジェクト
    257         * @param  dbid 接続先ID
    258         */
    259        private void connClose( final Connection conn, final String dbid ) {
    260                // commit されており、rollback されていない場合
    261                final boolean isOK ;
    262                if( isCommit && isEndCommit && !isRollback ) {          // 6.4.3.3 (2016/03/04)
    263                        isOK = Closer.commit( conn );
    264                }
    265                else {
    266                        isOK = Closer.rollback( conn );
    267                }
    268
    269                // rollback されているか、commit/rollback でエラーが発生した場合は、削除
    270                if( isRollback || !isOK ) {     ConnectionFactory.remove( conn,dbid ); }        // 削除
    271                else {                                          ConnectionFactory.close( conn,dbid );  }        // 返却
    272        }
    273}