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

future-architect / uroborosql / #810

30 Oct 2024 01:00PM UTC coverage: 90.074%. Remained the same
#810

push

web-flow
Changed the internal field that SqlInfo has from a Path type to a URL type (#345)

8 of 10 new or added lines in 2 files covered. (80.0%)

1 existing line in 1 file now uncovered.

8684 of 9641 relevant lines covered (90.07%)

0.9 hits per line

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

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

97
/**
98
 * SQL実行用クラス。
99
 *
100
 * @author H.Sugimoto
101
 */
102
public class SqlAgentImpl implements SqlAgent, ServiceLoggingSupport, PerformanceLoggingSupport, SqlLoggingSupport,
103
                CoverageLoggingSupport {
104
        /** ExecutionContext属性キー:リトライカウント */
105
        private static final String CTX_ATTR_KEY_RETRY_COUNT = "__retryCount";
106

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

199
                if (handler != null) {
1✔
200
                        COVERAGE_HANDLER_REF.set(handler);
1✔
201
                }
202
        }
1✔
203

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

982
                                if (!entityType.isInstance(entity)) {
1✔
983
                                        throw new IllegalArgumentException("Entity types do not match");
1✔
984
                                }
985

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

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

1000
                                entityList.add(entity);
1✔
1001
                                if (insertedEntities != null) {
1✔
1002
                                        insertedEntities.add(entity);
1✔
1003
                                }
1004

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

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

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

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

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

1063
                return counts;
1✔
1064
        }
1065

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

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

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

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

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

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

1126
                                entityList.add(entity);
1✔
1127
                                if (insertedEntities != null) {
1✔
1128
                                        insertedEntities.add(entity);
1✔
1129
                                }
1130

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

1142
                                frameCount++;
1✔
1143

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

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

1162
                } catch (SQLException ex) {
×
1163
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_BULK_INSERT, ex);
×
1164
                }
1165
        }
1166

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

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

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

1198
                return count;
1✔
1199
        }
1200

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

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

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

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

1241
                                entityList.add(entity);
1✔
1242
                                if (updatedEntities != null) {
1✔
1243
                                        updatedEntities.add(entity);
1✔
1244
                                }
1245
                                entityCount++;
1✔
1246

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

1256
                                handler.setUpdateParams(context, entity);
1✔
1257
                                context.addBatch();
1✔
1258

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

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

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

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

1287
                                        for (var start = 0; start < entitySize; start = start + IN_CLAUSE_MAX_PARAM_SIZE) {
1✔
1288
                                                var end = Math.min(start + IN_CLAUSE_MAX_PARAM_SIZE, entitySize);
1✔
1289
                                                var subList = keyList.subList(start, end);
1✔
1290

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

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

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

1339
                return counts;
1✔
1340
        }
1341

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

1353
                // コンテキスト変換
1354
                transformContext(executionContext);
1✔
1355

1356
                var stmt = getPreparedStatement(executionContext);
1✔
1357

1358
                // INパラメータ設定
1359
                executionContext.bindParams(stmt);
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.isDebugEnabled() ? 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
                        debugWith(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
                        debugWith(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
                        debugWith(LOG)
1✔
1516
                                        .setMessage("Execute update sql. sqlName: {}")
1✔
1517
                                        .addArgument(executionContext.getSqlName())
1✔
1518
                                        .log();
1✔
1519

1520
                        if (PERFORMANCE_LOG.isDebugEnabled()) {
1✔
1521
                                startTime = Instant.now(getSqlConfig().getClock());
×
1522
                        }
1523

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

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

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

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

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

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

1637
                Instant startTime = null;
1✔
1638

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

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

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

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

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

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

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

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

1759
                Instant startTime = null;
1✔
1760

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

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

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

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

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

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

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

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

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

1880
                if (SQL_LOG.isDebugEnabled() && sqlName != null) {
1✔
1881
                        if (executionContext.getSqlKind().isEntityType()) {
×
1882
                                debugWith(SQL_LOG)
×
1883
                                                .setMessage("EntityClass : {}")
×
1884
                                                .addArgument(sqlName)
×
1885
                                                .log();
×
1886
                        } else if (getSqlResourceManager().existSql(sqlName)) {
×
1887
                                debugWith(SQL_LOG)
×
NEW
1888
                                                .setMessage("SQLUrl : {}")
×
NEW
1889
                                                .addArgument(() -> getSqlResourceManager().getSqlUrl(sqlName))
×
UNCOV
1890
                                                .log();
×
1891
                        }
1892
                }
1893

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

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

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

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

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

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

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

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

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

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

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

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

2001
                throw cause;
1✔
2002
        }
2003

2004
        /**
2005
         *
2006
         * {@inheritDoc}
2007
         *
2008
         * @see jp.co.future.uroborosql.SqlAgent#find(java.lang.Class, java.lang.Object[])
2009
         */
2010
        @SuppressWarnings("unchecked")
2011
        @Override
2012
        public <E> Optional<E> find(final Class<? extends E> entityType, final Object... keys) {
2013
                @SuppressWarnings("rawtypes")
2014
                EntityHandler handler = this.getEntityHandler();
1✔
2015
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2016
                        throw new IllegalArgumentException("Entity type not supported");
×
2017
                }
2018

2019
                try {
2020
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2021
                        var keyNames = metadata.getColumns().stream()
1✔
2022
                                        .filter(TableMetadata.Column::isKey)
1✔
2023
                                        .sorted(Comparator.comparingInt(TableMetadata.Column::getKeySeq))
1✔
2024
                                        .map(TableMetadata.Column::getColumnName)
1✔
2025
                                        .map(CaseFormat.CAMEL_CASE::convert)
1✔
2026
                                        .toArray(String[]::new);
1✔
2027

2028
                        if (keyNames.length != keys.length) {
1✔
2029
                                throw new IllegalArgumentException("Number of keys does not match");
×
2030
                        }
2031
                        var params = new HashMap<String, Object>();
1✔
2032
                        for (var i = 0; i < keys.length; i++) {
1✔
2033
                                params.put(keyNames[i], keys[i]);
1✔
2034
                        }
2035

2036
                        var context = handler.createSelectContext(this, metadata, entityType, true);
1✔
2037
                        context.setSqlKind(SqlKind.ENTITY_SELECT).paramMap(params);
1✔
2038

2039
                        // EntityQuery実行前イベント発行
2040
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
2041
                        if (eventListenerHolder.hasBeforeEntityQueryListener()) {
1✔
2042
                                var eventObj = new BeforeEntityQueryEvent(context, null, entityType);
×
2043
                                for (var listener : eventListenerHolder.getBeforeEntityQueryListeners()) {
×
2044
                                        listener.accept(eventObj);
×
2045
                                }
×
2046
                        }
2047

2048
                        var results = handler.doSelect(this, context, entityType);
1✔
2049

2050
                        // EntityQuery実行後イベント発行
2051
                        if (eventListenerHolder.hasAfterEntityQueryListener()) {
1✔
2052
                                var eventObj = new AfterEntityQueryEvent(context(), null, entityType, results);
×
2053
                                for (var listener : eventListenerHolder.getAfterEntityQueryListeners()) {
×
2054
                                        listener.accept(eventObj);
×
2055
                                }
×
2056
                                results = eventObj.getResults();
×
2057
                        }
2058

2059
                        try (var stream = results) {
1✔
2060
                                return stream.findFirst();
1✔
2061
                        }
2062
                } catch (SQLException ex) {
×
2063
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_SELECT, ex);
×
2064
                }
