• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

future-architect / uroborosql / #767

26 Aug 2024 05:08PM UTC coverage: 90.274% (-0.9%) from 91.21%
#767

push

HidekiSugimoto189
各Logger用のインタフェースを作成し必要なクラスがimplementsするように修正。そのうえでSLF4J 2.xのAPIを利用するように変更(性能改善)

360 of 568 new or added lines in 32 files covered. (63.38%)

9 existing lines in 7 files now uncovered.

8864 of 9819 relevant lines covered (90.27%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.85
/src/main/java/jp/co/future/uroborosql/SqlAgentImpl.java
1
/**
2
 * Copyright (c) 2017-present, Future Corporation
3
 *
4
 * This source code is licensed under the MIT license found in the
5
 * LICENSE file in the root directory of this source tree.
6
 */
7
package jp.co.future.uroborosql;
8

9
import java.math.BigDecimal;
10
import java.math.BigInteger;
11
import java.sql.CallableStatement;
12
import java.sql.Connection;
13
import java.sql.PreparedStatement;
14
import java.sql.ResultSet;
15
import java.sql.SQLException;
16
import java.sql.Statement;
17
import java.time.Duration;
18
import java.time.Instant;
19
import java.time.LocalTime;
20
import java.time.format.DateTimeFormatter;
21
import java.util.ArrayList;
22
import java.util.Arrays;
23
import java.util.Collections;
24
import java.util.Comparator;
25
import java.util.HashMap;
26
import java.util.List;
27
import java.util.Map;
28
import java.util.Objects;
29
import java.util.Optional;
30
import java.util.Spliterator;
31
import java.util.Spliterators;
32
import java.util.concurrent.atomic.AtomicReference;
33
import java.util.function.Consumer;
34
import java.util.function.Supplier;
35
import java.util.stream.Collectors;
36
import java.util.stream.Stream;
37
import java.util.stream.StreamSupport;
38

39
import jp.co.future.uroborosql.client.SqlParamUtils;
40
import jp.co.future.uroborosql.config.SqlConfig;
41
import jp.co.future.uroborosql.connection.ConnectionContext;
42
import jp.co.future.uroborosql.context.ExecutionContext;
43
import jp.co.future.uroborosql.context.ExecutionContextImpl;
44
import jp.co.future.uroborosql.converter.MapResultSetConverter;
45
import jp.co.future.uroborosql.converter.ResultSetConverter;
46
import jp.co.future.uroborosql.coverage.CoverageData;
47
import jp.co.future.uroborosql.coverage.CoverageHandler;
48
import jp.co.future.uroborosql.dialect.Dialect;
49
import jp.co.future.uroborosql.enums.InsertsType;
50
import jp.co.future.uroborosql.enums.SqlKind;
51
import jp.co.future.uroborosql.event.AfterEntityBatchInsertEvent;
52
import jp.co.future.uroborosql.event.AfterEntityBatchUpdateEvent;
53
import jp.co.future.uroborosql.event.AfterEntityBulkInsertEvent;
54
import jp.co.future.uroborosql.event.AfterEntityDeleteEvent;
55
import jp.co.future.uroborosql.event.AfterEntityInsertEvent;
56
import jp.co.future.uroborosql.event.AfterEntityQueryEvent;
57
import jp.co.future.uroborosql.event.AfterEntityUpdateEvent;
58
import jp.co.future.uroborosql.event.AfterProcedureEvent;
59
import jp.co.future.uroborosql.event.AfterSqlBatchEvent;
60
import jp.co.future.uroborosql.event.AfterSqlQueryEvent;
61
import jp.co.future.uroborosql.event.AfterSqlUpdateEvent;
62
import jp.co.future.uroborosql.event.BeforeEntityBatchInsertEvent;
63
import jp.co.future.uroborosql.event.BeforeEntityBatchUpdateEvent;
64
import jp.co.future.uroborosql.event.BeforeEntityBulkInsertEvent;
65
import jp.co.future.uroborosql.event.BeforeEntityDeleteEvent;
66
import jp.co.future.uroborosql.event.BeforeEntityInsertEvent;
67
import jp.co.future.uroborosql.event.BeforeEntityQueryEvent;
68
import jp.co.future.uroborosql.event.BeforeEntityUpdateEvent;
69
import jp.co.future.uroborosql.event.BeforeParseSqlEvent;
70
import jp.co.future.uroborosql.event.BeforeTransformSqlEvent;
71
import jp.co.future.uroborosql.exception.EntitySqlRuntimeException;
72
import jp.co.future.uroborosql.exception.OptimisticLockException;
73
import jp.co.future.uroborosql.exception.PessimisticLockException;
74
import jp.co.future.uroborosql.exception.UroborosqlRuntimeException;
75
import jp.co.future.uroborosql.exception.UroborosqlSQLException;
76
import jp.co.future.uroborosql.fluent.Procedure;
77
import jp.co.future.uroborosql.fluent.SqlBatch;
78
import jp.co.future.uroborosql.fluent.SqlEntityDelete;
79
import jp.co.future.uroborosql.fluent.SqlEntityQuery;
80
import jp.co.future.uroborosql.fluent.SqlEntityUpdate;
81
import jp.co.future.uroborosql.fluent.SqlQuery;
82
import jp.co.future.uroborosql.fluent.SqlUpdate;
83
import jp.co.future.uroborosql.log.CoverageLogger;
84
import jp.co.future.uroborosql.log.PerformanceLogger;
85
import jp.co.future.uroborosql.log.ReplLogger;
86
import jp.co.future.uroborosql.log.ServiceLogger;
87
import jp.co.future.uroborosql.log.SqlLogger;
88
import jp.co.future.uroborosql.mapping.EntityHandler;
89
import jp.co.future.uroborosql.mapping.MappingColumn;
90
import jp.co.future.uroborosql.mapping.MappingUtils;
91
import jp.co.future.uroborosql.mapping.TableMetadata;
92
import jp.co.future.uroborosql.parameter.Parameter;
93
import jp.co.future.uroborosql.parser.SqlParserImpl;
94
import jp.co.future.uroborosql.store.SqlResourceManager;
95
import jp.co.future.uroborosql.tx.LocalTransactionManager;
96
import jp.co.future.uroborosql.tx.TransactionManager;
97
import jp.co.future.uroborosql.utils.CaseFormat;
98
import jp.co.future.uroborosql.utils.ObjectUtils;
99

100
/**
101
 * SQL実行用クラス。
102
 *
103
 * @author H.Sugimoto
104
 */
105
public class SqlAgentImpl implements SqlAgent, ServiceLogger, PerformanceLogger, SqlLogger, CoverageLogger, ReplLogger {
106
        /** ExecutionContext属性キー:リトライカウント */
107
        private static final String CTX_ATTR_KEY_RETRY_COUNT = "__retryCount";
108

109
        /** ExecutionContext属性キー:バインドパラメータコメントの出力有無 */
110
        private static final String CTX_ATTR_KEY_OUTPUT_BIND_COMMENT = "__outputBindComment";
111

112
        /** 例外発生にロールバックが必要なDBでリトライを実現するために設定するSavepointの名前 */
113
        private static final String RETRY_SAVEPOINT_NAME = "__retry_savepoint";
114

115
        /** IN句に渡すパラメータのMAXサイズ */
116
        private static final int IN_CLAUSE_MAX_PARAM_SIZE = 1000;
117

118
        /** 経過時間のフォーマッタ */
119
        private static final DateTimeFormatter ELAPSED_TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss.SSSSSS");
1✔
120

121
        /** カバレッジハンドラ */
122
        private static final AtomicReference<CoverageHandler> COVERAGE_HANDLER_REF = new AtomicReference<>();
1✔
123

124
        /** SQL設定管理クラス */
125
        private final SqlConfig sqlConfig;
126

127
        /** トランザクション管理機能 */
128
        private final TransactionManager transactionManager;
129

130
        /** 例外発生時のログ出力を行うかどうか デフォルトは<code>true</code> */
131
        private final boolean outputExceptionLog;
132

133
        /** クエリータイムアウト制限値 */
134
        private int queryTimeout = -1;
1✔
135

136
        /** フェッチサイズ */
137
        private int fetchSize = -1;
1✔
138

139
        /** SQL実行エラー時にリトライするエラーコードのリスト */
140
        private List<String> sqlRetryCodes = List.of();
1✔
141

142
        /** SQL実行エラー時の最大リトライ回数 */
143
        private int maxRetryCount = 0;
1✔
144

145
        /** SQL実行リトライ時の待機時間(ms) */
146
        private int retryWaitTime = 0;
1✔
147

148
        /** SQLを特定するための一意なIDに置換するためのキー */
149
        private final String keySqlId;
150

151
        /** Queryの結果を格納するMapのキーを生成する際に使用するCaseFormat */
152
        private CaseFormat mapKeyCaseFormat = CaseFormat.UPPER_SNAKE_CASE;
1✔
153

154
        /** {@link InsertsType} */
155
        private InsertsType insertsType = InsertsType.BATCH;
1✔
156

157
        static {
158
                // SQLカバレッジ取得用のクラス名を設定する。指定がない場合、またはfalseが指定された場合はカバレッジを収集しない。
159
                // クラス名が指定されている場合はそのクラス名を指定
160
                var sqlCoverageClassName = System.getProperty(KEY_SQL_COVERAGE);
1✔
161
                if (sqlCoverageClassName == null) {
1✔
NEW
162
                        COVERAGE_LOG.atInfo()
×
NEW
163
                                        .log("system property - uroborosql.sql.coverage not set. sql coverage turned off.");
×
164
                } else if (Boolean.FALSE.toString().equalsIgnoreCase(sqlCoverageClassName)) {
1✔
165
                        sqlCoverageClassName = null;
×
NEW
166
                        COVERAGE_LOG.atInfo()
×
NEW
167
                                        .log("system property - uroborosql.sql.coverage is set to false. sql coverage turned off.");
×
168
                } else if (Boolean.TRUE.toString().equalsIgnoreCase(sqlCoverageClassName)) {
1✔
169
                        // trueの場合は、デフォルト値を設定
170
                        sqlCoverageClassName = "jp.co.future.uroborosql.coverage.CoberturaCoverageHandler";
1✔
171
                        COVERAGE_LOG.atInfo()
1✔
172
                                        .log("system property - uroborosql.sql.coverage is set to true. sql coverage turned on.");
1✔
173
                }
174

175
                CoverageHandler handler = null;
1✔
176
                if (sqlCoverageClassName != null) {
1✔
177
                        try {
178
                                handler = (CoverageHandler) Class.forName(sqlCoverageClassName, true,
1✔
179
                                                Thread.currentThread().getContextClassLoader()).getConstructor().newInstance();
1✔
180
                                COVERAGE_LOG.atInfo()
1✔
181
                                                .setMessage("CoverageHandler : {}")
1✔
182
                                                .addArgument(sqlCoverageClassName)
1✔
183
                                                .log();
1✔
184
                        } catch (Exception ex) {
×
NEW
185
                                COVERAGE_LOG.atWarn()
×
NEW
186
                                                .setMessage("Failed to instantiate CoverageHandler class. Class:{}, Cause:{}")
×
NEW
187
                                                .addArgument(sqlCoverageClassName)
×
NEW
188
                                                .addArgument(ex.getMessage())
×
NEW
189
                                                .log();
×
NEW
190
                                COVERAGE_LOG.atInfo()
×
NEW
191
                                                .log("Turn off sql coverage due to failure to instantiate CoverageHandler class.");
×
192
                        }
1✔
193
                }
194

195
                if (handler != null) {
1✔
196
                        COVERAGE_HANDLER_REF.set(handler);
1✔
197
                }
198
        }
1✔
199

200
        /**
201
         * コンストラクタ。
202
         *
203
         * @param sqlConfig SQL設定管理クラス
204
         * @param settings 設定情報
205
         * @param connectionContext DB接続情報
206
         */
207
        SqlAgentImpl(final SqlConfig sqlConfig, final Map<String, String> settings,
208
                        final ConnectionContext connectionContext) {
1✔
209
                this.sqlConfig = sqlConfig;
1✔
210
                this.transactionManager = new LocalTransactionManager(sqlConfig, connectionContext);
1✔
211

212
                // デフォルトプロパティ設定
213
                if (settings.containsKey(SqlAgentProvider.PROPS_KEY_FETCH_SIZE)) {
1✔
214
                        this.fetchSize = Integer.parseInt(settings.get(SqlAgentProvider.PROPS_KEY_FETCH_SIZE));
1✔
215
                }
216
                if (settings.containsKey(SqlAgentProvider.PROPS_KEY_QUERY_TIMEOUT)) {
1✔
217
                        this.queryTimeout = Integer.parseInt(settings.get(SqlAgentProvider.PROPS_KEY_QUERY_TIMEOUT));
1✔
218
                }
219
                if (settings.containsKey(SqlAgentProvider.PROPS_KEY_SQL_RETRY_CODES)) {
1✔
220
                        this.sqlRetryCodes = Collections.unmodifiableList(List.of(settings.get(
1✔
221
                                        SqlAgentProvider.PROPS_KEY_SQL_RETRY_CODES).split(",")));
1✔
222
                }
223
                if (settings.containsKey(SqlAgentProvider.PROPS_KEY_DEFAULT_MAX_RETRY_COUNT)) {
1✔
224
                        this.maxRetryCount = Integer.parseInt(settings.get(SqlAgentProvider.PROPS_KEY_DEFAULT_MAX_RETRY_COUNT));
1✔
225
                }
226
                if (settings.containsKey(SqlAgentProvider.PROPS_KEY_DEFAULT_SQL_RETRY_WAIT_TIME)) {
1✔
227
                        this.retryWaitTime = Integer.parseInt(settings
1✔
228
                                        .get(SqlAgentProvider.PROPS_KEY_DEFAULT_SQL_RETRY_WAIT_TIME));
1✔
229
                }
230
                if (settings.containsKey(SqlAgentProvider.PROPS_KEY_SQL_ID_KEY_NAME)) {
1✔
231
                        this.keySqlId = settings.get(SqlAgentProvider.PROPS_KEY_SQL_ID_KEY_NAME);
1✔
232
                } else {
233
                        this.keySqlId = "_SQL_ID_";
1✔
234
                }
235
                if (settings.containsKey(SqlAgentProvider.PROPS_KEY_DEFAULT_MAP_KEY_CASE_FORMAT)) {
1✔
236
                        this.mapKeyCaseFormat = CaseFormat.valueOf(settings
1✔
237
                                        .get(SqlAgentProvider.PROPS_KEY_DEFAULT_MAP_KEY_CASE_FORMAT));
1✔
238
                }
239
                if (settings.containsKey(SqlAgentProvider.PROPS_KEY_DEFAULT_INSERTS_TYPE)) {
1✔
240
                        this.insertsType = InsertsType.valueOf(settings
1✔
241
                                        .get(SqlAgentProvider.PROPS_KEY_DEFAULT_INSERTS_TYPE));
1✔
242
                }
243
                if (settings.containsKey(SqlAgentProviderImpl.PROPS_KEY_OUTPUT_EXCEPTION_LOG)) {
1✔
244
                        outputExceptionLog = Boolean
1✔
245
                                        .parseBoolean(settings.get(SqlAgentProviderImpl.PROPS_KEY_OUTPUT_EXCEPTION_LOG));
1✔
246
                } else {
247
                        outputExceptionLog = true;
×
248
                }
249
        }
1✔
250

251
        /**
252
         * {@inheritDoc}
253
         *
254
         * @see jp.co.future.uroborosql.SqlAgent#close()
255
         */
256
        @Override
257
        public void close() {
258
                transactionManager.close();
1✔
259
                if (COVERAGE_HANDLER_REF.get() != null) {
1✔
260
                        COVERAGE_HANDLER_REF.get().onSqlAgentClose();
1✔
261
                }
262
        }
1✔
263

264
        /**
265
         *
266
         * {@inheritDoc}
267
         *
268
         * @see jp.co.future.uroborosql.connection.ConnectionManager#getConnection()
269
         */
270
        @Override
271
        public Connection getConnection() {
272
                return transactionManager.getConnection();
1✔
273
        }
274

275
        /**
276
         *
277
         * {@inheritDoc}
278
         *
279
         * @see jp.co.future.uroborosql.tx.TransactionManager#required(java.lang.Runnable)
280
         */
281
        @Override
282
        public void required(final Runnable runnable) {
283
                transactionManager.required(runnable);
1✔
284
        }
1✔
285

286
        /**
287
         *
288
         * {@inheritDoc}
289
         *
290
         * @see jp.co.future.uroborosql.tx.TransactionManager#required(java.util.function.Supplier)
291
         */
292
        @Override
293
        public <R> R required(final Supplier<R> supplier) {
294
                return transactionManager.required(supplier);
1✔
295
        }
296

297
        /**
298
         * {@inheritDoc}
299
         *
300
         * @see jp.co.future.uroborosql.tx.TransactionManager#requiresNew(jp.co.future.uroborosql.tx.Runnable)
301
         */
302
        @Override
303
        public void requiresNew(final Runnable runnable) {
304
                transactionManager.requiresNew(runnable);
1✔
305
        }
1✔
306

307
        /**
308
         *
309
         * {@inheritDoc}
310
         *
311
         * @see jp.co.future.uroborosql.tx.TransactionManager#requiresNew(java.util.function.Supplier)
312
         */
313
        @Override
314
        public <R> R requiresNew(final Supplier<R> supplier) {
315
                return transactionManager.requiresNew(supplier);
1✔
316
        }
317

318
        /**
319
         *
320
         * {@inheritDoc}
321
         *
322
         * @see jp.co.future.uroborosql.tx.TransactionManager#notSupported(java.lang.Runnable)
323
         */
324
        @Override
325
        public void notSupported(final Runnable runnable) {
326
                transactionManager.notSupported(runnable);
1✔
327
        }
1✔
328

329
        /**
330
         *
331
         * {@inheritDoc}
332
         *
333
         * @see jp.co.future.uroborosql.tx.TransactionManager#notSupported(java.util.function.Supplier)
334
         */
335
        @Override
336
        public <R> R notSupported(final Supplier<R> supplier) {
337
                return transactionManager.notSupported(supplier);
1✔
338
        }
339

340
        /**
341
         *
342
         * {@inheritDoc}
343
         *
344
         * @see jp.co.future.uroborosql.tx.TransactionManager#setRollbackOnly()
345
         */
346
        @Override
347
        public void setRollbackOnly() {
348
                transactionManager.setRollbackOnly();
1✔
349
        }
1✔
350

351
        /**
352
         *
353
         * {@inheritDoc}
354
         *
355
         * @see jp.co.future.uroborosql.tx.TransactionManager#setSavepoint(java.lang.String)
356
         */
357
        @Override
358
        public void setSavepoint(final String savepointName) {
359
                transactionManager.setSavepoint(savepointName);
1✔
360
        }
1✔
361

362
        /**
363
         *
364
         * {@inheritDoc}
365
         *
366
         * @see jp.co.future.uroborosql.tx.TransactionManager#releaseSavepoint(java.lang.String)
367
         */
368
        @Override
369
        public void releaseSavepoint(final String savepointName) {
370
                transactionManager.releaseSavepoint(savepointName);
1✔
371
        }
1✔
372

373
        /**
374
         *
375
         * {@inheritDoc}
376
         *
377
         * @see jp.co.future.uroborosql.tx.TransactionManager#rollback(java.lang.String)
378
         */
379
        @Override
380
        public void rollback(final String savepointName) {
381
                transactionManager.rollback(savepointName);
1✔
382
        }
1✔
383

384
        /**
385
         *
386
         * {@inheritDoc}
387
         *
388
         * @see jp.co.future.uroborosql.tx.TransactionManager#savepointScope(java.util.function.Supplier)
389
         */
390
        @Override
391
        public <R> R savepointScope(final Supplier<R> supplier) {
392
                return transactionManager.savepointScope(supplier);
1✔
393
        }
394

395
        /**
396
         *
397
         * {@inheritDoc}
398
         *
399
         * @see jp.co.future.uroborosql.tx.TransactionManager#savepointScope(java.lang.Runnable)
400
         */
401
        @Override
402
        public void savepointScope(final Runnable runnable) {
403
                transactionManager.savepointScope(runnable);
1✔
404
        }
1✔
405

406
        /**
407
         *
408
         * {@inheritDoc}
409
         *
410
         * @see jp.co.future.uroborosql.tx.TransactionManager#autoCommitScope(java.util.function.Supplier)
411
         */
412
        @Override
413
        public <R> R autoCommitScope(final Supplier<R> supplier) {
414
                return transactionManager.autoCommitScope(supplier);
1✔
415
        }
416

417
        /**
418
         *
419
         * {@inheritDoc}
420
         *
421
         * @see jp.co.future.uroborosql.tx.TransactionManager#autoCommitScope(java.lang.Runnable)
422
         */
423
        @Override
424
        public void autoCommitScope(final Runnable runnable) {
425
                transactionManager.autoCommitScope(runnable);
1✔
426
        }
1✔
427

428
        /**
429
         *
430
         * {@inheritDoc}
431
         *
432
         * @see jp.co.future.uroborosql.connection.ConnectionManager#commit()
433
         */
434
        @Override
435
        public void commit() {
436
                transactionManager.commit();
1✔
437
        }
1✔
438

439
        /**
440
         *
441
         * {@inheritDoc}
442
         *
443
         * @see jp.co.future.uroborosql.connection.ConnectionManager#rollback()
444
         */
445
        @Override
446
        public void rollback() {
447
                transactionManager.rollback();
1✔
448
        }
1✔
449

450
        /**
451
         * {@inheritDoc}
452
         *
453
         * @see jp.co.future.uroborosql.SqlAgent#query(java.lang.String)
454
         */
455
        @Override
456
        public SqlQuery query(final String sqlName) {
457
                if ("".equals(sqlName)) {
1✔
458
                        throw new IllegalArgumentException("sqlName is required.");
1✔
459
                }
460
                return new SqlQueryImpl(this, context().setSqlName(sqlName));
1✔
461
        }
462

463
        /**
464
         * {@inheritDoc}
465
         *
466
         * @see jp.co.future.uroborosql.SqlAgent#query(java.util.function.Supplier)
467
         */
468
        @Override
469
        public SqlQuery query(final Supplier<String> supplier) {
470
                return this.query(supplier.get());
1✔
471
        }
472

473
        /**
474
         * {@inheritDoc}
475
         *
476
         * @see jp.co.future.uroborosql.SqlAgent#queryWith(java.lang.String)
477
         */
478
        @Override
479
        public SqlQuery queryWith(final String sql) {
480
                if (sql == null || "".equals(sql)) {
1✔
481
                        throw new IllegalArgumentException("sql is required.");
1✔
482
                }
483
                return new SqlQueryImpl(this, context().setSql(sql));
1✔
484
        }
485

486
        /**
487
         * {@inheritDoc}
488
         *
489
         * @see jp.co.future.uroborosql.SqlAgent#update(java.lang.String)
490
         */
491
        @Override
492
        public SqlUpdate update(final String sqlName) {
493
                if ("".equals(sqlName)) {
1✔
494
                        throw new IllegalArgumentException("sqlName is required.");
1✔
495
                }
496
                return new SqlUpdateImpl(this, context().setSqlName(sqlName));
1✔
497
        }
498

499
        /**
500
         * {@inheritDoc}
501
         *
502
         * @see jp.co.future.uroborosql.SqlAgent#update(java.util.function.Supplier)
503
         */
504
        @Override
505
        public SqlUpdate update(final Supplier<String> supplier) {
506
                return this.update(supplier.get());
1✔
507
        }
508

509
        /**
510
         * {@inheritDoc}
511
         *
512
         * @see jp.co.future.uroborosql.SqlAgent#updateWith(java.lang.String)
513
         */
514
        @Override
515
        public SqlUpdate updateWith(final String sql) {
516
                if (sql == null || "".equals(sql)) {
1✔
517
                        throw new IllegalArgumentException("sql is required.");
1✔
518
                }
519
                return new SqlUpdateImpl(this, context().setSql(sql));
1✔
520
        }
521

522
        /**
523
         * {@inheritDoc}
524
         *
525
         * @see jp.co.future.uroborosql.SqlAgent#batch(java.lang.String)
526
         */
527
        @Override
528
        public SqlBatch batch(final String sqlName) {
529
                if ("".equals(sqlName)) {
1✔
530
                        throw new IllegalArgumentException("sqlName is required.");
1✔
531
                }
532
                return new SqlBatchImpl(this, context().setSqlName(sqlName));
1✔
533
        }
534

535
        /**
536
         * {@inheritDoc}
537
         *
538
         * @see jp.co.future.uroborosql.SqlAgent#batch(java.util.function.Supplier)
539
         */
540
        @Override
541
        public SqlBatch batch(final Supplier<String> supplier) {
542
                return this.batch(supplier.get());
1✔
543
        }
544

545
        /**
546
         * {@inheritDoc}
547
         *
548
         * @see jp.co.future.uroborosql.SqlAgent#batchWith(java.lang.String)
549
         */
550
        @Override
551
        public SqlBatch batchWith(final String sql) {
552
                if (sql == null || "".equals(sql)) {
1✔
553
                        throw new IllegalArgumentException("sql is required.");
1✔
554
                }
555
                return new SqlBatchImpl(this, context().setSql(sql));
1✔
556
        }
557

558
        /**
559
         * {@inheritDoc}
560
         *
561
         * @see jp.co.future.uroborosql.SqlAgent#proc(java.lang.String)
562
         */
563
        @Override
564
        public Procedure proc(final String sqlName) {
565
                if ("".equals(sqlName)) {
1✔
566
                        throw new IllegalArgumentException("sqlName is required.");
1✔
567
                }
568
                return new ProcedureImpl(this, context().setSqlName(sqlName));
1✔
569
        }
570

571
        /**
572
         * {@inheritDoc}
573
         *
574
         * @see jp.co.future.uroborosql.SqlAgent#proc(java.util.function.Supplier)
575
         */
576
        @Override
577
        public Procedure proc(final Supplier<String> supplier) {
578
                return this.proc(supplier.get());
1✔
579
        }
580

581
        /**
582
         * {@inheritDoc}
583
         *
584
         * @see jp.co.future.uroborosql.SqlAgent#procWith(java.lang.String)
585
         */
586
        @Override
587
        public Procedure procWith(final String sql) {
588
                if (sql == null || "".equals(sql)) {
1✔
589
                        throw new IllegalArgumentException("sql is required.");
1✔
590
                }
591
                return new ProcedureImpl(this, context().setSql(sql));
1✔
592
        }
593

594
        /**
595
         * {@inheritDoc}
596
         *
597
         * @see jp.co.future.uroborosql.SqlAgent#query(java.lang.Class)
598
         */
599
        @Override
600
        public <E> SqlEntityQuery<E> query(final Class<? extends E> entityType) {
601
                var handler = this.getEntityHandler();
1✔
602
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
603
                        throw new IllegalArgumentException("Entity type not supported");
×
604
                }
605
                try {
606
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
607
                        var context = handler.createSelectContext(this, metadata, entityType, false);
1✔
608
                        context.setSqlKind(SqlKind.ENTITY_SELECT);
1✔
609
                        return new SqlEntityQueryImpl<>(this, handler, metadata, context, entityType);
1✔
610
                } catch (SQLException ex) {
×
611
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_SELECT, ex);
×
612
                }
613
        }
