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.plugin.query; 017 018import org.opengion.hayabusa.db.AbstractQuery; 019import org.opengion.hayabusa.db.DBTableModel; 020import org.opengion.hayabusa.common.HybsSystemException; 021import org.opengion.fukurou.util.ErrorMessage; 022import org.opengion.fukurou.util.StringUtil; 023// import org.opengion.fukurou.util.HybsDateUtil; // 5.5.8.5 (2012/11/27) 024import org.opengion.fukurou.model.Formatter; 025import org.opengion.fukurou.db.DBUpdater; // 6.9.3.0 (2018/03/26) 026 027import java.sql.Connection; 028import java.sql.PreparedStatement; 029// import java.sql.ParameterMetaData; 030import java.sql.SQLException; 031 032/** 033 * 引数引き当て(PreparedStatement) を利用した登録系Queryです。 034 * 035 * java.sql.PreparedStatement を用いて、データベース登録処理を行います。 036 * 引数の指定方法は、DBTableModele のカラム名に対応する名称を、SQL文の[カラム名]形式で 037 * 記述します。これを解析して、実際に実行する PreparedStatement に対応する文字列を 038 * 作成します。 039 * たとえば、INSERT INTO GEXX (CLM,NAME_JA,LABEL_NAME) VALUES ([CLM],[NAME_JA],[LABEL_NAME] ) 040 * と記述すれば、内部で、DBTableModele のカラム名に対応する値を取り出し、SQL文として、 041 * INSERT INTO GEXX (CLM,NAME_JA,LABEL_NAME) VALUES (?,?,? ) を実行します。 042 * 043 * Query_JDBCTableUpdate との違いは、INSERT文とUPDATE文を渡しておき、 044 * UPDATEの処理結果が、0件の場合は、INSERTを行います。 045 * そのため、tableUpdateタグのBODY部に直接SQLを書くのではなく、tableUpdateParam タグを2個書くことになります。 046 * 047 * 基本的に、tableUpdateタグのqueryTypeにJDBCTableUpdateと記述しておき、tableUpdateParam の 048 * sqlType が MERGE の場合は、2種類のSQL文が作成され、自動的に、JDBCTableMerge が呼ばれます。 049 * ※ つまり、通常は、queryType="JDBCTableUpdate" のままで、sqlType="MERGE" を指定すればよい。 050 * 051 * @og.formSample 052 * ●使用例 053 * 054 * ・JDBCTableUpdate のまま、sqlType="MERGE" を指定する場合。 055 * 【entry.jsp】 056 * <og:tableUpdate 057 * command = "{@command}" 058 * queryType = "JDBCTableUpdate" 059 * <og:tableUpdateParam 060 * sqlType = "MERGE" // INSERT or UPDATE 061 * table = "{@TABLE_NAME}" // 処理対象のテーブル名 062 * names = "{@names}" // 処理対象のカラム名 063 * omitNames = "{@omitNames}" // 処理対象外のカラム名 064 * where = "{@where}" // 処理対象を特定するキー(INSERT時には使われず、UPDAET時に使われる。) 065 * constKeys = "{@constKeys}" // 処理カラム名の中の固定情報カラム名 066 * constVals = "{@constVals}" // 処理カラム名の中の固定情報設定値 067 * /> 068 * </og:tableUpdate> 069 * 070 * ・JDBCTableMerge を直接的に指定する場合。 071 * 【entry.jsp】 072 * <og:tableUpdate 073 * command = "{@command}" 074 * queryType = "JDBCTableMerge" 075 * <og:tableUpdateParam 076 * sqlType = "INSERT" // INSERT or UPDATE 077 * table = "{@TABLE_NAME}" // 処理対象のテーブル名 078 * names = "{@names}" // 処理対象のカラム名 079 * omitNames = "{@omitNames}" // 処理対象外のカラム名 080 * constKeys = "{@constKeys}" // 処理カラム名の中の固定情報カラム名 081 * constVals = "{@constVals}" // 処理カラム名の中の固定情報設定値 082 * /> 083 * <og:tableUpdateParam 084 * sqlType = "UPDATE" // INSERT or UPDATE 085 * table = "{@TABLE_NAME}" // 処理対象のテーブル名 086 * names = "{@names}" // 処理対象のカラム名 087 * omitNames = "{@omitNames}" // 処理対象外のカラム名 088 * where = "{@where}" // 処理対象を特定するキー 089 * constKeys = "{@constKeys}" // 処理カラム名の中の固定情報カラム名 090 * constVals = "{@constVals}" // 処理カラム名の中の固定情報設定値 091 * /> 092 * </og:tableUpdate> 093 * 094 * @og.rev 7.2.9.1 (2020/10/23) 新規作成 095 * @og.group データ編集 096 * 097 * @version 7.2 098 * @author Kazuhiko Hasegawa 099 * @since JDK11.0, 100 */ 101public class Query_JDBCTableMerge extends AbstractQuery { 102 /** このプログラムのVERSION文字列を設定します。 {@value} */ 103 private static final String VERSION = "7.2.9.1 (2020/10/23)" ; 104 105 /** 106 * デフォルトコンストラクター 107 * 108 * @og.rev 7.2.9.1 (2020/10/23) 新規作成 109 */ 110 public Query_JDBCTableMerge() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 111 112 /** 113 * 引数配列付のクエリーを実行します。 114 * 処理自体は, #execute() と同様に、各サブクラスの実装に依存します。 115 * これは、PreparedQuery で使用する引数を配列でセットするものです。 116 * select * from emp where deptno = ? and job = ? などの PreparedQuery の 117 * [カラム名] 部分の引数を、DBTableModelから順番にセットしていきます。 118 * 119 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 120 * 121 * @param rowNo 選択された行番号配列(登録する対象行) 122 * @param table DBTableModelオブジェクト(登録する元データ) 123 */ 124 @Override 125 public void execute( final int[] rowNo, final DBTableModel table ) { 126 127 int row = 0; // エラー時に表示するエラー行番号 128 int executeCount = 0; // 処理件数 129 130 final String[] sqls = getMergeStatement(); // UPDATE,INSERTの順番 131 132 final DBquery updQuery = new DBquery( table,sqls[0] ); 133 final DBquery insQuery = new DBquery( table,sqls[1] ); 134 135 // 6.9.8.0 (2018/05/28) エラー時に、わかりやすいように、引数を、パラメータ順にします。 136 String[] vals = null; 137 boolean isUpdate = false; // update/insert の区別が分かるように 138 final Connection conn = getConnection(); 139 140 // 6.4.2.1 (2016/02/05) try-with-resources 文 141 try( PreparedStatement updSstmt = updQuery.getPreStmt(conn); 142 final PreparedStatement insSstmt = insQuery.getPreStmt(conn) ) { // 更新系なので、setFetchSize は不要。 143 144 final boolean usePMeta = useParameterMetaData(); 145 146 final DBUpdater updDB = new DBUpdater( updQuery.size(),updSstmt,usePMeta,updQuery.getUseTime() ); 147 final DBUpdater insDB = new DBUpdater( insQuery.size(),insSstmt,usePMeta,insQuery.getUseTime() ); 148 149 // 5.5.5.4 (2012/08/18) Timestamp オブジェクトを登録する場合 150 for( int i=0; i<rowNo.length; i++ ) { 151 row = rowNo[i]; 152 vals = updQuery.getVals( row ); 153 154 isUpdate = true; 155 int cnt = updDB.update( vals ) ; 156 if( cnt == 0 ) { // UPDATE を実行して、結果が 0 件なら 157 vals = insQuery.getVals( row ); 158 isUpdate = false; 159 cnt = insDB.update( vals ); // INSERT 処理を行う。 160 } 161 executeCount += cnt ; 162 } 163 164 setExecuteCount( executeCount ); 165 setErrorCode( ErrorMessage.OK ); 166 } 167 catch( final SQLException ex) { // catch は、close() されてから呼ばれます。 168 setErrorCode( ErrorMessage.EXCEPTION ); 169 final StringBuilder errMsg = new StringBuilder( BUFFER_MIDDLE ) 170 .append( ex.getMessage() ).append( ':' ).append( ex.getSQLState() ).append( CR ) 171 .append( isUpdate ? "UPDATE" : "INSERT" ).append( CR ) 172 .append( " UPDATE=" ).append( sqls[0] ).append( CR ) 173 .append( " INSERT=" ).append( sqls[1] ).append( CR ) 174 .append( " ROW =[" ).append( (row+1) ).append( ']' ).append( CR ) 175 .append( " VALS=[" ).append( StringUtil.array2csv( vals )).append( ']' ) // 6.9.8.0 (2018/05/28) 176 .append( CR ) ; 177 178 throw new HybsSystemException( errMsg.toString(),ex ); // 3.5.5.4 (2004/04/15) 引数の並び順変更 179 } 180 } 181 182 /** 183 * DBUpdater で処理するにあたり、 184 * 処理自体は, #execute() と同様に、各サブクラスの実装に依存します。 185 * これは、PreparedQuery で使用する引数を配列でセットするものです。 186 * select * from emp where deptno = ? and job = ? などの PreparedQuery の 187 * [カラム名] 部分の引数を、DBTableModelから順番にセットしていきます。 188 * 189 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 190 */ 191 private static final class DBquery { 192 private final DBTableModel table; 193 private final int[] clmNos; 194 private final String query ; 195 private final int cnt ; 196 private final boolean[] isTime; 197 private final boolean useTime; 198 199 /** 200 * DBTableModelとQUERY文を指定した、コンストラクター 201 * 202 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 203 * 204 * @param table DBTableModelオブジェクト(登録する元データ) 205 * @param sql 実行するQUERY文 206 */ 207 public DBquery( final DBTableModel table , final String sql ) { 208 this.table = table; 209 final Formatter form = new Formatter( table,sql ); 210 clmNos = form.getClmNos(); // 引数の個数分の配列。カラム番号を保存 211 query = form.getQueryFormatString(); 212 cnt = clmNos.length; // 引数の個数(カラムの個数ではありません。) 213 214 isTime = new boolean[cnt]; 215 216 boolean useTimeStamp = false; 217 for( int j=0; j<cnt; j++ ) { 218 isTime[j] = table.getDBColumn( clmNos[j] ).isDateType(); // 6.4.6.0 (2016/05/27) 219 if( !useTimeStamp && isTime[j] ) { useTimeStamp = true; } // isTime[j] == true 時に、一度だけ実行される。 220 } 221 useTime = useTimeStamp; 222 } 223 224 /** 225 * Connectionから、PreparedStatement を作成して返します。 226 * 227 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 228 * 229 * @param conn Connectionオブジェクト 230 * @return PreparedStatementオブジェクト 231 */ 232 public PreparedStatement getPreStmt( final Connection conn ) throws SQLException { 233 final PreparedStatement pstmt = conn.prepareStatement( query ); 234 235 pstmt.setQueryTimeout( DB_MAX_QUERY_TIMEOUT ); 236 237 return pstmt; 238 } 239 240 /** 241 * パラメータの個数を返します。 242 * 243 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 244 * 245 * @return パラメータの個数 246 */ 247 public int size() { return cnt; } 248 249 /** 250 * TimeStampカラムかどうかの配列を返します。 251 * 配列は、パラメータの順番に再設定されています。 252 * すべて通常のカラムの場合は、null を返しますのでご注意ください。 253 * 254 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 255 * 256 * @return TimeStampカラムかどうかの配列(すべて通常のカラムの場合は、null) 257 */ 258 public boolean[] getUseTime() { return useTime ? isTime : null ; } // 使われない時は、null 259 260 /** 261 * 指定行のデータのうち、パラメータカラムの値の配列を返します。 262 * 263 * これは、[カラム]を? に置き換えた処理の、? の順番にDBTableModelの値を再設定します。 264 * 取り出した行データは、rTrim して、右側の空白文字を削除しています。 265 * 266 * @og.rev 7.2.9.1 (2020/10/23) TableUpdateParamTag のマージ(UPDATE,INSERT)対応 267 * 268 * @param row 指定行 269 * @return 指定行のデータ配列 270 */ 271 public String[] getVals( final int row ) { 272 final String[] vals = new String[cnt]; 273 final String[] data = table.getValues( row ); 274 for( int j=0; j<cnt; j++ ) { 275 vals[j] = StringUtil.rTrim( data[ clmNos[j] ] ); 276 } 277 return vals ; 278 } 279 } 280}