2065
        }
2066

2067
        /**
2068
         * {@inheritDoc}
2069
         *
2070
         * @see jp.co.future.uroborosql.SqlAgent#insert(java.lang.Object)
2071
         */
2072
        @SuppressWarnings("unchecked")
2073
        @Override
2074
        public <E> int insert(final E entity) {
2075
                if (entity instanceof Stream) {
1✔
2076
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
2077
                }
2078

2079
                @SuppressWarnings("rawtypes")
2080
                EntityHandler handler = this.getEntityHandler();
1✔
2081
                if (!handler.getEntityType().isInstance(entity)) {
1✔
2082
                        throw new IllegalArgumentException("Entity type not supported");
×
2083
                }
2084

2085
                try {
2086
                        var entityType = entity.getClass();
1✔
2087
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2088
                        var context = handler.createInsertContext(this, metadata, entityType);
1✔
2089
                        context.setSqlKind(SqlKind.ENTITY_INSERT);
1✔
2090

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

2095
                        // EntityInsert実行前イベント発行
2096
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
2097
                        if (eventListenerHolder.hasBeforeEntityInsertListener()) {
1✔
2098
                                var eventObj = new BeforeEntityInsertEvent(context, entity, entityType);
1✔
2099
                                for (var listener : eventListenerHolder.getBeforeEntityInsertListeners()) {
1✔
2100
                                        listener.accept(eventObj);
1✔
2101
                                }
1✔
2102
                        }
2103
                        handler.setInsertParams(context, entity);
1✔
2104

2105
                        var count = handler.doInsert(this, context, entity);
1✔
2106

2107
                        // EntityInsert実行後イベント発行
2108
                        if (eventListenerHolder.hasAfterEntityInsertListener()) {
1✔
2109
                                var eventObj = new AfterEntityInsertEvent(context, entity, entityType, count);
1✔
2110
                                for (var listener : eventListenerHolder.getAfterEntityInsertListeners()) {
1✔
2111
                                        listener.accept(eventObj);
1✔
2112
                                }
1✔
2113
                                count = eventObj.getCount();
1✔
2114
                        }
2115

2116
                        if (!autoGeneratedColumns.isEmpty()) {
1✔
2117
                                var ids = context.getGeneratedKeyValues();
1✔
2118
                                var idx = 0;
1✔
2119
                                for (var col : autoGeneratedColumns) {
1✔
2120
                                        setEntityIdValue(entity, ids[idx++], col);
1✔
2121
                                }
1✔
2122
                        }
2123
                        return count;
1✔
2124
                } catch (SQLException ex) {
1✔
2125
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_INSERT, ex);
1✔
2126
                }
2127
        }
2128

2129
        /**
2130
         * {@inheritDoc}
2131
         *
2132
         * @see jp.co.future.uroborosql.SqlAgent#insertAndReturn(java.lang.Object)
2133
         */
2134
        @Override
2135
        public <E> E insertAndReturn(final E entity) {
2136
                insert(entity);
1✔
2137
                return entity;
1✔
2138
        }
2139

2140
        /**
2141
         * {@inheritDoc}
2142
         *
2143
         * @see jp.co.future.uroborosql.SqlAgent#update(java.lang.Object)
2144
         */
2145
        @SuppressWarnings("unchecked")
2146
        @Override