614

615
        /**
616
         * {@inheritDoc}
617
         *
618
         * @see jp.co.future.uroborosql.SqlAgent#inserts(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition, jp.co.future.uroborosql.enums.InsertsType)
619
         */
620
        @Override
621
        public <E> int inserts(final Class<E> entityType,
622
                        final Stream<E> entities,
623
                        final InsertsCondition<? super E> condition,
624
                        final InsertsType insertsType) {
625
                if (insertsType == InsertsType.BULK && getDialect().supportsBulkInsert()) {
1✔
626
                        return bulkInsert(entityType, entities, condition, null);
1✔
627
                } else {
628
                        return batchInsert(entityType, entities, condition, null);
1✔
629
                }
630
        }
631

632
        /**
633
         * {@inheritDoc}
634
         *
635
         * @see jp.co.future.uroborosql.SqlAgent#inserts(java.lang.Class, java.util.stream.Stream)
636
         */
637
        @Override
638
        public <E> int inserts(final Class<E> entityType,
639
                        final Stream<E> entities) {
640
                return inserts(entityType, entities, getInsertsCondition(getInsertsType()));
1✔
641
        }
642

643
        /**
644
         * {@inheritDoc}
645
         *
646
         * @see jp.co.future.uroborosql.SqlAgent#inserts(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition)
647
         */
648
        @Override
649
        public <E> int inserts(final Class<E> entityType,
650
                        final Stream<E> entities,
651
                        final InsertsCondition<? super E> condition) {
652
                return inserts(entityType, entities, condition, getInsertsType());
1✔
653
        }
654

655
        /**
656
         * {@inheritDoc}
657
         *
658
         * @see jp.co.future.uroborosql.SqlAgent#inserts(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.enums.InsertsType)
659
         */
660
        @Override
661
        public <E> int inserts(final Class<E> entityType,
662
                        final Stream<E> entities,
663
                        final InsertsType insertsType) {
664
                return inserts(entityType, entities, getInsertsCondition(insertsType), insertsType);
1✔
665
        }
666

667
        /**
668
         * {@inheritDoc}
669
         *
670
         * @see jp.co.future.uroborosql.SqlAgent#inserts(java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition, jp.co.future.uroborosql.enums.InsertsType)
671
         */
672
        @Override
673
        public <E> int inserts(final Stream<E> entities,
674
                        final InsertsCondition<? super E> condition,
675
                        final InsertsType insertsType) {
676
                var iterator = entities.iterator();
1✔
677
                if (!iterator.hasNext()) {
1✔
678
                        return 0;
1✔
679
                }
680
                var firstEntity = iterator.next();
1✔
681
                @SuppressWarnings("unchecked")
682
                var type = (Class<E>) firstEntity.getClass();
1✔
683
                var stream = Stream.concat(Stream.of(firstEntity),
1✔
684
                                StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.NONNULL), false));
1✔
685
                return inserts(type, stream, condition, insertsType);
1✔
686
        }
687

688
        /**
689
         * {@inheritDoc}
690
         *
691
         * @see jp.co.future.uroborosql.SqlAgent#inserts(java.util.stream.Stream)
692
         */
693
        @Override
694
        public <E> int inserts(final Stream<E> entities) {
695
                return inserts(entities, getInsertsCondition(getInsertsType()));
1✔
696
        }
697

698
        /**
699
         * {@inheritDoc}
700
         *
701
         * @see jp.co.future.uroborosql.SqlAgent#inserts(java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition)
702
         */
703
        @Override
704
        public <E> int inserts(final Stream<E> entities,
705
                        final InsertsCondition<? super E> condition) {
706
                return inserts(entities, condition, getInsertsType());
1✔
707
        }
708

709
        /**
710
         * {@inheritDoc}
711
         *
712
         * @see jp.co.future.uroborosql.SqlAgent#inserts(java.util.stream.Stream, jp.co.future.uroborosql.enums.InsertsType)
713
         */
714
        @Override
715
        public <E> int inserts(final Stream<E> entities,
716
                        final InsertsType insertsType) {
717
                return inserts(entities, getInsertsCondition(insertsType), insertsType);
1✔
718
        }
719

720
        /**
721
         * {@inheritDoc}
722
         *
723
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition, jp.co.future.uroborosql.enums.InsertsType)
724
         */
725
        @Override
726
        public <E> Stream<E> insertsAndReturn(final Class<E> entityType,
727
                        final Stream<E> entities,
728
                        final InsertsCondition<? super E> condition,
729
                        final InsertsType insertsType) {
730
                var insertedEntities = new ArrayList<E>();
1✔
731
                if (insertsType == InsertsType.BULK && getDialect().supportsBulkInsert()) {
1✔
732
                        bulkInsert(entityType, entities, condition, insertedEntities);
1✔
733
                } else {
734
                        batchInsert(entityType, entities, condition, insertedEntities);
1✔
735
                }
736
                return insertedEntities.stream();
1✔
737
        }
738

739
        /**
740
         * {@inheritDoc}
741
         *
742
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition)
743
         */
744
        @Override
745
        public <E> Stream<E> insertsAndReturn(final Class<E> entityType,
746
                        final Stream<E> entities,
747
                        final InsertsCondition<? super E> condition) {
748
                return insertsAndReturn(entityType, entities, condition, getInsertsType());
1✔
749
        }
750

751
        /**
752
         * {@inheritDoc}
753
         *
754
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.lang.Class, java.util.stream.Stream)
755
         */
756
        @Override
757
        public <E> Stream<E> insertsAndReturn(final Class<E> entityType,
758
                        final Stream<E> entities) {
759
                return insertsAndReturn(entityType, entities, getInsertsCondition(getInsertsType()));
1✔
760
        }
761

762
        /**
763
         * {@inheritDoc}
764
         *
765
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.enums.InsertsType)
766
         */
767
        @Override
768
        public <E> Stream<E> insertsAndReturn(final Class<E> entityType,
769
                        final Stream<E> entities,
770
                        final InsertsType insertsType) {
771
                return insertsAndReturn(entityType, entities, getInsertsCondition(insertsType), insertsType);
1✔
772
        }
773

774
        /**
775
         * {@inheritDoc}
776
         *
777
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition, jp.co.future.uroborosql.enums.InsertsType)
778
         */
779
        @Override
780
        public <E> Stream<E> insertsAndReturn(final Stream<E> entities,
781
                        final InsertsCondition<? super E> condition,
782
                        final InsertsType insertsType) {
783
                var iterator = entities.iterator();
1✔
784
                if (!iterator.hasNext()) {
1✔
785
                        return Stream.empty();
1✔
786
                }
787
                var firstEntity = iterator.next();
1✔
788
                @SuppressWarnings("unchecked")
789
                var type = (Class<E>) firstEntity.getClass();
1✔
790
                var stream = Stream.concat(Stream.of(firstEntity),
1✔
791
                                StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.NONNULL), false));
1✔
792
                return insertsAndReturn(type, stream, condition, insertsType);
1✔
793
        }
794

795
        /**
796
         * {@inheritDoc}
797
         *
798
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition)
799
         */
800
        @Override
801
        public <E> Stream<E> insertsAndReturn(final Stream<E> entities,
802
                        final InsertsCondition<? super E> condition) {
803
                return insertsAndReturn(entities, condition, getInsertsType());
1✔
804
        }
805

806
        /**
807
         * {@inheritDoc}
808
         *
809
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.util.stream.Stream)
810
         */
