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
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         * 1回でもコミットが行われており、ロールバックが行われていなければ、
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
155        public void commit() {
156                isCommit = true;
157        }
158
159        /**
160         * ロールバック処理が行われた場合に、内部フラグ(isRollback)を true にセットします。
161         * 1回でもロールバックが行われていれば、最終的にはロールバックされます。
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
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
187        public void close() {
188                // Impl は、finish() で、実質的な完了処理を行う。
189        }
190
191        /**
192         * 最終的なコミットが行われた場合に、内部フラグ(isEndCommit)を true にセットします。
193         * 通常は、この処理は、1値度だけ実行されます。
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        public void endCommit() {
205                isEndCommit = true ;
206                isCommit    = true ;
207        }
208
209        /**
210         * トランザクションとして、終了時処理を行います。
211         *
212         * トランザクションがすべて完了した後で、呼び出します。
213         * 一度でも、Rollback が指定されていれば、ロールバックを行い、コネクションを
214         * 破棄します。それ以外で、Commit が指定されていれば、コミットを行い、
215         * コネクションを、プールに戻します。どちらも指定されていなければ、
216         * コネクションプールに戻すだけになります。
217         *
218         * @og.rev 6.3.6.1 (2015/08/28) AutoCloseable の close() メソッドに対応。メソッド名変更。
219         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
220         * @og.rev 6.4.3.4 (2016/03/11) Map#computeIfAbsent で対応する。
221         */
222        public void finish() {
223                if( isFinish ) {
224                        final String errMsg = "すでに、finish() 実行済みです。"            + CR
225                                                + "  新規に Transaction オブジェクトの作成から行ってください。"               + CR ;
226                        throw new OgRuntimeException( errMsg );
227                }
228
229                if( defconn != null ) {
230                        connClose( defconn,DBID );
231                        defconn = null;                 // 内部変数を初期化します。
232                }
233
234                // Map#computeIfAbsent : 戻り値は、既存の、または計算された値。追加有り、置換なし、削除なし
235                dbidMap.forEach( (dbid,conn) -> connClose( conn,dbid ) );
236                dbidMap.clear();
237
238                isFinish   = true;              // 次回実行時にエラーにします。
239        }
240
241        /**
242         * Connection オブジェクトをクローズします。
243         *
244         *
245         * commit されており、rollback されていない場合のみ、commit します。
246         * それ以外は、rollback します。
247         * rollback された場合は、コネクションプールに戻さずに、破棄します。
248         * 本来は、その、Connection に問題はないかもしれませんが、あえて、
249         * キャッシュを継続させる必要もないと考えます。
250         *
251         * @og.rev 6.3.6.1 (2015/08/28) AutoCloseable の close() メソッドに対応。内部処理見直し。
252         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
253         * @og.rev 6.4.3.3 (2016/03/04) 一般的なタグで、SKIP_PAGE された場合、rollback するようにします。
254         *
255         * @param  conn クローズ処理を行う、Connectionオブジェクト
256         * @param  dbid 接続先ID
257         */
258        private void connClose( final Connection conn, final String dbid ) {
259                // commit されており、rollback されていない場合
260                final boolean isOK ;
261                if( isCommit && isEndCommit && !isRollback ) {          // 6.4.3.3 (2016/03/04)
262                        isOK = Closer.commit( conn );
263                }
264                else {
265                        isOK = Closer.rollback( conn );
266                }
267
268                // rollback されているか、commit/rollback でエラーが発生した場合は、削除
269                if( isRollback || !isOK ) {     ConnectionFactory.remove( conn,dbid ); }        // 削除
270                else {                                          ConnectionFactory.close( conn,dbid );  }        // 返却
271        }
272}