2147
        public <E> int update(final E entity) {
2148
                if (entity instanceof Stream) {
1✔
2149
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
2150
                }
2151

2152
                @SuppressWarnings("rawtypes")
2153
                EntityHandler handler = this.getEntityHandler();
1✔
2154
                if (!handler.getEntityType().isInstance(entity)) {
1✔
2155
                        throw new IllegalArgumentException("Entity type not supported");
×
2156
                }
2157

2158
                try {
2159
                        var entityType = entity.getClass();
1✔
2160
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2161
                        var context = handler.createUpdateContext(this, metadata, entityType, true);
1✔
2162
                        context.setSqlKind(SqlKind.ENTITY_UPDATE);
1✔
2163

2164
                        // EntityUpdate実行前イベント発行
2165
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
2166
                        if (eventListenerHolder.hasBeforeEntityUpdateListener()) {
1✔
2167
                                var eventObj = new BeforeEntityUpdateEvent(context, entity, entityType);
1✔
2168
                                for (var listener : eventListenerHolder.getBeforeEntityUpdateListeners()) {
1✔
2169
                                        listener.accept(eventObj);
1✔
2170
                                }
1✔
2171
                        }
2172
                        handler.setUpdateParams(context, entity);
1✔
2173

2174
                        var count = handler.doUpdate(this, context, entity);
1✔
2175

2176
                        // EntityUpdate実行後イベント発行
2177
                        if (eventListenerHolder.hasAfterEntityUpdateListener()) {
1✔
2178
                                var eventObj = new AfterEntityUpdateEvent(context, entity, entityType, count);
1✔
2179
                                for (var listener : eventListenerHolder.getAfterEntityUpdateListeners()) {
1✔
2180
                                        listener.accept(eventObj);
1✔
2181
                                }
1✔
2182
                                count = eventObj.getCount();
1✔
2183
                        }
2184

2185
                        var updateCount = count;
1✔
2186
                        MappingUtils.getVersionMappingColumn(metadata.getSchema(), entityType).ifPresent(versionColumn -> {
1✔
2187
                                if (updateCount == 0) {
1✔
2188
                                        throw new OptimisticLockException(context);
1✔
2189
                                } else {
2190
                                        var columnMap = MappingUtils.getMappingColumnMap(metadata.getSchema(), entityType, SqlKind.NONE);
1✔
2191
                                        var keys = metadata.getColumns().stream()
1✔
2192
                                                        .filter(TableMetadata.Column::isKey)
1✔
2193
                                                        .sorted(Comparator.comparingInt(TableMetadata.Column::getKeySeq))
1✔
2194
                                                        .map(c -> columnMap.get(c.getCamelColumnName()).getValue(entity))
1✔
2195
                                                        .toArray();
1✔
2196

2197
                                        find(entityType, keys).ifPresent(e -> versionColumn.setValue(entity, versionColumn.getValue(e)));
1✔
2198
                                }
2199
                        });
1✔
2200
                        return updateCount;
1✔
2201
                } catch (SQLException ex) {
×
2202
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_UPDATE, ex);
×
2203
                }
2204
        }
2205

2206
        /**
2207
         * {@inheritDoc}
2208
         *
2209
         * @see jp.co.future.uroborosql.SqlAgent#updateAndReturn(java.lang.Object)
2210
         */
2211
        @Override
2212
        public <E> E updateAndReturn(final E entity) {
2213
                update(entity);
1✔
2214
                return entity;
1✔
2215
        }
2216

2217
        /**
2218
         * {@inheritDoc}
2219
         *
2220
         * @see jp.co.future.uroborosql.SqlAgent#update(java.lang.Class)
2221
         */
2222
        @Override
2223
        public <E> SqlEntityUpdate<E> update(final Class<? extends E> entityType) {
2224
                var handler = this.getEntityHandler();
1✔
2225
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2226
                        throw new IllegalArgumentException("Entity type not supported");
×
2227
                }
2228

2229
                try {
2230
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2231

2232
                        var context = handler.createUpdateContext(this, metadata, entityType, false);
1✔
2233
                        context.setSqlKind(SqlKind.ENTITY_UPDATE);
1✔
2234

2235
                        return new SqlEntityUpdateImpl<>(this, handler, metadata, context, entityType);
1✔
2236
                } catch (SQLException ex) {
×
2237
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_UPDATE, ex);
×
2238
                }
2239
        }
2240

2241
        /**
2242
         * {@inheritDoc}
2243
         *
2244
         * @see jp.co.future.uroborosql.SqlAgent#merge(java.lang.Object)
2245
         */
2246
        @Override
2247
        public <E> int merge(final E entity) {
2248
                mergeAndReturn(entity);
1✔
2249
                return 1;
1✔
2250
        }
2251

2252
        /**
2253
         * {@inheritDoc}
2254
         *
2255
         * @see jp.co.future.uroborosql.SqlAgent#mergeAndReturn(java.lang.Object)
2256
         */
2257
        @Override
2258
        public <E> E mergeAndReturn(final E entity) {
2259
                return doMergeAndReturn(entity, false);
1✔
2260
        }
2261

2262
        /**
2263
         * {@inheritDoc}
2264
         *
2265
         * @see jp.co.future.uroborosql.SqlAgent#mergeWithLocking(java.lang.Object)
2266
         */
2267
        @Override
2268
        public <E> int mergeWithLocking(final E entity) {
2269
                mergeWithLockingAndReturn(entity);
1✔
2270
                return 1;
1✔
2271
        }
2272

2273
        /**
2274
         * {@inheritDoc}
2275
         *
2276
         * @see jp.co.future.uroborosql.SqlAgent#mergeWithLockingAndReturn(java.lang.Object)
2277
         */
2278
        @Override
2279
        public <E> E mergeWithLockingAndReturn(final E entity) {
2280
                return doMergeAndReturn(entity, true);
1✔
2281
        }
2282