811
        @Override
812
        public <E> Stream<E> insertsAndReturn(final Stream<E> entities) {
813
                return insertsAndReturn(entities, getInsertsCondition(getInsertsType()));
1✔
814
        }
815

816
        /**
817
         * {@inheritDoc}
818
         *
819
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.util.stream.Stream, jp.co.future.uroborosql.enums.InsertsType)
820
         */
821
        @Override
822
        public <E> Stream<E> insertsAndReturn(final Stream<E> entities,
823
                        final InsertsType insertsType) {
824
                return insertsAndReturn(entities, getInsertsCondition(insertsType), insertsType);
1✔
825
        }
826

827
        /**
828
         * {@inheritDoc}
829
         *
830
         * @see jp.co.future.uroborosql.SqlAgent#updates(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.UpdatesCondition)
831
         */
832
        @Override
833
        public <E> int updates(final Class<E> entityType,
834
                        final Stream<E> entities,
835
                        final UpdatesCondition<? super E> condition) {
836
                return batchUpdate(entityType, entities, condition, null);
1✔
837
        }
838

839
        /**
840
         * {@inheritDoc}
841
         *
842
         * @see jp.co.future.uroborosql.SqlAgent#updatesAndReturn(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.UpdatesCondition)
843
         */
844
        @Override
845
        public <E> Stream<E> updatesAndReturn(final Class<E> entityType,
846
                        final Stream<E> entities,
847
                        final UpdatesCondition<? super E> condition) {
848
                var updatedEntities = new ArrayList<E>();
1✔
849
                batchUpdate(entityType, entities, condition, updatedEntities);
1✔
850
                return updatedEntities.stream();
1✔
851
        }
852

853
        /**
854
         * {@inheritDoc}
855
         *
856
         * @see jp.co.future.uroborosql.SqlAgent#updates(java.lang.Class, java.util.stream.Stream)
857
         */
858
        @Override
859
        public <E> int updates(final Class<E> entityType,
860
                        final Stream<E> entities) {
861
                return updates(entityType, entities, DEFAULT_UPDATES_WHEN_CONDITION);
1✔
862
        }
863

864
        /**
865
         * {@inheritDoc}
866
         *
867
         * @see jp.co.future.uroborosql.SqlAgent#updatesAndReturn(java.lang.Class, java.util.stream.Stream)
868
         */
869
        @Override
870
        public <E> Stream<E> updatesAndReturn(final Class<E> entityType,
871
                        final Stream<E> entities) {
872
                return updatesAndReturn(entityType, entities, DEFAULT_UPDATES_WHEN_CONDITION);
1✔
873
        }
874

875
        /**
876
         * {@inheritDoc}
877
         *
878
         * @see jp.co.future.uroborosql.SqlAgent#updates(java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.UpdatesCondition)
879
         */
880
        @Override
881
        public <E> int updates(final Stream<E> entities,
882
                        final UpdatesCondition<? super E> condition) {
883
                var iterator = entities.iterator();
1✔
884
                if (!iterator.hasNext()) {
1✔
885
                        return 0;
×
886
                }
887
                var firstEntity = iterator.next();
1✔
888
                @SuppressWarnings("unchecked")
889
                var type = (Class<E>) firstEntity.getClass();
1✔
890
                var stream = Stream.concat(Stream.of(firstEntity),
1✔
891
                                StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.NONNULL), false));
1✔
892
                return updates(type, stream, condition);
1✔
893
        }
894

895
        /**
896
         * {@inheritDoc}
897
         *
898
         * @see jp.co.future.uroborosql.SqlAgent#updatesAndReturn(java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.UpdatesCondition)
899
         */
900
        @Override
901
        public <E> Stream<E> updatesAndReturn(final Stream<E> entities,
902
                        final UpdatesCondition<? super E> condition) {
903
                var iterator = entities.iterator();
1✔
904
                if (!iterator.hasNext()) {
1✔
905
                        return Stream.empty();
×
906
                }
907
                var firstEntity = iterator.next();
1✔
908
                @SuppressWarnings("unchecked")
909
                var type = (Class<E>) firstEntity.getClass();
1✔
910
                var stream = Stream.concat(Stream.of(firstEntity),
1✔
911
                                StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.NONNULL), false));
1✔
912
                return updatesAndReturn(type, stream, condition);
1✔
913
        }
914

915
        /**
916
         * {@inheritDoc}
917
         *
918
         * @see jp.co.future.uroborosql.SqlAgent#updates(java.util.stream.Stream)
919
         */
920
        @Override
921
        public <E> int updates(final Stream<E> entities) {
922
                return updates(entities, DEFAULT_UPDATES_WHEN_CONDITION);
1✔
923
        }
924

925
        /**
926
         * {@inheritDoc}
927
         *
928
         * @see jp.co.future.uroborosql.SqlAgent#updatesAndReturn(java.util.stream.Stream)
929
         */
930
        @Override
931
        public <E> Stream<E> updatesAndReturn(final Stream<E> entities) {
932
                return updatesAndReturn(entities, DEFAULT_UPDATES_WHEN_CONDITION);
1✔
933
        }
934

935
        private <E> InsertsCondition<? super E> getInsertsCondition(final InsertsType insertsType) {
936
                return InsertsType.BATCH.equals(insertsType)
1✔
937
                                ? DEFAULT_BATCH_INSERTS_WHEN_CONDITION
1✔
938
                                : DEFAULT_BULK_INSERTS_WHEN_CONDITION;
1✔
939
        }
940

941
        /**
942
         * 複数エンティティのBULK INSERTを実行
943
         *
944
         * @param <E> エンティティの型
945
         * @param entityType エンティティの型
946
         * @param entities エンティティ
947
         * @param condition 一括INSERT用のフレームの判定条件
948
         * @param insertedEntities INSERTしたEntityのList. <code>null</code>の場合は格納されない
949
         * @return SQL実行結果
950
         */
951
        @SuppressWarnings("deprecation")
952
        private <E> int batchInsert(final Class<E> entityType, final Stream<E> entities,
953
                        final InsertsCondition<? super E> condition, final List<E> insertedEntities) {
954
                EntityHandler<E> handler = this.getEntityHandler();
1✔
955
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
956
                        throw new IllegalArgumentException("Entity type not supported");
1✔
957
                }
958

959
                try {
960
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
961
                        var context = handler.createBatchInsertContext(this, metadata, entityType);
1✔
962
                        context.setSqlKind(SqlKind.ENTITY_BATCH_INSERT);
1✔
963

964
                        var count = 0;
1✔
965
                        var entityList = new ArrayList<E>();
1✔
966
                        var isFirst = true;
1✔
967
                        List<MappingColumn> autoGeneratedColumns = List.of();
1✔
968
                        Map<String, Object> nonNullObjectIdFlags = null;
1✔
969
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
970
                        var hasBeforeEntityBatchInsertListener = eventListenerHolder.hasBeforeEntityBatchInsertListener();
1✔
971
                        var eventObj = hasBeforeEntityBatchInsertListener
1✔
972
                                        ? new BeforeEntityBatchInsertEvent(context, null, entityType)
1✔
973
                                        : null;
1✔
974
                        for (var iterator = entities.iterator(); iterator.hasNext();) {
1✔
975
                                var entity = iterator.next();
1✔
976

977
                                if (!entityType.isInstance(entity)) {
1✔
978
                                        throw new IllegalArgumentException("Entity types do not match");
1✔
979
                                }
980

981
                                if (isFirst) {
1✔
982
                                        isFirst = false;
1✔
983
                                        var mappingColumns = MappingUtils.getMappingColumns(metadata.getSchema(), entityType);
1✔
984
                                        autoGeneratedColumns = getAutoGeneratedColumns(context, mappingColumns, metadata, entity);
1✔
985

986
                                        // SQLのID項目IF分岐判定をtrueにするために値が設定されているID項目を保持しておく
987
                                        var excludeColumns = autoGeneratedColumns;
1✔
988
                                        nonNullObjectIdFlags = Arrays.stream(mappingColumns)
1✔
989
                                                        .filter(col -> !excludeColumns.contains(col)
1✔
990
                                                                        && !col.getJavaType().getRawType().isPrimitive()
1✔
991
                                                                        && col.getValue(entity) != null)
1✔
992
                                                        .collect(Collectors.toMap(MappingColumn::getCamelName, col -> true));
1✔
993
                                }
994

995
                                entityList.add(entity);
1✔
996
                                if (insertedEntities != null) {
1✔
997
                                        insertedEntities.add(entity);
1✔
998
                                }
999

1000
                                // EntityBatchInsert実行前イベント発行
1001
                                if (hasBeforeEntityBatchInsertListener) {
1✔
1002
                                        // BatchInsertでは大量のEntityが処理されるため、実行前イベントでは都度Eventインスタンスを生成せず、1つのイベントインスタンスを使いまわす実装とする(性能対応)
1003
                                        eventObj.setEntity(entity);
1✔
1004
                                        for (var listener : eventListenerHolder.getBeforeEntityBatchInsertListeners()) {
1✔
1005
                                                listener.accept(eventObj);
1✔
1006
                                        }
1✔
1007
                                }
1008
                                handler.setInsertParams(context, entity);
1✔
1009

1010
                                context.addBatch();
1✔
1011
                                // SQLのID項目IF分岐判定をtrueにするためにaddBatch()の後に保持しておいたID項目をcontextにバインドする
1012
                                if (nonNullObjectIdFlags != null && !nonNullObjectIdFlags.isEmpty()) {
1✔
1013
                                        context.paramMap(nonNullObjectIdFlags);
1✔
1014
                                }
1015
                                if (condition.test(context, context.batchCount(), entity)) {
1✔
1016
                                        count += Arrays
1✔
1017
                                                        .stream(doBatchInsert(context, handler, entityList, entityType, autoGeneratedColumns))
1✔
1018
                                                        .sum();
1✔
1019
                                        entityList.clear();
1✔
1020
                                }
1021
                        }
1✔
1022
                        return count + (context.batchCount() != 0
1✔
1023
                                        ? Arrays.stream(doBatchInsert(context, handler, entityList, entityType, autoGeneratedColumns)).sum()
1✔
1024
                                        : 0);
1✔
1025
                } catch (SQLException ex) {
×
1026
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_BATCH_INSERT, ex);
×
1027
                }
1028
        }
1029

1030
        private <E> int[] doBatchInsert(final ExecutionContext context,
1031
                        final EntityHandler<E> handler,
1032
                        final List<E> entityList,
1033
                        final Class<E> entityType,
1034
                        final List<MappingColumn> autoGeneratedColumns)
1035
                        throws SQLException {
1036
                var counts = handler.doBatchInsert(this, context);
1✔
1037

1038
                // EntityBatchInsert実行後イベント発行
1039
                var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
1040
                if (eventListenerHolder.hasAfterEntityBatchInsertListener()) {
1✔
1041
                        var eventObj = new AfterEntityBatchInsertEvent(context, entityList, entityType, counts);
1✔
1042
                        for (var listener : eventListenerHolder.getAfterEntityBatchInsertListeners()) {
1✔
1043
                                listener.accept(eventObj);
1✔
1044
                        }
1✔
1045
                        counts = eventObj.getCounts();
1✔
1046
                }
1047

1048
                if (!autoGeneratedColumns.isEmpty()) {
1✔
1049
                        var ids = context.getGeneratedKeyValues();
1✔
1050
                        var idx = 0;
1✔
1051
                        for (E ent : entityList) {
1✔
1052
                                for (var col : autoGeneratedColumns) {
1✔
1053
                                        setEntityIdValue(ent, ids[idx++], col);
1✔
1054
                                }
1✔
1055
                        }
1✔
1056
                }
1057

1058
                return counts;
1✔
1059
        }
1060

1061
        /**
1062
         * 複数エンティティのINSERTをバッチ実行
1063
         *
1064
         * @param <E> エンティティの型
1065
         * @param entityType エンティティの型
1066
         * @param entities エンティティ
1067
         * @param condition 一括INSERT用のフレームの判定条件
1068
         * @param insertedEntities INSERTしたEntityのList. <code>null</code>の場合は格納されない
1069
         * @return SQL実行結果
1070
         */
1071
        @SuppressWarnings("deprecation")
1072
        private <E> int bulkInsert(final Class<E> entityType, final Stream<E> entities,
1073
                        final InsertsCondition<? super E> condition, final List<E> insertedEntities) {
1074
                EntityHandler<E> handler = this.getEntityHandler();
1✔
1075
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
1076
                        throw new IllegalArgumentException("Entity type not supported");
×
1077
                }
1078

1079
                try {
1080
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
1081
                        var context = handler.createBulkInsertContext(this, metadata, entityType);
1✔
1082
                        context.setSqlKind(SqlKind.ENTITY_BULK_INSERT);
1✔
1083

1084
                        var frameCount = 0;
1✔
1085
                        var count = 0;
1✔
1086
                        var isFirst = true;
1✔
1087
                        List<MappingColumn> autoGeneratedColumns = List.of();
1✔
1088
                        Map<String, Object> nonNullObjectIdFlags = null;
1✔
1089
                        var entityList = new ArrayList<E>();
1✔
1090
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
1091
                        var hasBeforeEntityBulkInsertListener = eventListenerHolder.hasBeforeEntityBulkInsertListener();
1✔
1092
                        var eventObj = hasBeforeEntityBulkInsertListener
1✔
1093
                                        ? new BeforeEntityBulkInsertEvent(context, null, entityType, frameCount)
1✔
1094
                                        : null;
1✔
1095
                        for (var iterator = entities.iterator(); iterator.hasNext();) {
1✔
1096
                                var entity = iterator.next();
1✔
1097

1098
                                if (!entityType.isInstance(entity)) {
1✔
1099
                                        throw new IllegalArgumentException("Entity types do not match");
×
1100
                                }
1101

1102
                                if (isFirst) {
1✔
1103
                                        isFirst = false;
1✔
1104
                                        var mappingColumns = MappingUtils.getMappingColumns(metadata.getSchema(), entityType);
1✔
1105
                                        autoGeneratedColumns = getAutoGeneratedColumns(context, mappingColumns, metadata, entity);
1✔
1106

1107
                                        // SQLのID項目IF分岐判定をtrueにするために値が設定されているID項目を保持しておく
1108
                                        var excludeColumns = autoGeneratedColumns;
1✔
1109
                                        // indexなしのid値がcontextにバインドされないため、値としてkey=trueを退避しておく
1110
                                        nonNullObjectIdFlags = Arrays.stream(mappingColumns)
1✔
1111
                                                        .filter(col -> !excludeColumns.contains(col)
1✔
1112
                                                                        && !col.getJavaType().getRawType().isPrimitive()
1✔
1113
                                                                        && col.getValue(entity) != null)
1✔
1114
                                                        .collect(Collectors.toMap(MappingColumn::getCamelName, col -> true));
1✔
1115
                                }
1116
                                // 退避しておいたid値をこのタイミングで設定する
1117
                                if (nonNullObjectIdFlags != null && !nonNullObjectIdFlags.isEmpty()) {
1✔
1118
                                        context.paramMap(nonNullObjectIdFlags);
1✔
1119
                                }
1120

1121
                                entityList.add(entity);
1✔
1122
                                if (insertedEntities != null) {
1✔
1123
                                        insertedEntities.add(entity);
1✔
1124
                                }
1125

1126
                                // EntityBulkInsert実行前イベント発行
1127
                                if (hasBeforeEntityBulkInsertListener) {
1✔
1128
                                        // BulkInsertでは大量のEntityが処理されるため、実行前イベントでは都度Eventインスタンスを生成せず、1つのイベントインスタンスを使いまわす実装とする(性能対応)
1129
                                        eventObj.setEntity(entity);
1✔
1130
                                        eventObj.setFrameCount(frameCount);
1✔
1131
                                        for (var listener : eventListenerHolder.getBeforeEntityBulkInsertListeners()) {
1✔
1132
                                                listener.accept(eventObj);
1✔
1133
                                        }
1✔
1134
                                }
1135
                                handler.setBulkInsertParams(context, entity, frameCount);
1✔
1136

1137
                                frameCount++;
1✔
1138

1139
                                if (condition.test(context, frameCount, entity)) {
1✔
1140
                                        count += doBulkInsert(context, handler, entityList, entityType, metadata, autoGeneratedColumns,
1✔
1141
                                                        frameCount);
1142
                                        frameCount = 0;
1✔
1143
                                        entityList.clear();
1✔
1144

1145
                                        // 新しいExecutionContextを作成する前にgeneratedKeyColumnsを退避しておく
1146
                                        var generatedKeyColumns = context.getGeneratedKeyColumns();
1✔
1147
                                        context = handler.createBulkInsertContext(this, metadata, entityType);
1✔
1148
                                        context.setSqlKind(SqlKind.ENTITY_BULK_INSERT);
1✔
1149
                                        // 実行結果から生成されたIDを取得できるようにPreparedStatementにIDカラムを渡す
1150
                                        context.setGeneratedKeyColumns(generatedKeyColumns);
1✔
1151
                                }
1152
                        }
1✔
1153
                        return count + (frameCount > 0
1✔
1154
                                        ? doBulkInsert(context, handler, entityList, entityType, metadata, autoGeneratedColumns, frameCount)
1✔
1155
                                        : 0);
1✔
1156

1157
                } catch (SQLException ex) {
×
1158
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_BULK_INSERT, ex);
×
1159
                }
1160
        }
1161

