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

future-architect / uroborosql / #769

12 Sep 2024 02:26PM UTC coverage: 90.35% (-0.9%) from 91.21%
#769

Pull #332

HidekiSugimoto189
Refactoring log-related class and method names.
Pull Request #332: Enable per-SQL-ID log suppression (#322)

384 of 587 new or added lines in 32 files covered. (65.42%)

9 existing lines in 7 files now uncovered.

8885 of 9834 relevant lines covered (90.35%)

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.support.CoverageLoggingSupport;
84
import jp.co.future.uroborosql.log.support.PerformanceLoggingSupport;
85
import jp.co.future.uroborosql.log.support.ReplLoggingSupport;
86
import jp.co.future.uroborosql.log.support.ServiceLoggingSupport;
87
import jp.co.future.uroborosql.log.support.SqlLoggingSupport;
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, ServiceLoggingSupport, PerformanceLoggingSupport, SqlLoggingSupport,
106
                CoverageLoggingSupport, ReplLoggingSupport {
107
        /** ExecutionContext属性キー:リトライカウント */
108
        private static final String CTX_ATTR_KEY_RETRY_COUNT = "__retryCount";
109

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

617
        /**
618
         * {@inheritDoc}
619
         *
620
         * @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)
621
         */
622
        @Override
623
        public <E> int inserts(final Class<E> entityType,
624
                        final Stream<E> entities,
625
                        final InsertsCondition<? super E> condition,
626
                        final InsertsType insertsType) {
627
                if (insertsType == InsertsType.BULK && getDialect().supportsBulkInsert()) {
1✔
628
                        return bulkInsert(entityType, entities, condition, null);
1✔
629
                } else {
630
                        return batchInsert(entityType, entities, condition, null);
1✔
631
                }
632
        }
633

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

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

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

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

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

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

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

722
        /**
723
         * {@inheritDoc}
724
         *
725
         * @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)
726
         */
727
        @Override
728
        public <E> Stream<E> insertsAndReturn(final Class<E> entityType,
729
                        final Stream<E> entities,
730
                        final InsertsCondition<? super E> condition,
731
                        final InsertsType insertsType) {
732
                var insertedEntities = new ArrayList<E>();
1✔
733
                if (insertsType == InsertsType.BULK && getDialect().supportsBulkInsert()) {
1✔
734
                        bulkInsert(entityType, entities, condition, insertedEntities);
1✔
735
                } else {
736
                        batchInsert(entityType, entities, condition, insertedEntities);
1✔
737
                }
738
                return insertedEntities.stream();
1✔
739
        }
740

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1060
                return counts;
1✔
1061
        }
1062

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

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

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

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

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

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

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

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

1139
                                frameCount++;
1✔
1140

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

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

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

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

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

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

1195
                return count;
1✔
1196
        }
1197

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

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

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

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

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

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

1253
                                handler.setUpdateParams(context, entity);
1✔
1254
                                context.addBatch();
1✔
1255

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

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

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

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

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

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

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

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

1336
                return counts;
1✔
1337
        }
1338

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

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

1353
                var stmt = getPreparedStatement(executionContext);
1✔
1354

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

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

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

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

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

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

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

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

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

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

1508
                Instant startTime = null;
1✔
1509

1510
                try (var stmt = getPreparedStatement(executionContext)) {
1✔
1511

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

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

1518
                        debugWith(LOG)
1✔
1519
                                        .setMessage("Execute update sql. sqlName: {}")
1✔
1520
                                        .addArgument(executionContext.getSqlName())
1✔
1521
                                        .log();
1✔
1522

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

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

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

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

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

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

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

1640
                Instant startTime = null;
1✔
1641

1642
                try (var stmt = getPreparedStatement(executionContext)) {
1✔
1643

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

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

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

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

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

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

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

1762
                Instant startTime = null;
1✔
1763

1764
                try (var callableStatement = getCallableStatement(executionContext)) {
1✔
1765

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

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

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

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

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

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

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

1880
                        originalSql = originalSql.replace(keySqlId, sqlId);
1✔
1881
                }
1882

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

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

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

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

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

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

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

1942
                                COVERAGE_HANDLER_REF.get().accept(coverageData);
1✔
1943
                        }
1944
                }
1945

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

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

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

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

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

2004
                throw cause;
1✔
2005
        }
2006

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2977
                /**
2978
                 * {@inheritDoc}
2979
                 *
2980
                 * @see jp.co.future.uroborosql.AbstractResultSetWrapper#close()
2981
                 */
2982
                @Override
2983
                public void close() throws SQLException {
2984
                        try {
2985
                                super.close();
1✔
2986
                        } finally {
2987
                                try {
2988
                                        if (stmt != null && !stmt.isClosed()) {
1✔
2989
                                                stmt.close();
1✔
2990
                                        }
2991
                                } catch (SQLException ex) {
×
2992
                                        // do nothing
2993
                                }
1✔
2994
                        }
2995
                }
1✔
2996
        }
2997
}
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