2283
        /**
2284
         * エンティティのMERGE(INSERTまたはUPDATE)を実行し、MERGEしたエンティティを返却する.<br>
2285
         * locking引数が<code>true</code>の場合、マージの最初に発行するEntityの検索で悲観ロック(forUpdateNoWait)を行う
2286
         *
2287
         * @param <E> エンティティ型
2288
         * @param entity エンティティ
2289
         * @param locking エンティティの悲観ロックを行うかどうか
2290
         * @return MERGEしたエンティティ
2291
         *
2292
         */
2293
        @SuppressWarnings("unchecked")
2294
        private <E> E doMergeAndReturn(final E entity, final boolean locking) {
2295
                if (entity instanceof Stream) {
1✔
2296
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
2297
                }
2298

2299
                var handler = this.getEntityHandler();
1✔
2300
                if (!handler.getEntityType().isInstance(entity)) {
1✔
2301
                        throw new IllegalArgumentException("Entity type not supported.");
×
2302
                }
2303

2304
                var type = entity.getClass();
1✔
2305
                try {
2306
                        var metadata = handler.getMetadata(this.transactionManager, type);
1✔
2307
                        var keyColumns = metadata.getKeyColumns();
1✔
2308

2309
                        if (keyColumns.isEmpty()) {
1✔
2310
                                throw new IllegalArgumentException("Entity has no keys.");
×
2311
                        }
2312

2313
                        var mappingColumns = MappingUtils.getMappingColumnMap(metadata.getSchema(), type, SqlKind.UPDATE);
1✔
2314
                        var query = (SqlEntityQuery<E>) query(type);
1✔
2315
                        for (var column : keyColumns) {
1✔
2316
                                var camelName = column.getCamelColumnName();
1✔
2317
                                query.equal(camelName, mappingColumns.get(camelName).getValue(entity));
1✔
2318
                        }
1✔
2319
                        if (locking) {
1✔
2320
                                query.forUpdateNoWait();
1✔
2321
                        }
2322
                        return query.first()
1✔
2323
                                        .map(findEntity -> {
1✔
2324
                                                for (var mappingColumn : mappingColumns.values()) {
1✔
2325
                                                        if (!mappingColumn.isId() && !mappingColumn.isVersion()) {
1✔
2326
                                                                var value = mappingColumn.getValue(entity);
1✔
2327
                                                                if (value != null) {
1✔
2328
                                                                        mappingColumn.setValue(findEntity, value);
1✔
2329
                                                                }
2330
                                                        }
2331
                                                }
1✔
2332
                                                return updateAndReturn(findEntity);
1✔
2333
                                        }).orElseGet(() -> insertAndReturn(entity));
1✔
2334
                } catch (SQLException ex) {
×
2335
                        throw new EntitySqlRuntimeException(SqlKind.MERGE, ex);
×
2336
                }
2337
        }
2338

2339
        /**
2340
         * {@inheritDoc}
2341
         *
2342
         * @see jp.co.future.uroborosql.SqlAgent#delete(java.lang.Object)
2343
         */
2344
        @Override
2345
        public <E> int delete(final E entity) {
2346
                if (entity instanceof Stream) {
1✔
2347
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
2348
                }
2349

2350
                var handler = this.getEntityHandler();
1✔
2351
                if (!handler.getEntityType().isInstance(entity)) {
1✔
2352
                        throw new IllegalArgumentException("Entity type not supported");
×
2353
                }
2354

2355
                try {
2356
                        var entityType = entity.getClass();
1✔
2357
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2358
                        var context = handler.createDeleteContext(this, metadata, entityType, true);
1✔
2359
                        context.setSqlKind(SqlKind.ENTITY_DELETE);
1✔
2360

2361
                        // EntityDelete実行前イベント発行
2362
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
2363
                        if (eventListenerHolder.hasBeforeEntityDeleteListener()) {
1✔
2364
                                var eventObj = new BeforeEntityDeleteEvent(context, entity, entityType);
1✔
2365
                                for (var listener : eventListenerHolder.getBeforeEntityDeleteListeners()) {
1✔
2366
                                        listener.accept(eventObj);
1✔
2367
                                }
1✔
2368
                        }
2369
                        handler.setDeleteParams(context, entity);
1✔
2370

2371
                        var count = handler.doDelete(this, context, entity);
1✔
2372

2373
                        // EntityDelete実行後イベント発行
2374
                        if (eventListenerHolder.hasAfterEntityDeleteListener()) {
1✔
2375
                                var eventObj = new AfterEntityDeleteEvent(context, entity, entityType, count);
1✔
2376
                                for (var listener : eventListenerHolder.getAfterEntityDeleteListeners()) {
1✔
2377
                                        listener.accept(eventObj);
1✔
2378
                                }
1✔
2379
                                count = eventObj.getCount();
1✔
2380
                        }
2381

2382
                        return count;
1✔
2383
                } catch (SQLException ex) {
×
2384
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_DELETE, ex);
×
2385
                }
2386
        }
2387

2388
        /**
2389
         * {@inheritDoc}
2390
         *
2391
         * @see jp.co.future.uroborosql.SqlAgent#deleteAndReturn(java.lang.Object)
2392
         */
2393
        @Override
2394
        public <E> E deleteAndReturn(final E entity) {
2395
                delete(entity);
1✔
2396
                return entity;
1✔
2397
        }
2398

2399
        /**
2400
         * {@inheritDoc}
2401
         *
2402
         * @see jp.co.future.uroborosql.SqlAgent#delete(java.lang.Class, java.lang.Object[])
2403
         */
2404
        @Override
2405
        public <E> int delete(final Class<? extends E> entityType, final Object... keys) {
2406
                var handler = this.getEntityHandler();
1✔
2407
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2408
                        throw new IllegalArgumentException("Entity type not supported");
×
2409
                }
2410