1162
        private <E> int doBulkInsert(final ExecutionContext context,
1163
                        final EntityHandler<E> handler,
1164
                        final List<E> entityList,
1165
                        final Class<E> entityType,
1166
                        final TableMetadata metadata,
1167
                        final List<MappingColumn> autoGeneratedColumns,
1168
                        final int frameCount)
1169
                        throws SQLException {
1170
                var count = handler.doBulkInsert(this,
1✔
1171
                                handler.setupSqlBulkInsertContext(this, context, metadata, entityType, entityList.size()));
1✔
1172

1173
                // EntityBulkInsert実行前イベント発行
1174
                var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
1175
                if (eventListenerHolder.hasAfterEntityBulkInsertListener()) {
1✔
1176
                        var eventObj = new AfterEntityBulkInsertEvent(context, entityList, entityType, frameCount, count);
1✔
1177
                        for (var listener : eventListenerHolder.getAfterEntityBulkInsertListeners()) {
1✔
1178
                                listener.accept(eventObj);
1✔
1179
                        }
1✔
1180
                        count = eventObj.getCount();
1✔
1181
                }
1182

1183
                if (!autoGeneratedColumns.isEmpty()) {
1✔
1184
                        var ids = context.getGeneratedKeyValues();
1✔
1185
                        var idx = 0;
1✔
1186
                        for (E ent : entityList) {
1✔
1187
                                for (var col : autoGeneratedColumns) {
1✔
1188
                                        setEntityIdValue(ent, ids[idx++], col);
1✔
1189
                                }
1✔
1190
                        }
1✔
1191
                }
1192

1193
                return count;
1✔
1194
        }
1195

1196
        /**
1197
         * 複数エンティティのBATCH UPDATEを実行
1198
         *
1199
         * @param <E> エンティティの型
1200
         * @param entityType エンティティの型
1201
         * @param entities エンティティ
1202
         * @param condition 一括更新用のフレームの判定条件
1203
         * @param updatedEntities INSERTしたEntityのList. <code>null</code>の場合は格納されない
1204
         * @return SQL実行結果
1205
         */
1206
        @SuppressWarnings("deprecation")
1207
        private <E> int batchUpdate(final Class<E> entityType, final Stream<E> entities,
1208
                        final UpdatesCondition<? super E> condition, final List<E> updatedEntities) {
1209
                var handler = this.getEntityHandler();
1✔
1210
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
1211
                        throw new IllegalArgumentException("Entity type not supported");
×
1212
                }
1213

1214
                try {
1215
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
1216
                        var context = handler.createBatchUpdateContext(this, metadata, entityType);
1✔
1217
                        context.setSqlKind(SqlKind.BATCH_UPDATE);
1✔
1218

1219
                        var versionColumn = MappingUtils.getVersionMappingColumn(metadata.getSchema(),
1✔
1220
                                        entityType);
1221
                        var entityCount = 0;
1✔
1222
                        var updateCount = 0;
1✔
1223
                        var entityList = new ArrayList<E>();
1✔
1224
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
1225
                        var hasBeforeEntityBatchUpdateListener = eventListenerHolder.hasBeforeEntityBatchUpdateListener();
1✔
1226
                        var eventObj = hasBeforeEntityBatchUpdateListener
1✔
1227
                                        ? new BeforeEntityBatchUpdateEvent(context, null, entityType)
1✔
1228
                                        : null;
1✔
1229
                        for (var iterator = entities.iterator(); iterator.hasNext();) {
1✔
1230
                                var entity = iterator.next();
1✔
1231

1232
                                if (!entityType.isInstance(entity)) {
1✔
1233
                                        throw new IllegalArgumentException("Entity types do not match");
×
1234
                                }
1235

1236
                                entityList.add(entity);
1✔
1237
                                if (updatedEntities != null) {
1✔
1238
                                        updatedEntities.add(entity);
1✔
1239
                                }
1240
                                entityCount++;
1✔
1241

1242
                                // EntityBatchUpdate実行前イベント発行
1243
                                if (hasBeforeEntityBatchUpdateListener) {
1✔
1244
                                        // BatchUpdateでは大量のEntityが処理されるため、実行前イベントでは都度Eventインスタンスを生成せず、1つのイベントインスタンスを使いまわす実装とする(性能対応)
1245
                                        eventObj.setEntity(entity);
1✔
1246
                                        for (var listener : eventListenerHolder.getBeforeEntityBatchUpdateListeners()) {
1✔
1247
                                                listener.accept(eventObj);
1✔
1248
                                        }
1✔
1249
                                }
1250

1251
                                handler.setUpdateParams(context, entity);
1✔
1252
                                context.addBatch();
1✔
1253

1254
                                if (condition.test(context, context.batchCount(), entity)) {
1✔
1255
                                        updateCount += Arrays.stream(doBatchUpdate(handler, context, entityList, entityType)).sum();
1✔
1256
                                        entityList.clear();
1✔
1257
                                }
1258
                        }
1✔
1259
                        updateCount = updateCount + (context.batchCount() != 0
1✔
1260
                                        ? Arrays.stream(doBatchUpdate(handler, context, entityList, entityType)).sum()
1✔
1261
                                        : 0);
1✔
1262

1263
                        if (updatedEntities != null && versionColumn.isPresent()) {
1✔
1264
                                var vColumn = versionColumn.orElseThrow();
1✔
1265
                                var keyColumns = metadata.getColumns().stream()
1✔
1266
                                                .filter(TableMetadata.Column::isKey)
1✔
1267
                                                .sorted(Comparator.comparingInt(TableMetadata.Column::getKeySeq))
1✔
1268
                                                .map(c -> MappingUtils.getMappingColumnMap(metadata.getSchema(), entityType, SqlKind.NONE)
1✔
1269
                                                                .get(c.getCamelColumnName()))
1✔
1270
                                                .collect(Collectors.toList());
1✔
1271

1272
                                if (keyColumns.size() == 1) {
1✔
1273
                                        // 単一キーの場合はIN句で更新した行を一括取得し@Versionのついたフィールドを更新する
1274
                                        var keyColumn = keyColumns.get(0);
1✔
1275
                                        var updatedEntityMap = updatedEntities.stream()
1✔
1276
                                                        .collect(Collectors.groupingBy(e -> keyColumn.getValue(e)));
1✔
1277

1278
                                        // updatedEntitiesのサイズが大きいとin句の上限にあたるため、1000件ずつに分割して検索する
1279
                                        var keyList = new ArrayList<>(updatedEntityMap.keySet());
1✔
1280
                                        var entitySize = updatedEntities.size();
1✔
1281

1282
                                        for (var start = 0; start < entitySize; start = start + IN_CLAUSE_MAX_PARAM_SIZE) {
1✔
1283
                                                var end = Math.min(start + IN_CLAUSE_MAX_PARAM_SIZE, entitySize);
1✔
1284
                                                var subList = keyList.subList(start, end);
1✔
1285

1286
                                                query(entityType).in(keyColumn.getCamelName(), subList).stream()
1✔
1287
                                                                .map(e -> {
1✔
1288
                                                                        var updatedEntity = updatedEntityMap.get(keyColumn.getValue(e)).get(0);
1✔
1289
                                                                        vColumn.setValue(updatedEntity, vColumn.getValue(e));
1✔
1290
                                                                        return updatedEntity;
1✔
1291
                                                                }).count();
1✔
1292
                                        }
1293
                                } else if (keyColumns.size() > 1) {
1✔
1294
                                        // 複合キーの場合はIN句で一括取得できないため1件ずつ取得して@Versionのついたフィールドを更新する
1295
                                        updatedEntities.stream()
1✔
1296
                                                        .map(updatedEntity -> {
1✔
1297
                                                                var keyValues = keyColumns.stream().map(k -> k.getValue(updatedEntity)).toArray();
×
1298
                                                                find(entityType, keyValues).ifPresent(e -> {
×
1299
                                                                        vColumn.setValue(updatedEntity, vColumn.getValue(e));
×
1300
                                                                });
×
1301
                                                                return updatedEntity;
×
1302
                                                        }).count();
1✔
1303
                                }
1304
                        }
1305
                        if (versionColumn.isPresent() && getDialect().supportsEntityBulkUpdateOptimisticLock()
1✔
1306
                                        && updateCount != entityCount) {
1307
                                // バージョンカラムの指定があり、更新件数と更新対象Entityの件数が不一致の場合は楽観ロックエラーとする
1308
                                throw new OptimisticLockException(String.format(
1✔
1309
                                                "An error occurred due to optimistic locking.%nExecuted SQL [%n%s]%nBatch Entity Count: %d, Update Count: %d.",
1310
                                                context.getExecutableSql(), entityCount, updateCount));
1✔
1311
                        }
1312
                        return updateCount;
1✔
1313
                } catch (SQLException ex) {
×
1314
                        throw new EntitySqlRuntimeException(SqlKind.BATCH_UPDATE, ex);
×
1315
                }
1316
        }
1317

1318
        private <E> int[] doBatchUpdate(final EntityHandler<Object> handler,
1319
                        final ExecutionContext context,
1320
                        final List<E> entityList,
1321
                        final Class<E> entityType) throws SQLException {
1322
                var counts = handler.doBatchUpdate(this, context);
1✔
1323

1324
                // EntityBatchUpdate実行後イベント発行
1325
                var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
1326
                if (eventListenerHolder.hasAfterEntityBatchUpdateListener()) {
1✔
1327
                        var eventObj = new AfterEntityBatchUpdateEvent(context, entityList, entityType, counts);
1✔
1328
                        for (var listener : eventListenerHolder.getAfterEntityBatchUpdateListeners()) {
1✔
1329
                                listener.accept(eventObj);
1✔
1330
                        }
1✔
1331
                        counts = eventObj.getCounts();
1✔
1332
                }
1333

1334
                return counts;
1✔
1335
        }
1336

1337
        /**
1338
         * {@inheritDoc}
1339
         *
1340
         * @see jp.co.future.uroborosql.SqlAgent#query(jp.co.future.uroborosql.context.ExecutionContext)
1341
         */
1342
        @Override
1343
        public ResultSet query(final ExecutionContext executionContext) throws SQLException {
1344
                if (SqlKind.NONE.equals(executionContext.getSqlKind())) {
1✔
1345
                        executionContext.setSqlKind(SqlKind.SELECT);
1✔
1346
                }
1347

1348
                // コンテキスト変換
1349
                transformContext(executionContext);
1✔
1350

1351
                var stmt = getPreparedStatement(executionContext);
1✔
1352

1353
                // INパラメータ設定
1354
                executionContext.bindParams(stmt);
1✔
1355

1356
                // REPLで実行するための文字列をREPLログに出力する
1357
                outputReplLog(executionContext);
1✔
1358

1359
                LOG.atDebug()
1✔
1360
                                .setMessage("Execute query sql. sqlName: {}")
1✔
1361
                                .addArgument(executionContext.getSqlName())
1✔
1362
                                .log();
1✔
1363
                var startTime = PERFORMANCE_LOG.isInfoEnabled() ? Instant.now(getSqlConfig().getClock()) : null;
1✔
1364

1365
                try {
1366
                        // デフォルト最大リトライ回数を取得し、個別指定(ExecutionContextの値)があれば上書き
1367
                        var maxRetryCount = executionContext.getMaxRetryCount() >= 0
1✔
1368
                                        ? executionContext.getMaxRetryCount()
1✔
1369
                                        : getMaxRetryCount();
1✔
1370

1371
                        // デフォルトリトライ待機時間を取得し、個別指定(ExecutionContextの値)があれば上書き
1372
                        var retryWaitTime = executionContext.getRetryWaitTime() > 0
1✔
1373
                                        ? executionContext.getRetryWaitTime()
1✔
1374
                                        : getRetryWaitTime();
1✔
1375
                        var loopCount = 0;
1✔
1376
                        var dialect = getDialect();
1✔
1377
                        ResultSet rs = null;
1✔
1378
                        try {
1379
                                do {
1380
                                        try {
1381
                                                if (maxRetryCount > 0 && dialect.isRollbackToSavepointBeforeRetry()) {
1✔
1382
                                                        setSavepoint(RETRY_SAVEPOINT_NAME);
1✔
1383
                                                }
1384
                                                rs = stmt.executeQuery();
1✔
1385
                                                // Query実行後イベント発行
1386
                                                if (getSqlConfig().getEventListenerHolder().hasAfterSqlQueryListener()) {
1✔
1387
                                                        var eventObj = new AfterSqlQueryEvent(executionContext, rs, stmt.getConnection(), stmt);
1✔
1388
                                                        for (var listener : getSqlConfig().getEventListenerHolder().getAfterSqlQueryListeners()) {
1✔
1389
                                                                listener.accept(eventObj);
1✔
1390
                                                        }
1✔
1391
                                                        rs = eventObj.getResultSet();
1✔
1392
                                                }
1393
                                                stmt.closeOnCompletion();
1✔
1394
                                                return new InnerResultSet(rs, stmt);
1✔
1395
                                        } catch (SQLException ex) {
1✔
1396
                                                if (maxRetryCount > 0 && dialect.isRollbackToSavepointBeforeRetry()) {
1✔
1397
                                                        rollback(RETRY_SAVEPOINT_NAME);
1✔
1398
                                                }
1399
                                                var errorCode = String.valueOf(ex.getErrorCode());
1✔
1400
                                                var sqlState = ex.getSQLState();
1✔
1401
                                                var pessimisticLockingErrorCodes = dialect.getPessimisticLockingErrorCodes();
1✔
1402
                                                if (maxRetryCount > loopCount
1✔
1403
                                                                && (getSqlRetryCodes().contains(errorCode) || getSqlRetryCodes().contains(sqlState))) {
1✔
1404
                                                        LOG.atDebug()
1✔
1405
                                                                        .setMessage("Caught the error code to be retried.({} times). Retry after {} ms.")
1✔
1406
                                                                        .addArgument(loopCount + 1)
1✔
1407
                                                                        .addArgument(() -> String.format("%,3d", retryWaitTime))
1✔
1408
                                                                        .log();
1✔
1409
                                                        if (retryWaitTime > 0) {
1✔
1410
                                                                try {
1411
                                                                        Thread.sleep(retryWaitTime);
1✔
1412
                                                                } catch (InterruptedException ie) {
×
1413
                                                                        // do nothing
1414
                                                                }
1✔
1415
                                                        }
1416
                                                } else {
1417
                                                        if (pessimisticLockingErrorCodes.contains(errorCode)
1✔
1418
                                                                        || pessimisticLockingErrorCodes.contains(sqlState)) {
1✔
1419
                                                                throw new PessimisticLockException(executionContext, ex);
1✔
1420
                                                        } else {
1421
                                                                throw ex;
1✔
1422
                                                        }
1423
                                                }
1424
                                        } finally {
1425
                                                if (maxRetryCount > 0 && dialect.isRollbackToSavepointBeforeRetry()) {
1✔
1426
                                                        releaseSavepoint(RETRY_SAVEPOINT_NAME);
1✔
1427
                                                }
1428
                                                executionContext.contextAttrs().put(CTX_ATTR_KEY_RETRY_COUNT, loopCount);
1✔
1429
                                        }
1430
                                } while (maxRetryCount > loopCount++);
1✔
1431
                        } catch (SQLException | RuntimeException ex) {
1✔
1432
                                if (rs != null && !rs.isClosed()) {
1✔
1433
                                        rs.close();
1✔
1434
                                }
1435
                                throw ex;
1✔
1436
                        }
×
1437
                        return null;
×
1438
                } catch (SQLException ex) {
1✔
1439
                        handleException(executionContext, ex);
×
1440
                        return null;
×
1441
                } finally {
1442
                        // 後処理
1443
                        PERFORMANCE_LOG.atInfo()
1✔
1444
                                        .setMessage("SQL execution time [{}({})] : [{}]")
1✔
1445
                                        .addArgument(() -> generateSqlName(executionContext))
1✔
1446
                                        .addArgument(executionContext.getSqlKind())
1✔
1447
                                        .addArgument(() -> formatElapsedTime(startTime, Instant.now(getSqlConfig().getClock())))
1✔
1448
                                        .log();
1✔
1449
                }
1450
        }
1451

1452
        /**
1453
         * {@inheritDoc}
1454
         *
1455
         * @see jp.co.future.uroborosql.SqlAgent#query(jp.co.future.uroborosql.context.ExecutionContext,
1456
         *      jp.co.future.uroborosql.converter.ResultSetConverter)
1457
         */
1458
        @Override
1459
        public <T> Stream<T> query(final ExecutionContext executionContext, final ResultSetConverter<T> converter)
1460
                        throws SQLException {
1461
                var rs = query(executionContext);
1✔
1462
                return StreamSupport.stream(new ResultSetSpliterator<>(rs, converter), false).onClose(() -> {
1✔
1463
                        try {
1464
                                if (rs != null && !rs.isClosed()) {
1✔
1465
                                        rs.close();
1✔
1466
                                }
1467
                        } catch (SQLException ex) {
×
1468
                                // do nothing
1469
                        }
1✔
1470
                });
1✔
1471
        }
1472

1473
        /**
1474
         * {@inheritDoc}
1475
         *
1476
         * @see jp.co.future.uroborosql.SqlAgent#query(jp.co.future.uroborosql.context.ExecutionContext,
1477
         *      jp.co.future.uroborosql.utils.CaseFormat)
1478
         */
1479
        @Override