2411
                try {
2412
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2413
                        TableMetadata.Column keyColumn = null;
1✔
2414
                        var keyColumns = metadata.getKeyColumns();
1✔
2415
                        if (keyColumns.size() == 1) {
1✔
2416
                                keyColumn = keyColumns.get(0);
1✔
2417
                        } else if (keyColumns.isEmpty()) {
1✔
2418
                                keyColumn = metadata.getColumns().get(0);
1✔
2419
                        } else {
2420
                                throw new IllegalArgumentException("Entity has multiple keys");
1✔
2421
                        }
2422
                        return delete(entityType).in(keyColumn.getCamelColumnName(), keys).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#delete(java.lang.Class)
2432
         */
2433
        @Override
2434
        public <E> SqlEntityDelete<E> delete(final Class<? extends E> entityType) {
2435
                var handler = this.getEntityHandler();
1✔
2436
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2437
                        throw new IllegalArgumentException("Entity type not supported");
×
2438
                }
2439

2440
                try {
2441
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2442
                        var context = handler.createDeleteContext(this, metadata, entityType, false);
1✔
2443
                        context.setSqlKind(SqlKind.ENTITY_DELETE);
1✔
2444
                        return new SqlEntityDeleteImpl<>(this, handler, metadata, context, entityType);
1✔
2445
                } catch (SQLException ex) {
×
2446
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_DELETE, ex);
×
2447
                }
2448
        }
2449

2450
        /**
2451
         * {@inheritDoc}
2452
         *
2453
         * @see jp.co.future.uroborosql.SqlAgent#truncate(java.lang.Class)
2454
         */
2455
        @Override
2456
        public <E> SqlAgent truncate(final Class<? extends E> entityType) {
2457
                var handler = this.getEntityHandler();
1✔
2458
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2459
                        throw new IllegalArgumentException("Entity type not supported");
×
2460
                }
2461
                try {
2462
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2463
                        var context = this.context().setSql("truncate table " + metadata.getTableIdentifier());
1✔
2464
                        context.setSqlKind(SqlKind.TRUNCATE);
1✔
2465
                        update(context);
1✔
2466
                        return this;
1✔
2467
                } catch (SQLException ex) {
1✔
2468
                        throw new EntitySqlRuntimeException(SqlKind.TRUNCATE, ex);
1✔
2469
                }
2470
        }
2471

2472
        /**
2473
         * 自動採番カラムを取得する. 合わせて、{@link ExecutionContext#setGeneratedKeyColumns(String[])} に自動採番カラムを設定する.
2474
         * <pre>
2475
         * 自動採番カラムの条件
2476
         * 1. {@link MappingColumn#isId()} == true 、もしくは{@link TableMetadata.Column#isAutoincrement()} == true であること
2477
         * 2. 1.に加え、エンティティの対象フィールド型がprimitive型、もしくはフィールドの値が<code>null</code>であること
2478
         * </pre>
2479
         *
2480
         * @param <E> entity
2481
         * @param context ExecutionContext
2482
         * @param mappingColumns EntityのMappingColumn配列
2483
         * @param metadata TableMetadata
2484
         * @param entity Entity
2485
         * @return 自動採番カラム配列
2486
         */
2487
        private <E> List<MappingColumn> getAutoGeneratedColumns(final ExecutionContext context,
2488
                        final MappingColumn[] mappingColumns,
2489
                        final TableMetadata metadata,
2490
                        final E entity) {
2491
                var autoGeneratedColumns = new ArrayList<MappingColumn>();
1✔
2492
                var autoGeneratedColumnNames = new ArrayList<String>();
1✔
2493

2494
                if (mappingColumns != null && mappingColumns.length > 0) {
1✔
2495
                        for (var column : metadata.getColumns()) {
1✔
2496
                                if (column.isAutoincrement()) {
1✔
2497
                                        for (var mappingColumn : mappingColumns) {
1✔
2498
                                                if (mappingColumn.getCamelName().equals(column.getCamelColumnName())) {
1✔
2499
                                                        // primitive型か、エンティティの対象フィールドがnullとなっている場合自動生成カラムとする
2500
                                                        if (mappingColumn.getJavaType().getRawType().isPrimitive()
1✔
2501
                                                                        || mappingColumn.getValue(entity) == null) {
1✔
2502
                                                                autoGeneratedColumns.add(mappingColumn);
1✔
2503
                                                                autoGeneratedColumnNames.add(column.getColumnName());
1✔
2504
                                                        }
2505
                                                        break;
2506
                                                }
2507
                                        }
2508
                                }
2509
                        }
1✔
2510
                        for (var mappingColumn : mappingColumns) {
1✔
2511
                                if (!mappingColumn.isId() || autoGeneratedColumns.contains(mappingColumn)) {
1✔
2512
                                        continue;
1✔
2513
                                }
2514
                                for (var column : metadata.getColumns()) {
1✔
2515
                                        if (mappingColumn.getCamelName().equals(column.getCamelColumnName())) {
1✔
2516
                                                // primitive型か、エンティティの対象フィールドがnullとなっている場合自動生成カラムとする
2517
                                                if (mappingColumn.getJavaType().getRawType().isPrimitive()
1✔
2518
                                                                || mappingColumn.getValue(entity) == null) {
1✔
2519
                                                        autoGeneratedColumns.add(mappingColumn);
1✔
2520
                                                        autoGeneratedColumnNames.add(column.getColumnName());
1✔
2521
                                                }
2522
                                                break;
2523
                                        }
2524
                                }
1✔
2525
                        }
2526
                }
2527

2528
                if (!autoGeneratedColumnNames.isEmpty()) {
1✔
2529
                        context.setGeneratedKeyColumns(
1✔
2530
                                        autoGeneratedColumnNames.toArray(new String[autoGeneratedColumnNames.size()]));
1✔
2531
                }
2532
                return autoGeneratedColumns;
1✔
2533
        }
2534

2535
        /**
2536
         * entityにID値を設定する
2537
         * @param entity Entity
2538
         * @param id ID値
2539
         * @param column 設定する対象のカラム
2540
         */
2541
        private void setEntityIdValue(final Object entity, final Object id, final MappingColumn column) {
2542
                var rawType = column.getJavaType().getRawType();
1✔
2543
                try {
2544
                        if (int.class.equals(rawType) || Integer.class.equals(rawType)) {
1✔
2545
                                if (id instanceof Number) {
1✔
2546
                                        column.setValue(entity, ((Number) id).intValue());
1✔
2547
                                } else {
2548
                                        throw new IllegalArgumentException();
1✔
2549
                                }
2550
                        } else if (long.class.equals(rawType) || Long.class.equals(rawType)) {
1✔
2551
                                if (id instanceof Number) {
1✔
2552
                                        column.setValue(entity, ((Number) id).longValue());
1✔
2553
                                } else {
2554
                                        throw new IllegalArgumentException();
1✔
2555
                                }
2556
                        } else if (BigInteger.class.equals(rawType)) {
1✔
2557
                                if (id instanceof BigInteger) {
1✔
2558
                                        column.setValue(entity, id);
×
2559
                                } else if (id instanceof BigDecimal) {
1✔
2560
                                        column.setValue(entity, ((BigDecimal) id).toBigInteger());
1✔
2561
                                } else if (id instanceof Number) {
1✔
2562
                                        column.setValue(entity, new BigInteger(((Number) id).toString()));
1✔
2563
                                } else {
2564
                                        throw new IllegalArgumentException();
1✔
2565
                                }
2566
                        } else if (BigDecimal.class.equals(rawType)) {
1✔
2567
                                if (id instanceof BigDecimal) {
1✔
2568
                                        column.setValue(entity, id);
1✔
2569
                                } else if (id instanceof BigInteger) {
1✔
2570
                                        column.setValue(entity, new BigDecimal((BigInteger) id));
×
2571
                                } else if (id instanceof Number) {
1✔
2572
                                        column.setValue(entity, new BigDecimal(((Number) id).toString()));
1✔
2573
                                } else {
2574
                                        throw new IllegalArgumentException();
1✔
2575
                                }
2576
                        } else if (String.class.equals(rawType)) {
1✔
2577
                                if (id instanceof String) {
1✔
2578
                                        column.setValue(entity, id);
×
2579
                                } else if (id instanceof BigDecimal) {
1✔
2580
                                        column.setValue(entity, ((BigDecimal) id).toPlainString());
1✔
2581
                                } else if (id instanceof Number) {
1✔
2582
                                        column.setValue(entity, ((Number) id).toString());
1✔
2583
                                } else {
2584
                                        column.setValue(entity, id.toString());
1✔
2585
                                }
2586
                        } else {
2587
                                try {
2588
                                        column.setValue(entity, id);
1✔
2589
                                } catch (UroborosqlRuntimeException ex) {
1✔
2590
                                        throw new UroborosqlRuntimeException(
1✔
2591
                                                        "Column is not correct as ID type. column=" + column.getCamelName() + ", type=" + rawType);
1✔
2592
                                }
1✔
2593
                        }
2594
                } catch (IllegalArgumentException ex) {
1✔
2595
                        throw new UroborosqlRuntimeException(
1✔
2596
                                        "Column type and ID type do not match. column=" + column.getCamelName() + ", type="
1✔
2597
                                                        + rawType
2598
                                                        + ", id=" + id + ", type=" + id.getClass().getSimpleName());
1✔
2599
                }
1✔
2600
        }
1✔
2601

2602
        /**
2603
         *
2604
         * {@inheritDoc}
2605
         *
2606
         * @see jp.co.future.uroborosql.SqlAgent#getSqlConfig()
2607
         */
2608
        @Override
2609
        public SqlConfig getSqlConfig() {
2610
                return this.sqlConfig;
1✔
2611
        }
2612

2613
        /**
2614
         *
2615
         * {@inheritDoc}
2616
         *
2617
         * @see jp.co.future.uroborosql.SqlAgent#context()
2618
         */
2619
        @Override
2620
        public ExecutionContext context() {
2621
                return sqlConfig.context();
1✔
2622
        }
2623

2624
        /**
2625
         * {@inheritDoc}
2626
         *
2627
         * @see jp.co.future.uroborosql.SqlAgent#getFetchSize()
2628
         */
2629
        @Override
2630
        public int getFetchSize() {
2631
                return fetchSize;
1✔
2632
        }
2633

2634
        /**
2635
         * {@inheritDoc}
2636
         *
2637
         * @see jp.co.future.uroborosql.SqlAgent#setFetchSize(int)
2638
         */
2639
        @Override
2640
        public SqlAgent setFetchSize(final int fetchSize) {
2641
                this.fetchSize = fetchSize;
1✔
2642
                return this;
1✔
2643
        }
2644

2645
        /**
2646
         * {@inheritDoc}
2647
         *
2648
         * @see jp.co.future.uroborosql.SqlAgent#getQueryTimeout()
2649
         */
2650
        @Override
2651
        public int getQueryTimeout() {
2652
                return queryTimeout;
1✔
2653
        }
2654

2655
        /**
2656
         * {@inheritDoc}
2657
         *
2658
         * @see jp.co.future.uroborosql.SqlAgent#setQueryTimeout(int)
2659
         */
2660
        @Override