1480
        public List<Map<String, Object>> query(final ExecutionContext executionContext, final CaseFormat caseFormat)
1481
                        throws SQLException {
1482
                try (var stream = query(executionContext, new MapResultSetConverter(getSqlConfig(), caseFormat))) {
1✔
1483
                        return stream.collect(Collectors.toList());
1✔
1484
                }
1485
        }
1486

1487
        /**
1488
         * @see jp.co.future.uroborosql.SqlAgent#update(jp.co.future.uroborosql.context.ExecutionContext)
1489
         */
1490
        @Override
1491
        public int update(final ExecutionContext executionContext) throws SQLException {
1492
                if (SqlKind.NONE.equals(executionContext.getSqlKind())) {
1✔
1493
                        executionContext.setSqlKind(SqlKind.UPDATE);
1✔
1494
                }
1495

1496
                // コンテキスト変換
1497
                transformContext(executionContext);
1✔
1498

1499
                // 更新移譲処理の指定がある場合は移譲処理を実行し結果を返却
1500
                if (executionContext.getUpdateDelegate() != null) {
1✔
1501
                        LOG.atInfo()
1✔
1502
                                        .log("Performs update delegate of update process.");
1✔
1503
                        return executionContext.getUpdateDelegate().apply(executionContext);
1✔
1504
                }
1505

1506
                Instant startTime = null;
1✔
1507

1508
                try (var stmt = getPreparedStatement(executionContext)) {
1✔
1509

1510
                        // INパラメータ設定
1511
                        executionContext.bindParams(stmt);
1✔
1512

1513
                        // REPLで実行するための文字列をREPLログに出力する
1514
                        outputReplLog(executionContext);
1✔
1515

1516
                        LOG.atDebug()
1✔
1517
                                        .setMessage("Execute update sql. sqlName: {}")
1✔
1518
                                        .addArgument(executionContext.getSqlName())
1✔
1519
                                        .log();
1✔
1520

1521
                        if (PERFORMANCE_LOG.isInfoEnabled()) {
1✔
1522
                                startTime = Instant.now(getSqlConfig().getClock());
1✔
1523
                        }
1524

1525
                        // デフォルト最大リトライ回数を取得し、個別指定(ExecutionContextの値)があれば上書き
1526
                        var maxRetryCount = executionContext.getMaxRetryCount() >= 0
1✔
1527
                                        ? executionContext.getMaxRetryCount()
1✔
1528
                                        : getMaxRetryCount();
1✔
1529

1530
                        // デフォルトリトライ待機時間を取得し、個別指定(ExecutionContextの値)があれば上書き
1531
                        var retryWaitTime = executionContext.getRetryWaitTime() > 0
1✔
1532
                                        ? executionContext.getRetryWaitTime()
1✔
1533
                                        : getRetryWaitTime();
1✔
1534
                        var loopCount = 0;
1✔
1535
                        do {
1536
                                try {
1537
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
1538
                                                setSavepoint(RETRY_SAVEPOINT_NAME);
1✔
1539
                                        }
1540
                                        var count = stmt.executeUpdate();
1✔
1541
                                        // Update実行後イベント発行
1542
                                        if (getSqlConfig().getEventListenerHolder().hasAfterSqlUpdateListener()) {
1✔
1543
                                                var eventObj = new AfterSqlUpdateEvent(executionContext, count, stmt.getConnection(), stmt);
1✔
1544
                                                for (var listener : getSqlConfig().getEventListenerHolder().getAfterSqlUpdateListeners()) {
1✔
1545
                                                        listener.accept(eventObj);
1✔
1546
                                                }
1✔
1547
                                                count = eventObj.getCount();
1✔
1548
                                        }
1549
                                        if ((SqlKind.INSERT.equals(executionContext.getSqlKind()) ||
1✔
1550
                                                        SqlKind.ENTITY_INSERT.equals(executionContext.getSqlKind()) ||
1✔
1551
                                                        SqlKind.BULK_INSERT.equals(executionContext.getSqlKind()) ||
1✔
1552
                                                        SqlKind.ENTITY_BULK_INSERT.equals(executionContext.getSqlKind()))
1✔
1553
                                                        && executionContext.hasGeneratedKeyColumns()) {
1✔
1554
                                                try (var rs = stmt.getGeneratedKeys()) {
1✔
1555
                                                        var generatedKeyValues = new ArrayList<>();
1✔
1556
                                                        while (rs.next()) {
1✔
1557
                                                                for (var i = 1; i <= executionContext.getGeneratedKeyColumns().length; i++) {
1✔
1558
                                                                        generatedKeyValues.add(rs.getObject(i));
1✔
1559
                                                                }
1560
                                                        }
1561
                                                        executionContext.setGeneratedKeyValues(
1✔
1562
                                                                        generatedKeyValues.toArray(new Object[generatedKeyValues.size()]));
1✔
1563
                                                }
1564
                                        }
1565
                                        return count;
1✔
1566
                                } catch (SQLException ex) {
1✔
1567
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
1568
                                                rollback(RETRY_SAVEPOINT_NAME);
1✔
1569
                                        }
1570
                                        if (maxRetryCount > loopCount) {
1✔
1571
                                                var errorCode = String.valueOf(ex.getErrorCode());
1✔
1572
                                                var sqlState = ex.getSQLState();
1✔
1573
                                                if (getSqlRetryCodes().contains(errorCode) || getSqlRetryCodes().contains(sqlState)) {
1✔
1574
                                                        LOG.atDebug()
1✔
1575
                                                                        .setMessage("Caught the error code to be retried.({} times). Retry after {} ms.")
1✔
1576
                                                                        .addArgument(loopCount + 1)
1✔
1577
                                                                        .addArgument(() -> String.format("%,3d", retryWaitTime))
1✔
1578
                                                                        .log();
1✔
1579
                                                        if (retryWaitTime > 0) {
1✔
1580
                                                                try {
1581
                                                                        Thread.sleep(retryWaitTime);
1✔
1582
                                                                } catch (InterruptedException ie) {
×
1583
                                                                        // do nothing
1584
                                                                }
1✔
1585
                                                        }
1586
                                                } else {
1587
                                                        throw ex;
1✔
1588
                                                }
1589
                                        } else {
1✔
1590
                                                throw ex;
1✔
1591
                                        }
1592
                                } finally {
1593
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
1594
                                                releaseSavepoint(RETRY_SAVEPOINT_NAME);
1✔
1595
                                        }
1596
                                        executionContext.contextAttrs().put(CTX_ATTR_KEY_RETRY_COUNT, loopCount);
1✔
1597
                                }
1598
                        } while (maxRetryCount > loopCount++);
1✔
1599
                        return 0;
×
1600
                } catch (SQLException ex) {
1✔
1601
                        handleException(executionContext, ex);
×
1602
                        return 0;
×
1603
                } finally {
1604
                        // 後処理
1605
                        var curStartTime = startTime;
1✔
1606
                        PERFORMANCE_LOG.atInfo()
1✔
1607
                                        .setMessage("SQL execution time [{}({})] : [{}]")
1✔
1608
                                        .addArgument(() -> generateSqlName(executionContext))
1✔
1609
                                        .addArgument(executionContext.getSqlKind())
1✔
1610
                                        .addArgument(() -> formatElapsedTime(curStartTime, Instant.now(getSqlConfig().getClock())))
1✔
1611
                                        .log();
1✔
1612
                }
1613
        }
1614

1615
        /**
1616
         * @see jp.co.future.uroborosql.SqlAgent#batch(jp.co.future.uroborosql.context.ExecutionContext)
1617
         */
1618
        @Override
1619
        public int[] batch(final ExecutionContext executionContext) throws SQLException {
1620
                // バッチ処理の場合大量のログが出力されるため、パラメータログの出力を抑止する
1621
                suppressParameterLogging();
1✔
1622

1623
                if (SqlKind.NONE.equals(executionContext.getSqlKind())) {
1✔
1624
                        executionContext.setSqlKind(SqlKind.BATCH_INSERT);
1✔
1625
                }
1626

1627
                // コンテキスト変換
1628
                transformContext(executionContext);
1✔
1629

1630
                // 更新移譲処理の指定がある場合は移譲処理を実行し結果を返却
1631
                if (executionContext.getUpdateDelegate() != null) {
1✔
1632
                        releaseParameterLogging();
1✔
1633
                        LOG.atInfo()
1✔
1634
                                        .log("Performs update delegate of batch process.");
1✔
1635
                        return new int[] { executionContext.getUpdateDelegate().apply(executionContext) };
1✔
1636
                }
1637

1638
                Instant startTime = null;
1✔
1639

1640
                try (var stmt = getPreparedStatement(executionContext)) {
1✔
1641

1642
                        // INパラメータ設定
1643
                        executionContext.bindBatchParams(stmt);
1✔
1644

1645
                        LOG.atDebug()
1✔
1646
                                        .setMessage("Execute batch sql. sqlName: {}")
1✔
1647
                                        .addArgument(executionContext.getSqlName())
1✔
1648
                                        .log();
1✔
1649
                        if (PERFORMANCE_LOG.isInfoEnabled()) {
1✔
1650
                                startTime = Instant.now(getSqlConfig().getClock());
1✔
1651
                        }
1652

1653
                        // デフォルト最大リトライ回数を取得し、個別指定(ExecutionContextの値)があれば上書き
1654
                        var maxRetryCount = executionContext.getMaxRetryCount() >= 0
1✔
1655
                                        ? executionContext.getMaxRetryCount()
1✔
1656
                                        : getMaxRetryCount();
1✔
1657

1658
                        // デフォルトリトライ待機時間を取得し、個別指定(ExecutionContextの値)があれば上書き
1659
                        var retryWaitTime = executionContext.getRetryWaitTime() > 0
1✔
NEW
1660
                                        ? executionContext.getRetryWaitTime()
×
1661
                                        : getRetryWaitTime();
1✔
1662
                        var loopCount = 0;
1✔
1663
                        do {
1664
                                try {
1665
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
1666
                                                setSavepoint(RETRY_SAVEPOINT_NAME);
×
1667
                                        }
1668
                                        var counts = stmt.executeBatch();
1✔
1669
                                        // Batch実行後イベント発行
1670
                                        if (getSqlConfig().getEventListenerHolder().hasAfterSqlBatchListener()) {
1✔
1671
                                                var eventObj = new AfterSqlBatchEvent(executionContext, counts, stmt.getConnection(), stmt);
1✔
1672
                                                for (var listener : getSqlConfig().getEventListenerHolder().getAfterSqlBatchListeners()) {
1✔
1673
                                                        listener.accept(eventObj);
1✔
1674
                                                }
1✔
1675
                                                counts = eventObj.getCounts();
1✔
1676
                                        }
1677
                                        if ((SqlKind.BATCH_INSERT.equals(executionContext.getSqlKind()) ||
1✔
1678
                                                        SqlKind.ENTITY_BATCH_INSERT.equals(executionContext.getSqlKind()))
1✔
1679
                                                        && executionContext.hasGeneratedKeyColumns()) {
1✔
1680
                                                try (var rs = stmt.getGeneratedKeys()) {
1✔
1681
                                                        var generatedKeyValues = new ArrayList<>();
1✔
1682
                                                        while (rs.next()) {
1✔
1683
                                                                for (var i = 1; i <= executionContext.getGeneratedKeyColumns().length; i++) {
1✔
1684
                                                                        generatedKeyValues.add(rs.getObject(i));
1✔
1685
                                                                }
1686
                                                        }
1687
                                                        executionContext.setGeneratedKeyValues(
1✔
1688
                                                                        generatedKeyValues.toArray(new Object[generatedKeyValues.size()]));
1✔
1689
                                                }
1690
                                        }
1691
                                        return counts;
1✔
1692
                                } catch (SQLException ex) {
1✔
1693
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
1694
                                                rollback(RETRY_SAVEPOINT_NAME);
×
1695
                                        }
1696
                                        if (maxRetryCount > loopCount) {
1✔
1697
                                                var errorCode = String.valueOf(ex.getErrorCode());
1✔
1698
                                                var sqlState = ex.getSQLState();
1✔
1699
                                                if (getSqlRetryCodes().contains(errorCode) || getSqlRetryCodes().contains(sqlState)) {
1✔
1700
                                                        LOG.atDebug()
1✔
1701
                                                                        .setMessage("Caught the error code to be retried.({} times). Retry after {} ms.")
1✔
1702
                                                                        .addArgument(loopCount + 1)
1✔
1703
                                                                        .addArgument(() -> String.format("%,3d", retryWaitTime))
1✔
1704
                                                                        .log();
1✔
1705
                                                        if (retryWaitTime > 0) {
1✔
1706
                                                                try {
1707
                                                                        Thread.sleep(retryWaitTime);
1✔
1708
                                                                } catch (InterruptedException ie) {
×
1709
                                                                        // do nothing
1710
                                                                }
1✔
1711
                                                        }
1712
                                                } else {
1713
                                                        throw ex;
×
1714
                                                }
1715
                                        } else {
1✔
1716
                                                throw ex;
1✔
1717
                                        }
1718
                                } finally {
1719
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
1720
                                                releaseSavepoint(RETRY_SAVEPOINT_NAME);
×
1721
                                        }
1722
                                        executionContext.clearBatch();
1✔
1723
                                        executionContext.contextAttrs().put(CTX_ATTR_KEY_RETRY_COUNT, loopCount);
1✔
1724
                                }
1725
                        } while (maxRetryCount > loopCount++);
1✔
1726
                        return null;
×
1727
                } catch (SQLException ex) {
1✔
1728
                        handleException(executionContext, ex);
×
1729
                        return null;
×
1730
                } finally {
1731
                        // 後処理
1732
                        var curStartTime = startTime;
1✔
1733
                        PERFORMANCE_LOG.atInfo()
1✔
1734
                                        .setMessage("SQL execution time [{}({})] : [{}]")
1✔
1735
                                        .addArgument(() -> generateSqlName(executionContext))
1✔
1736
                                        .addArgument(executionContext.getSqlKind())
1✔
1737
                                        .addArgument(() -> formatElapsedTime(curStartTime, Instant.now(getSqlConfig().getClock())))
1✔
1738
                                        .log();
1✔
1739
                        releaseParameterLogging();
1✔
1740
                }
1741
        }
1742

1743
        /**
1744
         * {@inheritDoc}
1745
         *
1746
         * @see jp.co.future.uroborosql.SqlAgent#procedure(jp.co.future.uroborosql.context.ExecutionContext)
1747
         */
1748
        @Override
1749
        public Map<String, Object> procedure(final ExecutionContext executionContext) throws SQLException {
1750
                // procedureやfunctionの場合、SQL文法エラーになるためバインドパラメータコメントを出力しない
1751
                executionContext.contextAttrs().put(CTX_ATTR_KEY_OUTPUT_BIND_COMMENT, false);
1✔
1752

1753
                if (SqlKind.NONE.equals(executionContext.getSqlKind())) {
1✔
1754
                        executionContext.setSqlKind(SqlKind.PROCEDURE);
1✔
1755
                }
1756

1757
                // コンテキスト変換
1758
                transformContext(executionContext);
1✔
1759

1760
                Instant startTime = null;
1✔
1761

1762
                try (var callableStatement = getCallableStatement(executionContext)) {
1✔
1763

1764
                        // パラメータ設定
1765
                        executionContext.bindParams(callableStatement);
1✔
1766

1767
                        LOG.atDebug()
1✔
1768
                                        .setMessage("Execute stored procedure. sqlName: {}")
1✔
1769
                                        .addArgument(executionContext.getSqlName())
1✔
1770
                                        .log();
1✔
1771
                        if (PERFORMANCE_LOG.isInfoEnabled()) {
1✔
1772
                                startTime = Instant.now(getSqlConfig().getClock());
1✔
1773
                        }
1774

1775
                        // デフォルト最大リトライ回数を取得し、個別指定(ExecutionContextの値)があれば上書き
1776
                        var maxRetryCount = executionContext.getMaxRetryCount() >= 0
1✔
1777
                                        ? executionContext.getMaxRetryCount()
1✔
1778
                                        : getMaxRetryCount();
1✔
1779

1780
                        // デフォルトリトライ待機時間を取得し、個別指定(ExecutionContextの値)があれば上書き
1781
                        var retryWaitTime = executionContext.getRetryWaitTime() > 0
1✔
1782
                                        ? executionContext.getRetryWaitTime()
1✔
1783
                                        : getRetryWaitTime();
1✔
1784

1785
                        var loopCount = 0;
1✔
1786
                        do {
1787
                                try {
1788
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
1789
                                                setSavepoint(RETRY_SAVEPOINT_NAME);
1✔
1790
                                        }
1791
                                        var result = callableStatement.execute();
1✔
1792
                                        // Procedure実行後イベント発行
1793
                                        if (getSqlConfig().getEventListenerHolder().hasAfterProcedureListener()) {
1✔
1794
                                                var eventObj = new AfterProcedureEvent(executionContext, result,
1✔
1795
                                                                callableStatement.getConnection(),
1✔
1796
                                                                callableStatement);
1797
                                                for (var listener : getSqlConfig().getEventListenerHolder().getAfterProcedureListeners()) {
1✔
1798
                                                        listener.accept(eventObj);
1✔
1799
                                                }
1✔
1800
                                                result = eventObj.isResult();
1✔
1801
                                        }
1802
                                        break;
1803
                                } catch (SQLException ex) {
1✔
1804
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
1805
                                                rollback(RETRY_SAVEPOINT_NAME);
1✔
1806
                                        }
1807
                                        if (maxRetryCount > loopCount) {
1✔
1808
                                                var errorCode = String.valueOf(ex.getErrorCode());
1✔
1809
                                                var sqlState = ex.getSQLState();
1✔
1810
                                                if (getSqlRetryCodes().contains(errorCode) || getSqlRetryCodes().contains(sqlState)) {
1✔
1811
                                                        LOG.atDebug()
1✔
1812
                                                                        .setMessage("Caught the error code to be retried.({} times). Retry after {} ms.")
1✔
1813
                                                                        .addArgument(loopCount + 1)
1✔
1814
                                                                        .addArgument(() -> String.format("%,3d", retryWaitTime))
1✔
1815
                                                                        .log();
1✔
1816
                                                        if (retryWaitTime > 0) {
1✔
1817
                                                                try {
1818
                                                                        Thread.sleep(retryWaitTime);
1✔
1819
                                                                } catch (InterruptedException ie) {
×
1820
                                                                        // do nothing
1821
                                                                }
1✔
1822
                                                        }
1823
                                                } else {
1824
                                                        throw ex;
1✔
1825
                                                }
1826
                                        } else {
1✔
1827
                                                throw ex;
1✔
1828
                                        }
1829
                                } finally {
1830
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
1831
                                                releaseSavepoint(RETRY_SAVEPOINT_NAME);
1✔
1832
                                        }
1833
                                        executionContext.contextAttrs().put(CTX_ATTR_KEY_RETRY_COUNT, loopCount);
1✔
1834
                                }