2661
        public SqlAgent setQueryTimeout(final int queryTimeout) {
2662
                this.queryTimeout = queryTimeout;
1✔
2663
                return this;
1✔
2664
        }
2665

2666
        /**
2667
         * SQL実行をリトライするSQLエラーコードのリスト を取得します
2668
         *
2669
         * @return SQL実行をリトライするSQLエラーコードのリスト
2670
         */
2671
        public List<String> getSqlRetryCodes() {
2672
                return sqlRetryCodes;
1✔
2673
        }
2674

2675
        /**
2676
         * SQL実行をリトライするSQLエラーコードのリスト を設定します
2677
         *
2678
         * @param sqlRetryCodes SQL実行をリトライするSQLエラーコードのリスト
2679
         * @return SqlAgent
2680
         */
2681
        public SqlAgent setSqlRetryCodes(final List<String> sqlRetryCodes) {
2682
                this.sqlRetryCodes = sqlRetryCodes;
1✔
2683
                return this;
1✔
2684
        }
2685

2686
        /**
2687
         * 最大リトライ回数 を取得します
2688
         *
2689
         * @return 最大リトライ回数
2690
         */
2691
        public int getMaxRetryCount() {
2692
                return maxRetryCount;
1✔
2693
        }
2694

2695
        /**
2696
         * 最大リトライ回数 を設定します
2697
         *
2698
         * @param maxRetryCount 最大リトライ回数
2699
         * @return SqlAgent
2700
         */
2701
        public SqlAgent setMaxRetryCount(final int maxRetryCount) {
2702
                this.maxRetryCount = maxRetryCount;
1✔
2703
                return this;
1✔
2704
        }
2705

2706
        /**
2707
         * リトライタイムアウト時間(ms) を取得します
2708
         *
2709
         * @return リトライタイムアウト時間(ms)
2710
         */
2711
        public int getRetryWaitTime() {
2712
                return retryWaitTime;
1✔
2713
        }
2714

2715
        /**
2716
         * リトライタイムアウト時間(ms) を設定します
2717
         *
2718
         * @param retryWaitTime リトライタイムアウト時間(ms)
2719
         * @return SqlAgent
2720
         */
2721
        public SqlAgent setRetryWaitTime(final int retryWaitTime) {
2722
                this.retryWaitTime = retryWaitTime;
1✔
2723
                return this;
1✔
2724
        }
2725

2726
        /**
2727
         *
2728
         * {@inheritDoc}
2729
         *
2730
         * @see jp.co.future.uroborosql.SqlAgent#getMapKeyCaseFormat()
2731
         */
2732
        @Override
2733
        public CaseFormat getMapKeyCaseFormat() {
2734
                return mapKeyCaseFormat;
1✔
2735
        }
2736

2737
        /**
2738
         *
2739
         * {@inheritDoc}
2740
         *
2741
         * @see jp.co.future.uroborosql.SqlAgent#setMapKeyCaseFormat(jp.co.future.uroborosql.utils.CaseFormat)
2742
         */
2743
        @Override
2744
        public SqlAgent setMapKeyCaseFormat(final CaseFormat mapKeyCaseFormat) {
2745
                this.mapKeyCaseFormat = mapKeyCaseFormat;
1✔
2746
                return this;
1✔
2747
        }
2748

2749
        /**
2750
         *
2751
         * {@inheritDoc}
2752
         *
2753
         * @see jp.co.future.uroborosql.SqlAgent#getInsertsType()
2754
         */
2755
        @Override
2756
        public InsertsType getInsertsType() {
2757
                return this.insertsType;
1✔
2758
        }
2759

2760
        /**
2761
         *
2762
         * {@inheritDoc}
2763
         *
2764
         * @see jp.co.future.uroborosql.SqlAgent#setInsertsType(jp.co.future.uroborosql.enums.InsertsType)
2765
         */
2766
        @Override
2767
        public SqlAgent setInsertsType(final InsertsType defaultInsertsType) {
2768
                this.insertsType = defaultInsertsType;
1✔
2769
                return this;
1✔
2770
        }
2771

2772
        /**
2773
         * SQLリソース管理クラスを取得します。
2774
         *
2775
         * @return SQLリソース管理クラス
2776
         */
2777
        private SqlResourceManager getSqlResourceManager() {
2778
                return sqlConfig.getSqlResourceManager();
1✔
2779
        }
2780

2781
        /**
2782
         * ORM処理クラス を取得します。
2783
         *
2784
         * @return ORM処理クラス
2785
         */
2786
        @SuppressWarnings("unchecked")
2787
        private <E> EntityHandler<E> getEntityHandler() {
2788
                return (EntityHandler<E>) sqlConfig.getEntityHandler();
1✔
2789
        }
2790

2791
        /**
2792
         * Dialect を取得します。
2793
         *
2794
         * @return Dialect
2795
         */
2796
        private Dialect getDialect() {
2797
                return sqlConfig.getDialect();
1✔
2798
        }
2799

2800
        /**
2801
         * ステートメント初期化。
2802
         *
2803
         * @param executionContext ExecutionContext
2804
         * @return PreparedStatement
2805
         * @throws SQLException SQL例外
2806
         */
2807
        private PreparedStatement getPreparedStatement(final ExecutionContext executionContext) throws SQLException {
2808
                var stmt = ((LocalTransactionManager) transactionManager).getPreparedStatement(executionContext);
1✔
2809
                // プロパティ設定
2810
                applyProperties(stmt);
1✔
2811
                return stmt;
1✔
2812
        }
2813

2814
        /**
2815
         * Callableステートメント初期化
2816
         *
2817
         * @param executionContext ExecutionContext
2818
         * @return CallableStatement
2819
         * @throws SQLException SQL例外
2820
         */
2821
        private CallableStatement getCallableStatement(final ExecutionContext executionContext) throws SQLException {
2822
                var stmt = ((LocalTransactionManager) transactionManager).getCallableStatement(executionContext);
1✔
2823
                // プロパティ設定
2824
                applyProperties(stmt);
1✔
2825
                return stmt;
1✔
2826
        }
2827

2828
        /**
2829
         * フェッチサイズとクエリタイムアウトをPreparedStatementに設定する
2830
         *
2831
         * @param preparedStatement PreparedStatement
2832
         * @throws SQLException SQL例外
2833
         */
2834
        private void applyProperties(final PreparedStatement preparedStatement) throws SQLException {
2835
                // フェッチサイズ指定
2836
                if (getFetchSize() >= 0 && !(preparedStatement instanceof CallableStatement)) {
1✔
2837
                        preparedStatement.setFetchSize(getFetchSize());
1✔
2838
                }
2839

2840
                // クエリタイムアウト指定
2841
                if (getQueryTimeout() >= 0) {
1✔
2842
                        preparedStatement.setQueryTimeout(getQueryTimeout());
1✔
2843
                }
2844
        }
1✔
2845

2846
        /**
2847
         * 経過時間を計算し、HH:mm:ss.SSSSSSにフォーマットする.
2848
         *
2849
         * @param start 開始時間
2850
         * @param end 終了時間
2851
         * @return フォーマットした経過時間
2852
         */
2853
        private static String formatElapsedTime(final Instant start, final Instant end) {
2854
                return ELAPSED_TIME_FORMAT.format(LocalTime.MIDNIGHT.plus(Duration.between(start != null ? start : end, end)));
×
2855
        }
2856

2857
        /**
2858
         * ResultSetをStreamで扱うためのSpliterator
2859
         *
2860
         * @author H.Sugimoto
2861
         *
2862
         * @param <T> ResultSetの1行を変換した型
2863
         */
2864
        private static final class ResultSetSpliterator<T> extends Spliterators.AbstractSpliterator<T>
2865
                        implements ServiceLoggingSupport {
2866
                private final ResultSetConverter<T> converter;
2867
                private final ResultSet rs;
2868
                private boolean finished = false;
1✔
2869

2870
                private ResultSetSpliterator(final ResultSet rs, final ResultSetConverter<T> converter) {
2871
                        super(Long.MAX_VALUE, Spliterator.ORDERED);
1✔
2872
                        this.rs = rs;
1✔
2873
                        this.converter = converter;
1✔
2874
                }
1✔
2875

2876
                @Override
2877
                public boolean tryAdvance(final Consumer<? super T> action) {
2878
                        try {
2879
                                if (finished || !rs.next()) {
1✔
2880
                                        if (!rs.isClosed()) {
1✔
2881
                                                rs.close();
1✔
2882
                                        }
2883
                                        finished = true;
1✔
2884
                                        return false;
1✔
2885
                                }
2886
                                action.accept(converter.createRecord(rs));
1✔
2887
                                return true;
1✔
2888
                        } catch (RuntimeException | Error ex) {
1✔
2889
                                try {
2890
                                        if (rs != null && !rs.isClosed()) {
1✔
2891
                                                rs.close();
1✔
2892
                                        }
2893
                                } catch (SQLException ex2) {
×
2894
                                        errorWith(LOG)
×
2895
                                                        .setMessage(ex2.getMessage())
×
2896
                                                        .setCause(ex2)
×
2897
                                                        .log();
×
2898
                                }
1✔
2899
                                throw ex;
1✔
2900
                        } catch (SQLException ex) {
×
2901
                                try {
2902
                                        if (rs != null && !rs.isClosed()) {
×
2903
                                                rs.close();
×
2904
                                        }
2905
                                } catch (SQLException ex2) {
×
2906
                                        errorWith(LOG)
×
2907
                                                        .setMessage(ex2.getMessage())
×
2908
                                                        .setCause(ex2)
×
2909
                                                        .log();
×
2910
                                }
×
2911
                                throw new UroborosqlSQLException(ex);
×
2912
                        }
2913
                }
2914
        }
2915

2916
        /**
2917
         * ResultSetのラッパークラス。ResultSetのクローズに合わせてStatementもクローズする。
2918
         *
2919
         * @author H.Sugimoto
2920
         * @version 0.5.0
2921
         */
2922
        private static class InnerResultSet extends AbstractResultSetWrapper {
2923
                /** 同期してクローズするStatement */
2924
                private final Statement stmt;
2925

2926
                /**
2927
                 * コンストラクタ
2928
                 *
2929
                 * @param wrapped 元となるResultSet
2930
                 * @param stmt Statement
2931
                 */
2932
                InnerResultSet(final ResultSet wrapped, final Statement stmt) {
2933
                        super(wrapped);
1✔
2934
                        this.stmt = stmt;
1✔
2935
                }
1✔
2936

2937
                /**
2938
                 * {@inheritDoc}
2939
                 *
2940
                 * @see jp.co.future.uroborosql.AbstractResultSetWrapper#close()
2941
                 */
2942
                @Override
2943
                public void close() throws SQLException {
2944
                        try {
2945
                                super.close();
1✔
2946
                        } finally {
2947
                                try {
2948
                                        if (stmt != null && !stmt.isClosed()) {
1✔
2949
                                                stmt.close();
1✔
2950
                                        }
2951
                                } catch (SQLException ex) {
×
2952
                                        // do nothing
2953
                                }
1✔
2954
                        }
2955
                }
1✔
2956
        }
2957
}
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