1835
                        } while (maxRetryCount > loopCount++);
1✔
1836
                        // 結果取得
1837
                        return executionContext.getOutParams(callableStatement);
1✔
1838
                } catch (SQLException ex) {
1✔
1839
                        handleException(executionContext, ex);
×
1840
                } finally {
1841
                        // 後処理
1842
                        var curStartTime = startTime;
1✔
1843
                        PERFORMANCE_LOG.atInfo()
1✔
1844
                                        .setMessage("Stored procedure execution time [{}({})] : [{}]")
1✔
1845
                                        .addArgument(() -> generateSqlName(executionContext))
1✔
1846
                                        .addArgument(executionContext.getSqlKind())
1✔
1847
                                        .addArgument(() -> formatElapsedTime(curStartTime, Instant.now(getSqlConfig().getClock())))
1✔
1848
                                        .log();
1✔
1849
                }
1850
                return null;
×
1851
        }
1852

1853
        /**
1854
         * ExecutionContextの設定内容を元にSQLを構築する
1855
         *
1856
         * @param executionContext ExecutionContext
1857
         */
1858
        private void transformContext(final ExecutionContext executionContext) {
1859
                var originalSql = executionContext.getSql();
1✔
1860
                var sqlName = executionContext.getSqlName();
1✔
1861
                if (ObjectUtils.isEmpty(originalSql) && getSqlResourceManager() != null) {
1✔
1862
                        originalSql = getSqlResourceManager().getSql(sqlName);
1✔
1863
                        if (ObjectUtils.isEmpty(originalSql)) {
1✔
1864
                                throw new UroborosqlRuntimeException("sql file:[" + sqlName + "] is not found.");
×
1865
                        }
1866
                }
1867

1868
                // SQL-IDの付与
1869
                if (originalSql.contains(keySqlId)) {
1✔
1870
                        var sqlId = executionContext.getSqlId();
1✔
1871
                        if (ObjectUtils.isEmpty(sqlId)) {
1✔
1872
                                sqlId = sqlName;
1✔
1873
                        }
1874
                        if (ObjectUtils.isEmpty(sqlId)) {
1✔
1875
                                sqlId = String.valueOf(originalSql.hashCode());
×
1876
                        }
1877

1878
                        originalSql = originalSql.replace(keySqlId, sqlId);
1✔
1879
                }
1880

1881
                if (SQL_LOG.isInfoEnabled() && sqlName != null) {
1✔
1882
                        if (executionContext.getSqlKind().isEntityType()) {
1✔
1883
                                SQL_LOG.atInfo()
1✔
1884
                                                .setMessage("EntityClass : {}")
1✔
1885
                                                .addArgument(sqlName)
1✔
1886
                                                .log();
1✔
1887
                        } else if (getSqlResourceManager().existSql(sqlName)) {
1✔
1888
                                SQL_LOG.atInfo()
1✔
1889
                                                .setMessage("SQLPath : {}")
1✔
1890
                                                .addArgument(() -> getSqlResourceManager().getSqlPath(sqlName))
1✔
1891
                                                .log();
1✔
1892
                        }
1893
                }
1894

1895
                // SQL変換前イベント発行
1896
                if (getSqlConfig().getEventListenerHolder().hasBeforeTransformSqlListener()) {
1✔
1897
                        var eventObj = new BeforeTransformSqlEvent(executionContext, originalSql);
1✔
1898
                        getSqlConfig().getEventListenerHolder().getBeforeTransformSqlListeners()
1✔
1899
                                        .forEach(listener -> listener.accept(eventObj));
1✔
1900
                        originalSql = eventObj.getSql();
1✔
1901
                }
1902
                executionContext.setSql(originalSql);
1✔
1903

1904
                // Dialectに合わせたエスケープキャラクタの設定
1905
                executionContext.param(Dialect.PARAM_KEY_ESCAPE_CHAR, getDialect().getEscapeChar());
1✔
1906

1907
                // SQLパース前イベントの呼出
1908
                if (executionContext.batchCount() == 0
1✔
1909
                                && getSqlConfig().getEventListenerHolder().hasBeforeParseSqlListener()) {
1✔
1910
                        var eventObj = new BeforeParseSqlEvent(executionContext);
1✔
1911
                        getSqlConfig().getEventListenerHolder().getBeforeParseSqlListeners()
1✔
1912
                                        .forEach(listener -> listener.accept(eventObj));
1✔
1913
                }
1914

1915
                SQL_LOG.atDebug()
1✔
1916
                                .setMessage("Template SQL[{}{}{}]")
1✔
1917
                                .addArgument(System.lineSeparator())
1✔
1918
                                .addArgument(originalSql)
1✔
1919
                                .addArgument(System.lineSeparator())
1✔
1920
                                .log();
1✔
1921

1922
                if (ObjectUtils.isEmpty(executionContext.getExecutableSql())) {
1✔
1923
                        // SQLパーサーによるパース処理
1924
                        var outputBindComment = (boolean) executionContext.contextAttrs().getOrDefault(
1✔
1925
                                        CTX_ATTR_KEY_OUTPUT_BIND_COMMENT, true);
1✔
1926
                        var sqlParser = new SqlParserImpl(originalSql, sqlConfig.getExpressionParser(),
1✔
1927
                                        getDialect().isRemoveTerminator(), outputBindComment);
1✔
1928
                        var contextTransformer = sqlParser.parse();
1✔
1929
                        contextTransformer.transform(executionContext);
1✔
1930

1931
                        if (COVERAGE_HANDLER_REF.get() != null) {
1✔
1932
                                // SQLカバレッジ用のログを出力する
1933
                                var coverageData = new CoverageData(sqlName, originalSql,
1✔
1934
                                                contextTransformer.getPassedRoute());
1✔
1935
                                COVERAGE_LOG.atDebug()
1✔
1936
                                                .setMessage("coverage data: {}")
1✔
1937
                                                .addArgument(coverageData)
1✔
1938
                                                .log();
1✔
1939

1940
                                COVERAGE_HANDLER_REF.get().accept(coverageData);
1✔
1941
                        }
1942
                }
1943

1944
                SQL_LOG.atInfo()
1✔
1945
                                .setMessage("Executed SQL[{}{}{}]")
1✔
1946
                                .addArgument(System.lineSeparator())
1✔
1947
                                .addArgument(executionContext.getExecutableSql())
1✔
1948
                                .addArgument(System.lineSeparator())
1✔
1949
                                .log();
1✔
1950
        }
1✔
1951

1952
        /** 時間計測用のログに出力するSQL名を生成する.
1953
         *
1954
         * @param executionContext ExecutionContext
1955
         * @return SQL名. SQL名が取得できない場合はSQL_ID、または空文字を返却する
1956
         */
1957
        private String generateSqlName(final ExecutionContext executionContext) {
1958
                if (executionContext.getSqlName() != null) {
1✔
1959
                        return executionContext.getSqlName();
1✔
1960
                } else {
1961
                        return Objects.toString(executionContext.getSqlId(), "");
1✔
1962
                }
1963
        }
1964

1965
        /**
1966
         * 例外発生時ハンドラー
1967
         *
1968
         * @param executionContext ExecutionContext
1969
         * @param ex SQL例外
1970
         * @throws SQLException SQL例外
1971
         */
1972
        private void handleException(final ExecutionContext executionContext, final SQLException ex) throws SQLException {
1973
                var cause = ex;
1✔
1974

1975
                while (cause.getNextException() != null) {
1✔
1976
                        cause = cause.getNextException();
1✔
1977
                }
1978

1979
                if (outputExceptionLog) {
1✔
1980
                        LOG.atError()
1✔
1981
                                        .setMessage(() -> {
1✔
1982
                                                var builder = new StringBuilder();
1✔
1983
                                                builder.append(System.lineSeparator()).append("Exception occurred in SQL execution.")
1✔
1984
                                                                .append(System.lineSeparator());
1✔
1985
                                                builder.append("Executed SQL[").append(executionContext.getExecutableSql()).append("]")
1✔
1986
                                                                .append(System.lineSeparator());
1✔
1987
                                                if (executionContext instanceof ExecutionContextImpl) {
1✔
1988
                                                        var bindParameters = ((ExecutionContextImpl) executionContext).getBindParameters();
1✔
1989
                                                        for (var i = 0; i < bindParameters.length; i++) {
1✔
1990
                                                                var parameter = bindParameters[i];
1✔
1991
                                                                builder.append("Bind Parameter.[INDEX[").append(i + 1).append("], ")
1✔
1992
                                                                                .append(parameter.toString())
1✔
1993
                                                                                .append("]").append(System.lineSeparator());
1✔
1994
                                                        }
1995
                                                }
1996
                                                return builder.toString();
1✔
1997
                                        })
1998
                                        .setCause(cause)
1✔
1999
                                        .log();
1✔
2000
                }
2001

2002
                throw cause;
1✔
2003
        }
2004

2005
        /**
2006
         * REPLでSQLを実行するためのコマンドをログとしてREPL_LOGに出力する.
2007
         *
2008
         * @param executionContext executionContext
2009
         */
2010
        private void outputReplLog(final ExecutionContext executionContext) {
2011
                if (!(REPL_LOG.isInfoEnabled() &&
1✔
2012
                                executionContext.getSqlName() != null &&
1✔
2013
                                (SqlKind.SELECT.equals(executionContext.getSqlKind()) ||
1✔
2014
                                                SqlKind.UPDATE.equals(executionContext.getSqlKind())))) {
1✔
2015
                        // REPLログ出力対象でない場合は何もしない
2016
                        return;
1✔
2017
                }
2018

2019
                var builder = new StringBuilder();
1✔
2020

2021
                if (SqlKind.SELECT.equals(executionContext.getSqlKind())) {
1✔
2022
                        builder.append("query ");
1✔
2023
                } else {
2024
                        builder.append("update ");
1✔
2025
                }
2026

2027
                builder.append(executionContext.getSqlName());
1✔
2028

2029
                var params = new ArrayList<Parameter>();
1✔
2030
                for (var bindName : executionContext.getBindNames()) {
1✔
2031
                        params.add(executionContext.getParam(bindName));
1✔
2032
                }
1✔
2033
                if (!params.isEmpty()) {
1✔
2034
                        builder.append(" ");
1✔
2035
                        builder.append(SqlParamUtils.formatPrams(params));
1✔
2036
                }
2037
                REPL_LOG.atInfo()
1✔
2038
                                .setMessage("REPL command: {}")
1✔
2039
                                .addArgument(builder.toString())
1✔
2040
                                .log();
1✔
2041
        }
1✔
2042

2043
        /**
2044
         *
2045
         * {@inheritDoc}
2046
         *
2047
         * @see jp.co.future.uroborosql.SqlAgent#find(java.lang.Class, java.lang.Object[])
2048
         */
2049
        @SuppressWarnings("unchecked")
2050
        @Override
2051
        public <E> Optional<E> find(final Class<? extends E> entityType, final Object... keys) {
2052
                @SuppressWarnings("rawtypes")
2053
                EntityHandler handler = this.getEntityHandler();
1✔
2054
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2055
                        throw new IllegalArgumentException("Entity type not supported");
×
2056
                }
2057

2058
                try {
2059
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2060
                        var keyNames = metadata.getColumns().stream()
1✔
2061
                                        .filter(TableMetadata.Column::isKey)
1✔
2062
                                        .sorted(Comparator.comparingInt(TableMetadata.Column::getKeySeq))
1✔
2063
                                        .map(TableMetadata.Column::getColumnName)
1✔
2064
                                        .map(CaseFormat.CAMEL_CASE::convert)
1✔
2065
                                        .toArray(String[]::new);
1✔
2066

2067
                        if (keyNames.length != keys.length) {
1✔
2068
                                throw new IllegalArgumentException("Number of keys does not match");
×
2069
                        }
2070
                        var params = new HashMap<String, Object>();
1✔
2071
                        for (var i = 0; i < keys.length; i++) {
1✔
2072
                                params.put(keyNames[i], keys[i]);
1✔
2073
                        }
2074

2075
                        var context = handler.createSelectContext(this, metadata, entityType, true);
1✔
2076
                        context.setSqlKind(SqlKind.ENTITY_SELECT).paramMap(params);
1✔
2077

2078
                        // EntityQuery実行前イベント発行
2079
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
2080
                        if (eventListenerHolder.hasBeforeEntityQueryListener()) {
1✔
2081
                                var eventObj = new BeforeEntityQueryEvent(context, null, entityType);
×
2082
                                for (var listener : eventListenerHolder.getBeforeEntityQueryListeners()) {
×
2083
                                        listener.accept(eventObj);
×
2084
                                }
×
2085
                        }
2086

2087
                        var results = handler.doSelect(this, context, entityType);
1✔
2088

2089
                        // EntityQuery実行後イベント発行
2090
                        if (eventListenerHolder.hasAfterEntityQueryListener()) {
1✔
2091
                                var eventObj = new AfterEntityQueryEvent(context(), null, entityType, results);
×
2092
                                for (var listener : eventListenerHolder.getAfterEntityQueryListeners()) {
×
2093
                                        listener.accept(eventObj);
×
2094
                                }
×
2095
                                results = eventObj.getResults();
×
2096
                        }
2097

2098
                        try (var stream = results) {
1✔
2099
                                return stream.findFirst();
1✔
2100
                        }
2101
                } catch (SQLException ex) {
×
2102
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_SELECT, ex);
×
2103
                }
2104
        }
2105

2106
        /**
2107
         * {@inheritDoc}
2108
         *
2109
         * @see jp.co.future.uroborosql.SqlAgent#insert(java.lang.Object)
2110
         */
2111
        @SuppressWarnings("unchecked")
2112
        @Override
2113
        public <E> int insert(final E entity) {
2114
                if (entity instanceof Stream) {
1✔
2115
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
2116
                }
2117

2118
                @SuppressWarnings("rawtypes")
2119
                EntityHandler handler = this.getEntityHandler();
1✔
2120
                if (!handler.getEntityType().isInstance(entity)) {
1✔
2121
                        throw new IllegalArgumentException("Entity type not supported");
×
2122
                }
2123

2124
                try {
2125
                        var entityType = entity.getClass();
1✔
2126
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2127
                        var context = handler.createInsertContext(this, metadata, entityType);
1✔
2128
                        context.setSqlKind(SqlKind.ENTITY_INSERT);
1✔
2129

2130
                        // 自動採番カラムの取得とcontextへの設定を行う
2131
                        var mappingColumns = MappingUtils.getMappingColumns(metadata.getSchema(), entityType);
1✔
2132
                        var autoGeneratedColumns = getAutoGeneratedColumns(context, mappingColumns, metadata, entity);
1✔
2133

2134
                        // EntityInsert実行前イベント発行
2135
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
2136
                        if (eventListenerHolder.hasBeforeEntityInsertListener()) {
1✔
2137
                                var eventObj = new BeforeEntityInsertEvent(context, entity, entityType);
1✔
2138
                                for (var listener : eventListenerHolder.getBeforeEntityInsertListeners()) {
1✔
2139
                                        listener.accept(eventObj);
1✔
2140
                                }
1✔
2141
                        }
2142
                        handler.setInsertParams(context, entity);
1✔
2143

2144
                        var count = handler.doInsert(this, context, entity);
1✔
2145

2146
                        // EntityInsert実行後イベント発行
2147
                        if (eventListenerHolder.hasAfterEntityInsertListener()) {
1✔
2148
                                var eventObj = new AfterEntityInsertEvent(context, entity, entityType, count);
1✔
2149
                                for (var listener : eventListenerHolder.getAfterEntityInsertListeners()) {
1✔
2150
                                        listener.accept(eventObj);
1✔
2151
                                }
1✔
2152
                                count = eventObj.getCount();
1✔
2153
                        }
2154

2155
                        if (!autoGeneratedColumns.isEmpty()) {
1✔
2156
                                var ids = context.getGeneratedKeyValues();
1✔
2157
                                var idx = 0;
1✔
2158
                                for (var col : autoGeneratedColumns) {
1✔
2159
                                        setEntityIdValue(entity, ids[idx++], col);
1✔
2160
                                }
1✔
2161
                        }
2162
                        return count;
1✔
2163
                } catch (SQLException ex) {
1✔
2164
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_INSERT, ex);
1✔
2165
                }
2166
        }
2167

2168
        /**
2169
         * {@inheritDoc}
2170
         *
2171
         * @see jp.co.future.uroborosql.SqlAgent#insertAndReturn(java.lang.Object)
2172
         */
2173
        @Override
2174
        public <E> E insertAndReturn(final E entity) {
2175
                insert(entity);
1✔
2176
                return entity;
1✔
2177
        }
2178

2179
        /**
2180
         * {@inheritDoc}
2181
         *
2182
         * @see jp.co.future.uroborosql.SqlAgent#update(java.lang.Object)
2183
         */
2184
        @SuppressWarnings("unchecked")
2185
        @Override
2186
        public <E> int update(final E entity) {
2187
                if (entity instanceof Stream) {
1✔
2188
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
2189
                }
2190

2191
                @SuppressWarnings("rawtypes")
2192
                EntityHandler handler = this.getEntityHandler();
1✔
2193
                if (!handler.getEntityType().isInstance(entity)) {
1✔
2194
                        throw new IllegalArgumentException("Entity type not supported");
×
2195
                }
2196

2197
                try {
2198
                        var entityType = entity.getClass();
1✔
2199
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2200
                        var context = handler.createUpdateContext(this, metadata, entityType, true);
1✔
2201
                        context.setSqlKind(SqlKind.ENTITY_UPDATE);
1✔
2202

2203
                        // EntityUpdate実行前イベント発行
2204
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
2205
                        if (eventListenerHolder.hasBeforeEntityUpdateListener()) {
1✔
2206
                                var eventObj = new BeforeEntityUpdateEvent(context, entity, entityType);
1✔
2207
                                for (var listener : eventListenerHolder.getBeforeEntityUpdateListeners()) {
1✔
2208
                                        listener.accept(eventObj);
1✔
2209
                                }
1✔
2210
                        }
2211
                        handler.setUpdateParams(context, entity);
1✔
2212

2213
                        var count = handler.doUpdate(this, context, entity);
1✔
2214

2215
                        // EntityUpdate実行後イベント発行
2216
                        if (eventListenerHolder.hasAfterEntityUpdateListener()) {
1✔
2217
                                var eventObj = new AfterEntityUpdateEvent(context, entity, entityType, count);
1✔
2218
                                for (var listener : eventListenerHolder.getAfterEntityUpdateListeners()) {
1✔
2219
                                        listener.accept(eventObj);
1✔
2220
                                }
1✔
2221
                                count = eventObj.getCount();
1✔
2222
                        }
2223

2224
                        var updateCount = count;
1✔
2225
                        MappingUtils.getVersionMappingColumn(metadata.getSchema(), entityType).ifPresent(versionColumn -> {
1✔
2226
                                if (updateCount == 0) {
1✔
2227
                                        throw new OptimisticLockException(context);
1✔
2228
                                } else {
2229
                                        var columnMap = MappingUtils.getMappingColumnMap(metadata.getSchema(), entityType, SqlKind.NONE);
1✔
2230
                                        var keys = metadata.getColumns().stream()
1✔
2231
                                                        .filter(TableMetadata.Column::isKey)
1✔
2232
                                                        .sorted(Comparator.comparingInt(TableMetadata.Column::getKeySeq))
1✔
2233
                                                        .map(c -> columnMap.get(c.getCamelColumnName()).getValue(entity))
1✔
2234
                                                        .toArray();
1✔
2235

2236
                                        find(entityType, keys).ifPresent(e -> versionColumn.setValue(entity, versionColumn.getValue(e)));
1✔
2237
                                }
2238
                        });
1✔
2239
                        return updateCount;
1✔
2240
                } catch (SQLException ex) {
×
2241
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_UPDATE, ex);
×
2242
                }
2243
        }
2244

2245
        /**
2246
         * {@inheritDoc}
2247
         *
2248
         * @see jp.co.future.uroborosql.SqlAgent#updateAndReturn(java.lang.Object)
2249
         */
2250
        @Override
2251
        public <E> E updateAndReturn(final E entity) {
2252
                update(entity);
1✔
2253
                return entity;
1✔
2254
        }
2255

2256
        /**
2257
         * {@inheritDoc}
2258
         *
2259
         * @see jp.co.future.uroborosql.SqlAgent#delete(java.lang.Class)
2260
         */
2261
        @Override
2262
        public <E> SqlEntityUpdate<E> update(final Class<? extends E> entityType) {
2263
                var handler = this.getEntityHandler();
1✔
2264
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2265
                        throw new IllegalArgumentException("Entity type not supported");
×
2266
                }
2267

2268
                try {
2269
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2270

2271
                        var context = handler.createUpdateContext(this, metadata, entityType, false);
1✔
2272
                        context.setSqlKind(SqlKind.ENTITY_DELETE);
1✔
2273

2274
                        return new SqlEntityUpdateImpl<>(this, handler, metadata, context, entityType);
1✔
2275
                } catch (SQLException ex) {
×
2276
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_DELETE, ex);
×
2277
                }
2278
        }
2279

2280
        /**
2281
         * {@inheritDoc}
2282
         *
2283
         * @see jp.co.future.uroborosql.SqlAgent#merge(java.lang.Object)
2284
         */
2285
        @Override
2286
        public <E> int merge(final E entity) {
2287
                mergeAndReturn(entity);
1✔
2288
                return 1;
1✔
2289
        }
2290

2291
        /**
2292
         * {@inheritDoc}
2293
         *
2294
         * @see jp.co.future.uroborosql.SqlAgent#mergeAndReturn(java.lang.Object)
2295
         */
2296
        @Override
2297
        public <E> E mergeAndReturn(final E entity) {
2298
                return doMergeAndReturn(entity, false);
1✔
2299
        }
2300

2301
        /**
2302
         * {@inheritDoc}
2303
         *
2304
         * @see jp.co.future.uroborosql.SqlAgent#mergeWithLocking(java.lang.Object)
2305
         */
2306
        @Override
2307
        public <E> int mergeWithLocking(final E entity) {
2308
                mergeWithLockingAndReturn(entity);
1✔
2309
                return 1;
1✔
2310
        }
2311

2312
        /**
2313
         * {@inheritDoc}
2314
         *
2315
         * @see jp.co.future.uroborosql.SqlAgent#mergeWithLockingAndReturn(java.lang.Object)
2316
         */
2317
        @Override
2318
        public <E> E mergeWithLockingAndReturn(final E entity) {
2319
                return doMergeAndReturn(entity, true);
1✔
2320
        }
2321

2322
        /**
2323
         * エンティティのMERGE(INSERTまたはUPDATE)を実行し、MERGEしたエンティティを返却する.<br>
2324
         * locking引数が<code>true</code>の場合、マージの最初に発行するEntityの検索で悲観ロック(forUpdateNoWait)を行う
2325
         *
2326
         * @param <E> エンティティ型
2327
         * @param entity エンティティ
2328
         * @param locking エンティティの悲観ロックを行うかどうか
2329
         * @return MERGEしたエンティティ
2330
         *
2331
         */
2332
        @SuppressWarnings("unchecked")
2333
        private <E> E doMergeAndReturn(final E entity, final boolean locking) {
2334
                if (entity instanceof Stream) {
1✔
2335
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
2336
                }
2337

2338
                var handler = this.getEntityHandler();
1✔
2339
                if (!handler.getEntityType().isInstance(entity)) {
1✔
2340
                        throw new IllegalArgumentException("Entity type not supported.");
×
2341
                }
2342

2343
                var type = entity.getClass();
1✔
2344
                try {
2345
                        var metadata = handler.getMetadata(this.transactionManager, type);
1✔
2346
                        var keyColumns = metadata.getKeyColumns();
1✔
2347

2348
                        if (keyColumns.isEmpty()) {
1✔
2349
                                throw new IllegalArgumentException("Entity has no keys.");
×
2350
                        }
2351

2352
                        var mappingColumns = MappingUtils.getMappingColumnMap(metadata.getSchema(), type, SqlKind.UPDATE);
1✔
2353
                        var query = (SqlEntityQuery<E>) query(type);
1✔
2354
                        for (var column : keyColumns) {
1✔
2355
                                var camelName = column.getCamelColumnName();
1✔
2356
                                query.equal(camelName, mappingColumns.get(camelName).getValue(entity));
1✔
2357
                        }
1✔
2358
                        if (locking) {
1✔
2359
                                query.forUpdateNoWait();
1✔
2360
                        }
2361
                        return query.first()
1✔
2362
                                        .map(findEntity -> {
1✔
2363
                                                for (var mappingColumn : mappingColumns.values()) {
1✔
2364
                                                        if (!mappingColumn.isId()) {
1✔
2365
                                                                var value = mappingColumn.getValue(entity);
1✔
2366
                                                                if (value != null) {
1✔
2367
                                                                        mappingColumn.setValue(findEntity, value);
1✔
2368
                                                                }
2369
                                                        }
2370
                                                }
1✔
2371
                                                return updateAndReturn(findEntity);
1✔
2372
                                        }).orElseGet(() -> insertAndReturn(entity));
1✔
2373
                } catch (SQLException ex) {
×
2374
                        throw new EntitySqlRuntimeException(SqlKind.MERGE, ex);
×
2375
                }
2376
        }
2377

2378
        /**
2379
         * {@inheritDoc}
2380
         *
2381
         * @see jp.co.future.uroborosql.SqlAgent#delete(java.lang.Object)
2382
         */
2383
        @Override
2384
        public <E> int delete(final E entity) {
2385
                if (entity instanceof Stream) {
1✔
2386
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
2387
                }
2388

2389
                var handler = this.getEntityHandler();
1✔
2390
                if (!handler.getEntityType().isInstance(entity)) {
1✔
2391
                        throw new IllegalArgumentException("Entity type not supported");
×
2392
                }
2393

2394
                try {
2395
                        var entityType = entity.getClass();
1✔
2396
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2397
                        var context = handler.createDeleteContext(this, metadata, entityType, true);
1✔
2398
                        context.setSqlKind(SqlKind.ENTITY_DELETE);
1✔
2399

2400
                        // EntityDelete実行前イベント発行
2401
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
2402
                        if (eventListenerHolder.hasBeforeEntityDeleteListener()) {
1✔
2403
                                var eventObj = new BeforeEntityDeleteEvent(context, entity, entityType);
1✔
2404
                                for (var listener : eventListenerHolder.getBeforeEntityDeleteListeners()) {
1✔
2405
                                        listener.accept(eventObj);
1✔
2406
                                }
1✔
2407
                        }
2408
                        handler.setDeleteParams(context, entity);
1✔
2409

2410
                        var count = handler.doDelete(this, context, entity);
1✔
2411

2412
                        // EntityDelete実行後イベント発行
2413
                        if (eventListenerHolder.hasAfterEntityDeleteListener()) {
1✔
2414
                                var eventObj = new AfterEntityDeleteEvent(context, entity, entityType, count);
1✔
2415
                                for (var listener : eventListenerHolder.getAfterEntityDeleteListeners()) {
1✔
2416
                                        listener.accept(eventObj);
1✔
2417
                                }
1✔
2418
                                count = eventObj.getCount();
1✔
2419
                        }
2420

2421
                        return count;
1✔
2422
                } catch (SQLException ex) {
×
2423
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_DELETE, ex);
×
2424
                }
2425
        }
2426

2427
        /**
2428
         * {@inheritDoc}
2429
         *
2430
         * @see jp.co.future.uroborosql.SqlAgent#deleteAndReturn(java.lang.Object)
2431
         */
2432
        @Override
2433
        public <E> E deleteAndReturn(final E entity) {
2434
                delete(entity);
1✔
2435
                return entity;
1✔
2436
        }
2437

2438
        /**
2439
         * {@inheritDoc}
2440
         *
2441
         * @see jp.co.future.uroborosql.SqlAgent#delete(java.lang.Class, java.lang.Object[])
2442
         */
2443
        @Override
2444
        public <E> int delete(final Class<? extends E> entityType, final Object... keys) {
2445
                var handler = this.getEntityHandler();
1✔
2446
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2447
                        throw new IllegalArgumentException("Entity type not supported");
×
2448
                }
2449

2450
                try {
2451
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2452
                        TableMetadata.Column keyColumn = null;
1✔
2453
                        var keyColumns = metadata.getKeyColumns();
1✔
2454
                        if (keyColumns.size() == 1) {
1✔
2455
                                keyColumn = keyColumns.get(0);
1✔
2456
                        } else if (keyColumns.isEmpty()) {
1✔
2457
                                keyColumn = metadata.getColumns().get(0);
1✔
2458
                        } else {
2459
                                throw new IllegalArgumentException("Entity has multiple keys");
1✔
2460
                        }
2461
                        return delete(entityType).in(keyColumn.getCamelColumnName(), keys).count();
1✔
2462
                } catch (SQLException ex) {
×
2463
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_DELETE, ex);
×
2464
                }
2465
        }
2466

2467
        /**
2468
         * {@inheritDoc}
2469
         *
2470
         * @see jp.co.future.uroborosql.SqlAgent#delete(java.lang.Class)
2471
         */
2472
        @Override
2473
        public <E> SqlEntityDelete<E> delete(final Class<? extends E> entityType) {
2474
                var handler = this.getEntityHandler();
1✔
2475
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2476
                        throw new IllegalArgumentException("Entity type not supported");
×
2477
                }
2478

2479
                try {
2480
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2481
                        var context = handler.createDeleteContext(this, metadata, entityType, false);
1✔
2482
                        context.setSqlKind(SqlKind.ENTITY_DELETE);
1✔
2483
                        return new SqlEntityDeleteImpl<>(this, handler, metadata, context, entityType);
1✔
2484
                } catch (SQLException ex) {
×
2485
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_DELETE, ex);
×
2486
                }
2487
        }
2488

2489
        /**
2490
         * {@inheritDoc}
2491
         *
2492
         * @see jp.co.future.uroborosql.SqlAgent#truncate(java.lang.Class)
2493
         */
2494
        @Override
2495
        public <E> SqlAgent truncate(final Class<? extends E> entityType) {
2496
                var handler = this.getEntityHandler();
1✔
2497
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2498
                        throw new IllegalArgumentException("Entity type not supported");
×
2499
                }
2500
                try {
2501
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2502
                        var context = this.context().setSql("truncate table " + metadata.getTableIdentifier());
1✔
2503
                        context.setSqlKind(SqlKind.TRUNCATE);
1✔
2504
                        update(context);
1✔
2505
                        return this;
1✔
2506
                } catch (SQLException ex) {
1✔
2507
                        throw new EntitySqlRuntimeException(SqlKind.TRUNCATE, ex);
1✔
2508
                }
2509
        }
2510

2511
        /**
2512
         * 自動採番カラムを取得する. 合わせて、{@link ExecutionContext#setGeneratedKeyColumns(String[])} に自動採番カラムを設定する.
2513
         * <pre>
2514
         * 自動採番カラムの条件
2515
         * 1. {@link MappingColumn#isId()} == true 、もしくは{@link TableMetadata.Column#isAutoincrement()} == true であること
2516
         * 2. 1.に加え、エンティティの対象フィールド型がprimitive型、もしくはフィールドの値が<code>null</code>であること
2517
         * </pre>
2518
         *
2519
         * @param <E> entity
2520
         * @param context ExecutionContext
2521
         * @param mappingColumns EntityのMappingColumn配列
2522
         * @param metadata TableMetadata
2523
         * @param entity Entity
2524
         * @return 自動採番カラム配列
2525
         */
2526
        private <E> List<MappingColumn> getAutoGeneratedColumns(final ExecutionContext context,
2527
                        final MappingColumn[] mappingColumns,
2528
                        final TableMetadata metadata,
2529
                        final E entity) {
2530
                var autoGeneratedColumns = new ArrayList<MappingColumn>();
1✔
2531
                var autoGeneratedColumnNames = new ArrayList<String>();
1✔
2532

2533
                if (mappingColumns != null && mappingColumns.length > 0) {
1✔
2534
                        for (var column : metadata.getColumns()) {
1✔
2535
                                if (column.isAutoincrement()) {
1✔
2536
                                        for (var mappingColumn : mappingColumns) {
1✔
2537
                                                if (mappingColumn.getCamelName().equals(column.getCamelColumnName())) {
1✔
2538
                                                        // primitive型か、エンティティの対象フィールドがnullとなっている場合自動生成カラムとする
2539
                                                        if (mappingColumn.getJavaType().getRawType().isPrimitive()
1✔
2540
                                                                        || mappingColumn.getValue(entity) == null) {
1✔
2541
                                                                autoGeneratedColumns.add(mappingColumn);
1✔
2542
                                                                autoGeneratedColumnNames.add(column.getColumnName());
1✔
2543
                                                        }
2544
                                                        break;
2545
                                                }
2546
                                        }
2547
                                }
2548
                        }
1✔
2549
                        for (var mappingColumn : mappingColumns) {
1✔
2550
                                if (!mappingColumn.isId() || autoGeneratedColumns.contains(mappingColumn)) {
1✔
2551
                                        continue;
1✔
2552
                                }
2553
                                for (var column : metadata.getColumns()) {
1✔
2554
                                        if (mappingColumn.getCamelName().equals(column.getCamelColumnName())) {
1✔
2555
                                                // primitive型か、エンティティの対象フィールドがnullとなっている場合自動生成カラムとする
2556
                                                if (mappingColumn.getJavaType().getRawType().isPrimitive()
1✔
2557
                                                                || mappingColumn.getValue(entity) == null) {
1✔
2558
                                                        autoGeneratedColumns.add(mappingColumn);
1✔
2559
                                                        autoGeneratedColumnNames.add(column.getColumnName());
1✔
2560
                                                }
2561
                                                break;
2562
                                        }
2563
                                }
1✔
2564
                        }
2565
                }
2566

2567
                if (!autoGeneratedColumnNames.isEmpty()) {
1✔
2568
                        context.setGeneratedKeyColumns(
1✔
2569
                                        autoGeneratedColumnNames.toArray(new String[autoGeneratedColumnNames.size()]));
1✔
2570
                }
2571
                return autoGeneratedColumns;
1✔
2572
        }
2573

2574
        /**
2575
         * entityにID値を設定する
2576
         * @param entity Entity
2577
         * @param id ID値
2578
         * @param column 設定する対象のカラム
2579
         */
2580
        private void setEntityIdValue(final Object entity, final Object id, final MappingColumn column) {
2581
                var rawType = column.getJavaType().getRawType();
1✔
2582
                try {
2583
                        if (int.class.equals(rawType) || Integer.class.equals(rawType)) {
1✔
2584
                                if (id instanceof Number) {
1✔
2585
                                        column.setValue(entity, ((Number) id).intValue());
1✔
2586
                                } else {
2587
                                        throw new IllegalArgumentException();
1✔
2588
                                }
2589
                        } else if (long.class.equals(rawType) || Long.class.equals(rawType)) {
1✔
2590
                                if (id instanceof Number) {
1✔
2591
                                        column.setValue(entity, ((Number) id).longValue());
1✔
2592
                                } else {
2593
                                        throw new IllegalArgumentException();
1✔
2594
                                }
2595
                        } else if (BigInteger.class.equals(rawType)) {
1✔
2596
                                if (id instanceof BigInteger) {
1✔
2597
                                        column.setValue(entity, id);
×
2598
                                } else if (id instanceof BigDecimal) {
1✔
2599
                                        column.setValue(entity, ((BigDecimal) id).toBigInteger());
1✔
2600
                                } else if (id instanceof Number) {
1✔
2601
                                        column.setValue(entity, new BigInteger(((Number) id).toString()));
1✔
2602
                                } else {
2603
                                        throw new IllegalArgumentException();
1✔
2604
                                }
2605
                        } else if (BigDecimal.class.equals(rawType)) {
1✔
2606
                                if (id instanceof BigDecimal) {
1✔
2607
                                        column.setValue(entity, id);
1✔
2608
                                } else if (id instanceof BigInteger) {
1✔
2609
                                        column.setValue(entity, new BigDecimal((BigInteger) id));
×
2610
                                } else if (id instanceof Number) {
1✔
2611
                                        column.setValue(entity, new BigDecimal(((Number) id).toString()));
1✔
2612
                                } else {
2613
                                        throw new IllegalArgumentException();
1✔
2614
                                }
2615
                        } else if (String.class.equals(rawType)) {
1✔
2616
                                if (id instanceof String) {
1✔
2617
                                        column.setValue(entity, id);
×
2618
                                } else if (id instanceof BigDecimal) {
1✔
2619
                                        column.setValue(entity, ((BigDecimal) id).toPlainString());
1✔
2620
                                } else if (id instanceof Number) {
1✔
2621
                                        column.setValue(entity, ((Number) id).toString());
1✔
2622
                                } else {
2623
                                        column.setValue(entity, id.toString());
1✔
2624
                                }
2625
                        } else {
2626
                                try {
2627
                                        column.setValue(entity, id);
1✔
2628
                                } catch (UroborosqlRuntimeException ex) {
1✔
2629
                                        throw new UroborosqlRuntimeException(
1✔
2630
                                                        "Column is not correct as ID type. column=" + column.getCamelName() + ", type=" + rawType);
1✔
2631
                                }
1✔
2632
                        }
2633
                } catch (IllegalArgumentException ex) {
1✔
2634
                        throw new UroborosqlRuntimeException(
1✔
2635
                                        "Column type and ID type do not match. column=" + column.getCamelName() + ", type="
1✔
2636
                                                        + rawType
2637
                                                        + ", id=" + id + ", type=" + id.getClass().getSimpleName());
1✔
2638
                }
1✔
2639
        }
1✔
2640

2641
        /**
2642
         *
2643
         * {@inheritDoc}
2644
         *
2645
         * @see jp.co.future.uroborosql.SqlAgent#getSqlConfig()
2646
         */
2647
        @Override
2648
        public SqlConfig getSqlConfig() {
2649
                return this.sqlConfig;
1✔
2650
        }
2651

2652
        /**
2653
         *
2654
         * {@inheritDoc}
2655
         *
2656
         * @see jp.co.future.uroborosql.SqlAgent#context()
2657
         */
2658
        @Override
2659
        public ExecutionContext context() {
2660
                return sqlConfig.context();
1✔
2661
        }
2662

2663
        /**
2664
         * {@inheritDoc}
2665
         *
2666
         * @see jp.co.future.uroborosql.SqlAgent#getFetchSize()
2667
         */
2668
        @Override
2669
        public int getFetchSize() {
2670
                return fetchSize;
1✔
2671
        }
2672

2673
        /**
2674
         * {@inheritDoc}
2675
         *
2676
         * @see jp.co.future.uroborosql.SqlAgent#setFetchSize(int)
2677
         */
2678
        @Override
2679
        public SqlAgent setFetchSize(final int fetchSize) {
2680
                this.fetchSize = fetchSize;
1✔
2681
                return this;
1✔
2682
        }
2683

2684
        /**
2685
         * {@inheritDoc}
2686
         *
2687
         * @see jp.co.future.uroborosql.SqlAgent#getQueryTimeout()
2688
         */
2689
        @Override
2690
        public int getQueryTimeout() {
2691
                return queryTimeout;
1✔
2692
        }
2693

2694
        /**
2695
         * {@inheritDoc}
2696
         *
2697
         * @see jp.co.future.uroborosql.SqlAgent#setQueryTimeout(int)
2698
         */
2699
        @Override
2700
        public SqlAgent setQueryTimeout(final int queryTimeout) {
2701
                this.queryTimeout = queryTimeout;
1✔
2702
                return this;
1✔
2703
        }
2704

2705
        /**
2706
         * SQL実行をリトライするSQLエラーコードのリスト を取得します
2707
         *
2708
         * @return SQL実行をリトライするSQLエラーコードのリスト
2709
         */
2710
        public List<String> getSqlRetryCodes() {
2711
                return sqlRetryCodes;
1✔
2712
        }
2713

2714
        /**
2715
         * SQL実行をリトライするSQLエラーコードのリスト を設定します
2716
         *
2717
         * @param sqlRetryCodes SQL実行をリトライするSQLエラーコードのリスト
2718
         * @return SqlAgent
2719
         */
2720
        public SqlAgent setSqlRetryCodes(final List<String> sqlRetryCodes) {
2721
                this.sqlRetryCodes = sqlRetryCodes;
1✔
2722
                return this;
1✔
2723
        }
2724

2725
        /**
2726
         * 最大リトライ回数 を取得します
2727
         *
2728
         * @return 最大リトライ回数
2729
         */
2730
        public int getMaxRetryCount() {
2731
                return maxRetryCount;
1✔
2732
        }
2733

2734
        /**
2735
         * 最大リトライ回数 を設定します
2736
         *
2737
         * @param maxRetryCount 最大リトライ回数
2738
         * @return SqlAgent
2739
         */
2740
        public SqlAgent setMaxRetryCount(final int maxRetryCount) {
2741
                this.maxRetryCount = maxRetryCount;
1✔
2742
                return this;
1✔
2743
        }
2744

2745
        /**
2746
         * リトライタイムアウト時間(ms) を取得します
2747
         *
2748
         * @return リトライタイムアウト時間(ms)
2749
         */
2750
        public int getRetryWaitTime() {
2751
                return retryWaitTime;
1✔
2752
        }
2753

2754
        /**
2755
         * リトライタイムアウト時間(ms) を設定します
2756
         *
2757
         * @param retryWaitTime リトライタイムアウト時間(ms)
2758
         * @return SqlAgent
2759
         */
2760
        public SqlAgent setRetryWaitTime(final int retryWaitTime) {
2761
                this.retryWaitTime = retryWaitTime;
1✔
2762
                return this;
1✔
2763
        }
2764

2765
        /**
2766
         *
2767
         * {@inheritDoc}
2768
         *
2769
         * @see jp.co.future.uroborosql.SqlAgent#getMapKeyCaseFormat()
2770
         */
2771
        @Override
2772
        public CaseFormat getMapKeyCaseFormat() {
2773
                return mapKeyCaseFormat;
1✔
2774
        }
2775

2776
        /**
2777
         *
2778
         * {@inheritDoc}
2779
         *
2780
         * @see jp.co.future.uroborosql.SqlAgent#setMapKeyCaseFormat(jp.co.future.uroborosql.utils.CaseFormat)
2781
         */
2782
        @Override
2783
        public SqlAgent setMapKeyCaseFormat(final CaseFormat mapKeyCaseFormat) {
2784
                this.mapKeyCaseFormat = mapKeyCaseFormat;
1✔
2785
                return this;
1✔
2786
        }
2787

2788
        /**
2789
         *
2790
         * {@inheritDoc}
2791
         *
2792
         * @see jp.co.future.uroborosql.SqlAgent#getInsertsType()
2793
         */
2794
        @Override
2795
        public InsertsType getInsertsType() {
2796
                return this.insertsType;
1✔
2797
        }
2798

2799
        /**
2800
         *
2801
         * {@inheritDoc}
2802
         *
2803
         * @see jp.co.future.uroborosql.SqlAgent#setInsertsType(jp.co.future.uroborosql.enums.InsertsType)
2804
         */
2805
        @Override
2806
        public SqlAgent setInsertsType(final InsertsType defaultInsertsType) {
2807
                this.insertsType = defaultInsertsType;
1✔
2808
                return this;
1✔
2809
        }
2810

2811
        /**
2812
         * SQLリソース管理クラスを取得します。
2813
         *
2814
         * @return SQLリソース管理クラス
2815
         */
2816
        private SqlResourceManager getSqlResourceManager() {
2817
                return sqlConfig.getSqlResourceManager();
1✔
2818
        }
2819

2820
        /**
2821
         * ORM処理クラス を取得します。
2822
         *
2823
         * @return ORM処理クラス
2824
         */
2825
        @SuppressWarnings("unchecked")
2826
        private <E> EntityHandler<E> getEntityHandler() {
2827
                return (EntityHandler<E>) sqlConfig.getEntityHandler();
1✔
2828
        }
2829

2830
        /**
2831
         * Dialect を取得します。
2832
         *
2833
         * @return Dialect
2834
         */
2835
        private Dialect getDialect() {
2836
                return sqlConfig.getDialect();
1✔
2837
        }
2838

2839
        /**
2840
         * ステートメント初期化。
2841
         *
2842
         * @param executionContext ExecutionContext
2843
         * @return PreparedStatement
2844
         * @throws SQLException SQL例外
2845
         */
2846
        private PreparedStatement getPreparedStatement(final ExecutionContext executionContext) throws SQLException {
2847
                var stmt = ((LocalTransactionManager) transactionManager).getPreparedStatement(executionContext);
1✔
2848
                // プロパティ設定
2849
                applyProperties(stmt);
1✔
2850
                return stmt;
1✔
2851
        }
2852

2853
        /**
2854
         * Callableステートメント初期化
2855
         *
2856
         * @param executionContext ExecutionContext
2857
         * @return CallableStatement
2858
         * @throws SQLException SQL例外
2859
         */
2860
        private CallableStatement getCallableStatement(final ExecutionContext executionContext) throws SQLException {
2861
                var stmt = ((LocalTransactionManager) transactionManager).getCallableStatement(executionContext);
1✔
2862
                // プロパティ設定
2863
                applyProperties(stmt);
1✔
2864
                return stmt;
1✔
2865
        }
2866

2867
        /**
2868
         * フェッチサイズとクエリタイムアウトをPreparedStatementに設定する
2869
         *
2870
         * @param preparedStatement PreparedStatement
2871
         * @throws SQLException SQL例外
2872
         */
2873
        private void applyProperties(final PreparedStatement preparedStatement) throws SQLException {
2874
                // フェッチサイズ指定
2875
                if (getFetchSize() >= 0 && !(preparedStatement instanceof CallableStatement)) {
1✔
2876
                        preparedStatement.setFetchSize(getFetchSize());
1✔
2877
                }
2878

2879
                // クエリタイムアウト指定
2880
                if (getQueryTimeout() >= 0) {
1✔
2881
                        preparedStatement.setQueryTimeout(getQueryTimeout());
1✔
2882
                }
2883
        }
1✔
2884

2885
        /**
2886
         * 経過時間を計算し、HH:mm:ss.SSSSSSにフォーマットする.
2887
         *
2888
         * @param start 開始時間
2889
         * @param end 終了時間
2890
         * @return フォーマットした経過時間
2891
         */
2892
        private static String formatElapsedTime(final Instant start, final Instant end) {
2893
                return ELAPSED_TIME_FORMAT.format(LocalTime.MIDNIGHT.plus(Duration.between(start != null ? start : end, end)));
1✔
2894
        }
2895

2896
        /**
2897
         * ResultSetをStreamで扱うためのSpliterator
2898
         *
2899
         * @author H.Sugimoto
2900
         *
2901
         * @param <T> ResultSetの1行を変換した型
2902
         */
2903
        private static final class ResultSetSpliterator<T> extends Spliterators.AbstractSpliterator<T> {
2904
                private final ResultSetConverter<T> converter;
2905
                private final ResultSet rs;
2906
                private boolean finished = false;
1✔
2907

2908
                private ResultSetSpliterator(final ResultSet rs, final ResultSetConverter<T> converter) {
2909
                        super(Long.MAX_VALUE, Spliterator.ORDERED);
1✔
2910
                        this.rs = rs;
1✔
2911
                        this.converter = converter;
1✔
2912
                }
1✔
2913

2914
                @Override
2915
                public boolean tryAdvance(final Consumer<? super T> action) {
2916
                        try {
2917
                                if (finished || !rs.next()) {
1✔
2918
                                        if (!rs.isClosed()) {
1✔
2919
                                                rs.close();
1✔
2920
                                        }
2921
                                        finished = true;
1✔
2922
                                        return false;
1✔
2923
                                }
2924
                                action.accept(converter.createRecord(rs));
1✔
2925
                                return true;
1✔
2926
                        } catch (RuntimeException | Error ex) {
1✔
2927
                                try {
2928
                                        if (rs != null && !rs.isClosed()) {
1✔
2929
                                                rs.close();
1✔
2930
                                        }
2931
                                } catch (SQLException ex2) {
×
NEW
2932
                                        LOG.atError()
×
NEW
2933
                                                        .setMessage(ex2.getMessage())
×
NEW
2934
                                                        .setCause(ex2)
×
NEW
2935
                                                        .log();
×
2936
                                }
1✔
2937
                                throw ex;
1✔
2938
                        } catch (SQLException ex) {
×
2939
                                try {
2940
                                        if (rs != null && !rs.isClosed()) {
×
2941
                                                rs.close();
×
2942
                                        }
2943
                                } catch (SQLException ex2) {
×
NEW
2944
                                        LOG.atError()
×
NEW
2945
                                                        .setMessage(ex2.getMessage())
×
NEW
2946
                                                        .setCause(ex2)
×
NEW
2947
                                                        .log();
×
2948
                                }
×
2949
                                throw new UroborosqlSQLException(ex);
×
2950
                        }
2951
                }
2952
        }
2953

2954
        /**
2955
         * ResultSetのラッパークラス。ResultSetのクローズに合わせてStatementもクローズする。
2956
         *
2957
         * @author H.Sugimoto
2958
         * @version 0.5.0
2959
         */
2960
        private static class InnerResultSet extends AbstractResultSetWrapper {
2961
                /** 同期してクローズするStatement */
2962
                private final Statement stmt;
2963

2964
                /**
2965
                 * コンストラクタ
2966
                 *
2967
                 * @param wrapped 元となるResultSet
2968
                 * @param stmt Statement
2969
                 */
2970
                InnerResultSet(final ResultSet wrapped, final Statement stmt) {
2971
                        super(wrapped);
1✔
2972
                        this.stmt = stmt;
1✔
2973
                }
1✔
2974

2975
                /**
2976
                 * {@inheritDoc}
2977
                 *
2978
                 * @see jp.co.future.uroborosql.AbstractResultSetWrapper#close()
2979
                 */
2980
                @Override
2981
                public void close() throws SQLException {
2982
                        try {
2983
                                super.close();
1✔
2984
                        } finally {
2985
                                try {
2986
                                        if (stmt != null && !stmt.isClosed()) {
1✔
2987
                                                stmt.close();
1✔
2988
                                        }
2989
                                } catch (SQLException ex) {
×
2990
                                        // do nothing
2991
                                }
1✔
2992
                        }
2993
                }
1✔
2994
        }
2995
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc