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

future-architect / uroborosql / #837

05 May 2025 05:06AM UTC coverage: 89.877% (-0.04%) from 89.914%
#837

Pull #350

HidekiSugimoto189
fix review
Pull Request #350: Add multi sqlfile bulk update method.

15 of 16 new or added lines in 3 files covered. (93.75%)

5 existing lines in 1 file now uncovered.

8701 of 9681 relevant lines covered (89.88%)

0.9 hits per line

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

89.05
/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#updateChained(java.lang.String[])
531
         */
532
        @Override
533
        public SqlUpdate updateChained(final String... sqlNames) {
534
                if (sqlNames == null || sqlNames.length == 0) {
1✔
535
                        throw new IllegalArgumentException("sqlNames is required.");
1✔
536
                }
537
                if (sqlNames.length == 1) {
1✔
538
                        warnWith(LOG)
1✔
539
                                        .setMessage("If sqlNames is single, use update method instead of updateChained.")
1✔
540
                                        .log();
1✔
541
                        return update(sqlNames[0]);
1✔
542
                }
543
                if (!getDialect().supportsUpdateChained()) {
1✔
NEW
544
                        throw new UroborosqlRuntimeException(getDialect().getDatabaseName() + " does not support updateChained.");
×
545
                }
546
                var sqls = Arrays.stream(sqlNames)
1✔
547
                                .map(sqlName -> getSqlResourceManager().getSql(sqlName))
1✔
548
                                .collect(Collectors.joining(";" + System.lineSeparator()));
1✔
549

550
                var sqlName = String.join(",", sqlNames);
1✔
551

552
                return new SqlUpdateImpl(this, context().setSqlName(sqlName).setSql(sqls));
1✔
553
        }
554

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

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

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

591
        /**
592
         * {@inheritDoc}
593
         *
594
         * @see jp.co.future.uroborosql.SqlAgent#proc(java.lang.String)
595
         */
596
        @Override
597
        public Procedure proc(final String sqlName) {
598
                if ("".equals(sqlName)) {
1✔
599
                        throw new IllegalArgumentException("sqlName is required.");
1✔
600
                }
601
                return new ProcedureImpl(this, context().setSqlName(sqlName));
1✔
602
        }
603

604
        /**
605
         * {@inheritDoc}
606
         *
607
         * @see jp.co.future.uroborosql.SqlAgent#proc(java.util.function.Supplier)
608
         */
609
        @Override
610
        public Procedure proc(final Supplier<String> supplier) {
611
                return this.proc(supplier.get());
1✔
612
        }
613

614
        /**
615
         * {@inheritDoc}
616
         *
617
         * @see jp.co.future.uroborosql.SqlAgent#procWith(java.lang.String)
618
         */
619
        @Override
620
        public Procedure procWith(final String sql) {
621
                if (sql == null || "".equals(sql)) {
1✔
622
                        throw new IllegalArgumentException("sql is required.");
1✔
623
                }
624
                return new ProcedureImpl(this, context().setSql(sql));
1✔
625
        }
626

627
        /**
628
         * {@inheritDoc}
629
         *
630
         * @see jp.co.future.uroborosql.SqlAgent#query(java.lang.Class)
631
         */
632
        @Override
633
        public <E> SqlEntityQuery<E> query(final Class<? extends E> entityType) {
634
                var handler = this.getEntityHandler();
1✔
635
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
636
                        throw new IllegalArgumentException("Entity type not supported");
×
637
                }
638
                try {
639
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
640
                        var context = handler.createSelectContext(this, metadata, entityType, false);
1✔
641
                        context.setSqlKind(SqlKind.ENTITY_SELECT);
1✔
642
                        return new SqlEntityQueryImpl<>(this, handler, metadata, context, entityType);
1✔
643
                } catch (SQLException ex) {
×
644
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_SELECT, ex);
×
645
                }
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, jp.co.future.uroborosql.enums.InsertsType)
652
         */
653
        @Override
654
        public <E> int inserts(final Class<E> entityType,
655
                        final Stream<E> entities,
656
                        final InsertsCondition<? super E> condition,
657
                        final InsertsType insertsType) {
658
                if (insertsType == InsertsType.BULK && getDialect().supportsBulkInsert()) {
1✔
659
                        return bulkInsert(entityType, entities, condition, null);
1✔
660
                } else {
661
                        return batchInsert(entityType, entities, condition, null);
1✔
662
                }
663
        }
664

665
        /**
666
         * {@inheritDoc}
667
         *
668
         * @see jp.co.future.uroborosql.SqlAgent#inserts(java.lang.Class, java.util.stream.Stream)
669
         */
670
        @Override
671
        public <E> int inserts(final Class<E> entityType,
672
                        final Stream<E> entities) {
673
                return inserts(entityType, entities, getInsertsCondition(getInsertsType()));
1✔
674
        }
675

676
        /**
677
         * {@inheritDoc}
678
         *
679
         * @see jp.co.future.uroborosql.SqlAgent#inserts(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition)
680
         */
681
        @Override
682
        public <E> int inserts(final Class<E> entityType,
683
                        final Stream<E> entities,
684
                        final InsertsCondition<? super E> condition) {
685
                return inserts(entityType, entities, condition, getInsertsType());
1✔
686
        }
687

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

700
        /**
701
         * {@inheritDoc}
702
         *
703
         * @see jp.co.future.uroborosql.SqlAgent#inserts(java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition, jp.co.future.uroborosql.enums.InsertsType)
704
         */
705
        @Override
706
        public <E> int inserts(final Stream<E> entities,
707
                        final InsertsCondition<? super E> condition,
708
                        final InsertsType insertsType) {
709
                var iterator = entities.iterator();
1✔
710
                if (!iterator.hasNext()) {
1✔
711
                        return 0;
1✔
712
                }
713
                var firstEntity = iterator.next();
1✔
714
                @SuppressWarnings("unchecked")
715
                var type = (Class<E>) firstEntity.getClass();
1✔
716
                var stream = Stream.concat(Stream.of(firstEntity),
1✔
717
                                StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.NONNULL), false));
1✔
718
                return inserts(type, stream, condition, insertsType);
1✔
719
        }
720

721
        /**
722
         * {@inheritDoc}
723
         *
724
         * @see jp.co.future.uroborosql.SqlAgent#inserts(java.util.stream.Stream)
725
         */
726
        @Override
727
        public <E> int inserts(final Stream<E> entities) {
728
                return inserts(entities, getInsertsCondition(getInsertsType()));
1✔
729
        }
730

731
        /**
732
         * {@inheritDoc}
733
         *
734
         * @see jp.co.future.uroborosql.SqlAgent#inserts(java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition)
735
         */
736
        @Override
737
        public <E> int inserts(final Stream<E> entities,
738
                        final InsertsCondition<? super E> condition) {
739
                return inserts(entities, condition, getInsertsType());
1✔
740
        }
741

742
        /**
743
         * {@inheritDoc}
744
         *
745
         * @see jp.co.future.uroborosql.SqlAgent#inserts(java.util.stream.Stream, jp.co.future.uroborosql.enums.InsertsType)
746
         */
747
        @Override
748
        public <E> int inserts(final Stream<E> entities,
749
                        final InsertsType insertsType) {
750
                return inserts(entities, getInsertsCondition(insertsType), insertsType);
1✔
751
        }
752

753
        /**
754
         * {@inheritDoc}
755
         *
756
         * @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)
757
         */
758
        @Override
759
        public <E> Stream<E> insertsAndReturn(final Class<E> entityType,
760
                        final Stream<E> entities,
761
                        final InsertsCondition<? super E> condition,
762
                        final InsertsType insertsType) {
763
                var insertedEntities = new ArrayList<E>();
1✔
764
                if (insertsType == InsertsType.BULK && getDialect().supportsBulkInsert()) {
1✔
765
                        bulkInsert(entityType, entities, condition, insertedEntities);
1✔
766
                } else {
767
                        batchInsert(entityType, entities, condition, insertedEntities);
1✔
768
                }
769
                return insertedEntities.stream();
1✔
770
        }
771

772
        /**
773
         * {@inheritDoc}
774
         *
775
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition)
776
         */
777
        @Override
778
        public <E> Stream<E> insertsAndReturn(final Class<E> entityType,
779
                        final Stream<E> entities,
780
                        final InsertsCondition<? super E> condition) {
781
                return insertsAndReturn(entityType, entities, condition, getInsertsType());
1✔
782
        }
783

784
        /**
785
         * {@inheritDoc}
786
         *
787
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.lang.Class, java.util.stream.Stream)
788
         */
789
        @Override
790
        public <E> Stream<E> insertsAndReturn(final Class<E> entityType,
791
                        final Stream<E> entities) {
792
                return insertsAndReturn(entityType, entities, getInsertsCondition(getInsertsType()));
1✔
793
        }
794

795
        /**
796
         * {@inheritDoc}
797
         *
798
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.enums.InsertsType)
799
         */
800
        @Override
801
        public <E> Stream<E> insertsAndReturn(final Class<E> entityType,
802
                        final Stream<E> entities,
803
                        final InsertsType insertsType) {
804
                return insertsAndReturn(entityType, entities, getInsertsCondition(insertsType), insertsType);
1✔
805
        }
806

807
        /**
808
         * {@inheritDoc}
809
         *
810
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition, jp.co.future.uroborosql.enums.InsertsType)
811
         */
812
        @Override
813
        public <E> Stream<E> insertsAndReturn(final Stream<E> entities,
814
                        final InsertsCondition<? super E> condition,
815
                        final InsertsType insertsType) {
816
                var iterator = entities.iterator();
1✔
817
                if (!iterator.hasNext()) {
1✔
818
                        return Stream.empty();
1✔
819
                }
820
                var firstEntity = iterator.next();
1✔
821
                @SuppressWarnings("unchecked")
822
                var type = (Class<E>) firstEntity.getClass();
1✔
823
                var stream = Stream.concat(Stream.of(firstEntity),
1✔
824
                                StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.NONNULL), false));
1✔
825
                return insertsAndReturn(type, stream, condition, insertsType);
1✔
826
        }
827

828
        /**
829
         * {@inheritDoc}
830
         *
831
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition)
832
         */
833
        @Override
834
        public <E> Stream<E> insertsAndReturn(final Stream<E> entities,
835
                        final InsertsCondition<? super E> condition) {
836
                return insertsAndReturn(entities, condition, getInsertsType());
1✔
837
        }
838

839
        /**
840
         * {@inheritDoc}
841
         *
842
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.util.stream.Stream)
843
         */
844
        @Override
845
        public <E> Stream<E> insertsAndReturn(final Stream<E> entities) {
846
                return insertsAndReturn(entities, getInsertsCondition(getInsertsType()));
1✔
847
        }
848

849
        /**
850
         * {@inheritDoc}
851
         *
852
         * @see jp.co.future.uroborosql.SqlAgent#insertsAndReturn(java.util.stream.Stream, jp.co.future.uroborosql.enums.InsertsType)
853
         */
854
        @Override
855
        public <E> Stream<E> insertsAndReturn(final Stream<E> entities,
856
                        final InsertsType insertsType) {
857
                return insertsAndReturn(entities, getInsertsCondition(insertsType), insertsType);
1✔
858
        }
859

860
        /**
861
         * {@inheritDoc}
862
         *
863
         * @see jp.co.future.uroborosql.SqlAgent#updates(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.UpdatesCondition)
864
         */
865
        @Override
866
        public <E> int updates(final Class<E> entityType,
867
                        final Stream<E> entities,
868
                        final UpdatesCondition<? super E> condition) {
869
                return batchUpdate(entityType, entities, condition, null);
1✔
870
        }
871

872
        /**
873
         * {@inheritDoc}
874
         *
875
         * @see jp.co.future.uroborosql.SqlAgent#updatesAndReturn(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.UpdatesCondition)
876
         */
877
        @Override
878
        public <E> Stream<E> updatesAndReturn(final Class<E> entityType,
879
                        final Stream<E> entities,
880
                        final UpdatesCondition<? super E> condition) {
881
                var updatedEntities = new ArrayList<E>();
1✔
882
                batchUpdate(entityType, entities, condition, updatedEntities);
1✔
883
                return updatedEntities.stream();
1✔
884
        }
885

886
        /**
887
         * {@inheritDoc}
888
         *
889
         * @see jp.co.future.uroborosql.SqlAgent#updates(java.lang.Class, java.util.stream.Stream)
890
         */
891
        @Override
892
        public <E> int updates(final Class<E> entityType,
893
                        final Stream<E> entities) {
894
                return updates(entityType, entities, DEFAULT_UPDATES_WHEN_CONDITION);
1✔
895
        }
896

897
        /**
898
         * {@inheritDoc}
899
         *
900
         * @see jp.co.future.uroborosql.SqlAgent#updatesAndReturn(java.lang.Class, java.util.stream.Stream)
901
         */
902
        @Override
903
        public <E> Stream<E> updatesAndReturn(final Class<E> entityType,
904
                        final Stream<E> entities) {
905
                return updatesAndReturn(entityType, entities, DEFAULT_UPDATES_WHEN_CONDITION);
1✔
906
        }
907

908
        /**
909
         * {@inheritDoc}
910
         *
911
         * @see jp.co.future.uroborosql.SqlAgent#updates(java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.UpdatesCondition)
912
         */
913
        @Override
914
        public <E> int updates(final Stream<E> entities,
915
                        final UpdatesCondition<? super E> condition) {
916
                var iterator = entities.iterator();
1✔
917
                if (!iterator.hasNext()) {
1✔
918
                        return 0;
×
919
                }
920
                var firstEntity = iterator.next();
1✔
921
                @SuppressWarnings("unchecked")
922
                var type = (Class<E>) firstEntity.getClass();
1✔
923
                var stream = Stream.concat(Stream.of(firstEntity),
1✔
924
                                StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.NONNULL), false));
1✔
925
                return updates(type, stream, condition);
1✔
926
        }
927

928
        /**
929
         * {@inheritDoc}
930
         *
931
         * @see jp.co.future.uroborosql.SqlAgent#updatesAndReturn(java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.UpdatesCondition)
932
         */
933
        @Override
934
        public <E> Stream<E> updatesAndReturn(final Stream<E> entities,
935
                        final UpdatesCondition<? super E> condition) {
936
                var iterator = entities.iterator();
1✔
937
                if (!iterator.hasNext()) {
1✔
938
                        return Stream.empty();
×
939
                }
940
                var firstEntity = iterator.next();
1✔
941
                @SuppressWarnings("unchecked")
942
                var type = (Class<E>) firstEntity.getClass();
1✔
943
                var stream = Stream.concat(Stream.of(firstEntity),
1✔
944
                                StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.NONNULL), false));
1✔
945
                return updatesAndReturn(type, stream, condition);
1✔
946
        }
947

948
        /**
949
         * {@inheritDoc}
950
         *
951
         * @see jp.co.future.uroborosql.SqlAgent#updates(java.util.stream.Stream)
952
         */
953
        @Override
954
        public <E> int updates(final Stream<E> entities) {
955
                return updates(entities, DEFAULT_UPDATES_WHEN_CONDITION);
1✔
956
        }
957

958
        /**
959
         * {@inheritDoc}
960
         *
961
         * @see jp.co.future.uroborosql.SqlAgent#updatesAndReturn(java.util.stream.Stream)
962
         */
963
        @Override
964
        public <E> Stream<E> updatesAndReturn(final Stream<E> entities) {
965
                return updatesAndReturn(entities, DEFAULT_UPDATES_WHEN_CONDITION);
1✔
966
        }
967

968
        private <E> InsertsCondition<? super E> getInsertsCondition(final InsertsType insertsType) {
969
                return InsertsType.BATCH.equals(insertsType)
1✔
970
                                ? DEFAULT_BATCH_INSERTS_WHEN_CONDITION
1✔
971
                                : DEFAULT_BULK_INSERTS_WHEN_CONDITION;
1✔
972
        }
973

974
        /**
975
         * 複数エンティティのBULK INSERTを実行
976
         *
977
         * @param <E> エンティティの型
978
         * @param entityType エンティティの型
979
         * @param entities エンティティ
980
         * @param condition 一括INSERT用のフレームの判定条件
981
         * @param insertedEntities INSERTしたEntityのList. <code>null</code>の場合は格納されない
982
         * @return SQL実行結果
983
         */
984
        @SuppressWarnings("deprecation")
985
        private <E> int batchInsert(final Class<E> entityType, final Stream<E> entities,
986
                        final InsertsCondition<? super E> condition, final List<E> insertedEntities) {
987
                EntityHandler<E> handler = this.getEntityHandler();
1✔
988
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
989
                        throw new IllegalArgumentException("Entity type not supported");
1✔
990
                }
991

992
                try {
993
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
994
                        var context = handler.createBatchInsertContext(this, metadata, entityType);
1✔
995
                        context.setSqlKind(SqlKind.ENTITY_BATCH_INSERT);
1✔
996

997
                        var count = 0;
1✔
998
                        var entityList = new ArrayList<E>();
1✔
999
                        var isFirst = true;
1✔
1000
                        List<MappingColumn> autoGeneratedColumns = List.of();
1✔
1001
                        Map<String, Object> nonNullObjectIdFlags = null;
1✔
1002
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
1003
                        var hasBeforeEntityBatchInsertListener = eventListenerHolder.hasBeforeEntityBatchInsertListener();
1✔
1004
                        var eventObj = hasBeforeEntityBatchInsertListener
1✔
1005
                                        ? new BeforeEntityBatchInsertEvent(context, null, entityType)
1✔
1006
                                        : null;
1✔
1007
                        for (var iterator = entities.iterator(); iterator.hasNext();) {
1✔
1008
                                var entity = iterator.next();
1✔
1009

1010
                                if (!entityType.isInstance(entity)) {
1✔
1011
                                        throw new IllegalArgumentException("Entity types do not match");
1✔
1012
                                }
1013

1014
                                if (isFirst) {
1✔
1015
                                        isFirst = false;
1✔
1016
                                        var mappingColumns = MappingUtils.getMappingColumns(metadata.getSchema(), entityType);
1✔
1017
                                        autoGeneratedColumns = getAutoGeneratedColumns(context, mappingColumns, metadata, entity);
1✔
1018

1019
                                        // SQLのID項目IF分岐判定をtrueにするために値が設定されているID項目を保持しておく
1020
                                        var excludeColumns = autoGeneratedColumns;
1✔
1021
                                        nonNullObjectIdFlags = Arrays.stream(mappingColumns)
1✔
1022
                                                        .filter(col -> !excludeColumns.contains(col)
1✔
1023
                                                                        && !col.getJavaType().getRawType().isPrimitive()
1✔
1024
                                                                        && col.getValue(entity) != null)
1✔
1025
                                                        .collect(Collectors.toMap(MappingColumn::getCamelName, col -> true));
1✔
1026
                                }
1027

1028
                                entityList.add(entity);
1✔
1029
                                if (insertedEntities != null) {
1✔
1030
                                        insertedEntities.add(entity);
1✔
1031
                                }
1032

1033
                                // EntityBatchInsert実行前イベント発行
1034
                                if (hasBeforeEntityBatchInsertListener) {
1✔
1035
                                        // BatchInsertでは大量のEntityが処理されるため、実行前イベントでは都度Eventインスタンスを生成せず、1つのイベントインスタンスを使いまわす実装とする(性能対応)
1036
                                        eventObj.setEntity(entity);
1✔
1037
                                        for (var listener : eventListenerHolder.getBeforeEntityBatchInsertListeners()) {
1✔
1038
                                                listener.accept(eventObj);
1✔
1039
                                        }
1✔
1040
                                }
1041
                                handler.setInsertParams(context, entity);
1✔
1042

1043
                                context.addBatch();
1✔
1044
                                // SQLのID項目IF分岐判定をtrueにするためにaddBatch()の後に保持しておいたID項目をcontextにバインドする
1045
                                if (nonNullObjectIdFlags != null && !nonNullObjectIdFlags.isEmpty()) {
1✔
1046
                                        context.paramMap(nonNullObjectIdFlags);
1✔
1047
                                }
1048
                                if (condition.test(context, context.batchCount(), entity)) {
1✔
1049
                                        count += Arrays
1✔
1050
                                                        .stream(doBatchInsert(context, handler, entityList, entityType, autoGeneratedColumns))
1✔
1051
                                                        .sum();
1✔
1052
                                        entityList.clear();
1✔
1053
                                }
1054
                        }
1✔
1055
                        return count + (context.batchCount() != 0
1✔
1056
                                        ? Arrays.stream(doBatchInsert(context, handler, entityList, entityType, autoGeneratedColumns)).sum()
1✔
1057
                                        : 0);
1✔
1058
                } catch (SQLException ex) {
×
1059
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_BATCH_INSERT, ex);
×
1060
                }
1061
        }
1062

1063
        private <E> int[] doBatchInsert(final ExecutionContext context,
1064
                        final EntityHandler<E> handler,
1065
                        final List<E> entityList,
1066
                        final Class<E> entityType,
1067
                        final List<MappingColumn> autoGeneratedColumns)
1068
                        throws SQLException {
1069
                var counts = handler.doBatchInsert(this, context);
1✔
1070

1071
                // EntityBatchInsert実行後イベント発行
1072
                var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
1073
                if (eventListenerHolder.hasAfterEntityBatchInsertListener()) {
1✔
1074
                        var eventObj = new AfterEntityBatchInsertEvent(context, entityList, entityType, counts);
1✔
1075
                        for (var listener : eventListenerHolder.getAfterEntityBatchInsertListeners()) {
1✔
1076
                                listener.accept(eventObj);
1✔
1077
                        }
1✔
1078
                        counts = eventObj.getCounts();
1✔
1079
                }
1080

1081
                if (!autoGeneratedColumns.isEmpty()) {
1✔
1082
                        var ids = context.getGeneratedKeyValues();
1✔
1083
                        var idx = 0;
1✔
1084
                        for (E ent : entityList) {
1✔
1085
                                for (var col : autoGeneratedColumns) {
1✔
1086
                                        setEntityIdValue(ent, ids[idx++], col);
1✔
1087
                                }
1✔
1088
                        }
1✔
1089
                }
1090

1091
                return counts;
1✔
1092
        }
1093

1094
        /**
1095
         * 複数エンティティのINSERTをバッチ実行
1096
         *
1097
         * @param <E> エンティティの型
1098
         * @param entityType エンティティの型
1099
         * @param entities エンティティ
1100
         * @param condition 一括INSERT用のフレームの判定条件
1101
         * @param insertedEntities INSERTしたEntityのList. <code>null</code>の場合は格納されない
1102
         * @return SQL実行結果
1103
         */
1104
        @SuppressWarnings("deprecation")
1105
        private <E> int bulkInsert(final Class<E> entityType, final Stream<E> entities,
1106
                        final InsertsCondition<? super E> condition, final List<E> insertedEntities) {
1107
                EntityHandler<E> handler = this.getEntityHandler();
1✔
1108
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
1109
                        throw new IllegalArgumentException("Entity type not supported");
×
1110
                }
1111

1112
                try {
1113
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
1114
                        var context = handler.createBulkInsertContext(this, metadata, entityType);
1✔
1115
                        context.setSqlKind(SqlKind.ENTITY_BULK_INSERT);
1✔
1116

1117
                        var frameCount = 0;
1✔
1118
                        var count = 0;
1✔
1119
                        var isFirst = true;
1✔
1120
                        List<MappingColumn> autoGeneratedColumns = List.of();
1✔
1121
                        Map<String, Object> nonNullObjectIdFlags = null;
1✔
1122
                        var entityList = new ArrayList<E>();
1✔
1123
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
1124
                        var hasBeforeEntityBulkInsertListener = eventListenerHolder.hasBeforeEntityBulkInsertListener();
1✔
1125
                        var eventObj = hasBeforeEntityBulkInsertListener
1✔
1126
                                        ? new BeforeEntityBulkInsertEvent(context, null, entityType, frameCount)
1✔
1127
                                        : null;
1✔
1128
                        for (var iterator = entities.iterator(); iterator.hasNext();) {
1✔
1129
                                var entity = iterator.next();
1✔
1130

1131
                                if (!entityType.isInstance(entity)) {
1✔
1132
                                        throw new IllegalArgumentException("Entity types do not match");
×
1133
                                }
1134

1135
                                if (isFirst) {
1✔
1136
                                        isFirst = false;
1✔
1137
                                        var mappingColumns = MappingUtils.getMappingColumns(metadata.getSchema(), entityType);
1✔
1138
                                        autoGeneratedColumns = getAutoGeneratedColumns(context, mappingColumns, metadata, entity);
1✔
1139

1140
                                        // SQLのID項目IF分岐判定をtrueにするために値が設定されているID項目を保持しておく
1141
                                        var excludeColumns = autoGeneratedColumns;
1✔
1142
                                        // indexなしのid値がcontextにバインドされないため、値としてkey=trueを退避しておく
1143
                                        nonNullObjectIdFlags = Arrays.stream(mappingColumns)
1✔
1144
                                                        .filter(col -> !excludeColumns.contains(col)
1✔
1145
                                                                        && !col.getJavaType().getRawType().isPrimitive()
1✔
1146
                                                                        && col.getValue(entity) != null)
1✔
1147
                                                        .collect(Collectors.toMap(MappingColumn::getCamelName, col -> true));
1✔
1148
                                }
1149
                                // 退避しておいたid値をこのタイミングで設定する
1150
                                if (nonNullObjectIdFlags != null && !nonNullObjectIdFlags.isEmpty()) {
1✔
1151
                                        context.paramMap(nonNullObjectIdFlags);
1✔
1152
                                }
1153

1154
                                entityList.add(entity);
1✔
1155
                                if (insertedEntities != null) {
1✔
1156
                                        insertedEntities.add(entity);
1✔
1157
                                }
1158

1159
                                // EntityBulkInsert実行前イベント発行
1160
                                if (hasBeforeEntityBulkInsertListener) {
1✔
1161
                                        // BulkInsertでは大量のEntityが処理されるため、実行前イベントでは都度Eventインスタンスを生成せず、1つのイベントインスタンスを使いまわす実装とする(性能対応)
1162
                                        eventObj.setEntity(entity);
1✔
1163
                                        eventObj.setFrameCount(frameCount);
1✔
1164
                                        for (var listener : eventListenerHolder.getBeforeEntityBulkInsertListeners()) {
1✔
1165
                                                listener.accept(eventObj);
1✔
1166
                                        }
1✔
1167
                                }
1168
                                handler.setBulkInsertParams(context, entity, frameCount);
1✔
1169

1170
                                frameCount++;
1✔
1171

1172
                                if (condition.test(context, frameCount, entity)) {
1✔
1173
                                        count += doBulkInsert(context, handler, entityList, entityType, metadata, autoGeneratedColumns,
1✔
1174
                                                        frameCount);
1175
                                        frameCount = 0;
1✔
1176
                                        entityList.clear();
1✔
1177

1178
                                        // 新しいExecutionContextを作成する前にgeneratedKeyColumnsを退避しておく
1179
                                        var generatedKeyColumns = context.getGeneratedKeyColumns();
1✔
1180
                                        context = handler.createBulkInsertContext(this, metadata, entityType);
1✔
1181
                                        context.setSqlKind(SqlKind.ENTITY_BULK_INSERT);
1✔
1182
                                        // 実行結果から生成されたIDを取得できるようにPreparedStatementにIDカラムを渡す
1183
                                        context.setGeneratedKeyColumns(generatedKeyColumns);
1✔
1184
                                }
1185
                        }
1✔
1186
                        return count + (frameCount > 0
1✔
1187
                                        ? doBulkInsert(context, handler, entityList, entityType, metadata, autoGeneratedColumns, frameCount)
1✔
1188
                                        : 0);
1✔
1189

1190
                } catch (SQLException ex) {
×
1191
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_BULK_INSERT, ex);
×
1192
                }
1193
        }
1194

1195
        private <E> int doBulkInsert(final ExecutionContext context,
1196
                        final EntityHandler<E> handler,
1197
                        final List<E> entityList,
1198
                        final Class<E> entityType,
1199
                        final TableMetadata metadata,
1200
                        final List<MappingColumn> autoGeneratedColumns,
1201
                        final int frameCount)
1202
                        throws SQLException {
1203
                var count = handler.doBulkInsert(this,
1✔
1204
                                handler.setupSqlBulkInsertContext(this, context, metadata, entityType, entityList.size()));
1✔
1205

1206
                // EntityBulkInsert実行前イベント発行
1207
                var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
1208
                if (eventListenerHolder.hasAfterEntityBulkInsertListener()) {
1✔
1209
                        var eventObj = new AfterEntityBulkInsertEvent(context, entityList, entityType, frameCount, count);
1✔
1210
                        for (var listener : eventListenerHolder.getAfterEntityBulkInsertListeners()) {
1✔
1211
                                listener.accept(eventObj);
1✔
1212
                        }
1✔
1213
                        count = eventObj.getCount();
1✔
1214
                }
1215

1216
                if (!autoGeneratedColumns.isEmpty()) {
1✔
1217
                        var ids = context.getGeneratedKeyValues();
1✔
1218
                        var idx = 0;
1✔
1219
                        for (E ent : entityList) {
1✔
1220
                                for (var col : autoGeneratedColumns) {
1✔
1221
                                        setEntityIdValue(ent, ids[idx++], col);
1✔
1222
                                }
1✔
1223
                        }
1✔
1224
                }
1225

1226
                return count;
1✔
1227
        }
1228

1229
        /**
1230
         * 複数エンティティのBATCH UPDATEを実行
1231
         *
1232
         * @param <E> エンティティの型
1233
         * @param entityType エンティティの型
1234
         * @param entities エンティティ
1235
         * @param condition 一括更新用のフレームの判定条件
1236
         * @param updatedEntities INSERTしたEntityのList. <code>null</code>の場合は格納されない
1237
         * @return SQL実行結果
1238
         */
1239
        @SuppressWarnings("deprecation")
1240
        private <E> int batchUpdate(final Class<E> entityType, final Stream<E> entities,
1241
                        final UpdatesCondition<? super E> condition, final List<E> updatedEntities) {
1242
                var handler = this.getEntityHandler();
1✔
1243
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
1244
                        throw new IllegalArgumentException("Entity type not supported");
×
1245
                }
1246

1247
                try {
1248
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
1249
                        var context = handler.createBatchUpdateContext(this, metadata, entityType);
1✔
1250
                        context.setSqlKind(SqlKind.BATCH_UPDATE);
1✔
1251

1252
                        var versionColumn = MappingUtils.getVersionMappingColumn(metadata.getSchema(),
1✔
1253
                                        entityType);
1254
                        var entityCount = 0;
1✔
1255
                        var updateCount = 0;
1✔
1256
                        var entityList = new ArrayList<E>();
1✔
1257
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
1258
                        var hasBeforeEntityBatchUpdateListener = eventListenerHolder.hasBeforeEntityBatchUpdateListener();
1✔
1259
                        var eventObj = hasBeforeEntityBatchUpdateListener
1✔
1260
                                        ? new BeforeEntityBatchUpdateEvent(context, null, entityType)
1✔
1261
                                        : null;
1✔
1262
                        for (var iterator = entities.iterator(); iterator.hasNext();) {
1✔
1263
                                var entity = iterator.next();
1✔
1264

1265
                                if (!entityType.isInstance(entity)) {
1✔
1266
                                        throw new IllegalArgumentException("Entity types do not match");
×
1267
                                }
1268

1269
                                entityList.add(entity);
1✔
1270
                                if (updatedEntities != null) {
1✔
1271
                                        updatedEntities.add(entity);
1✔
1272
                                }
1273
                                entityCount++;
1✔
1274

1275
                                // EntityBatchUpdate実行前イベント発行
1276
                                if (hasBeforeEntityBatchUpdateListener) {
1✔
1277
                                        // BatchUpdateでは大量のEntityが処理されるため、実行前イベントでは都度Eventインスタンスを生成せず、1つのイベントインスタンスを使いまわす実装とする(性能対応)
1278
                                        eventObj.setEntity(entity);
1✔
1279
                                        for (var listener : eventListenerHolder.getBeforeEntityBatchUpdateListeners()) {
1✔
1280
                                                listener.accept(eventObj);
1✔
1281
                                        }
1✔
1282
                                }
1283

1284
                                handler.setUpdateParams(context, entity);
1✔
1285
                                context.addBatch();
1✔
1286

1287
                                if (condition.test(context, context.batchCount(), entity)) {
1✔
1288
                                        updateCount += Arrays.stream(doBatchUpdate(handler, context, entityList, entityType)).sum();
1✔
1289
                                        entityList.clear();
1✔
1290
                                }
1291
                        }
1✔
1292
                        updateCount = updateCount + (context.batchCount() != 0
1✔
1293
                                        ? Arrays.stream(doBatchUpdate(handler, context, entityList, entityType)).sum()
1✔
1294
                                        : 0);
1✔
1295

1296
                        if (updatedEntities != null && versionColumn.isPresent()) {
1✔
1297
                                var vColumn = versionColumn.orElseThrow();
1✔
1298
                                var keyColumns = metadata.getColumns().stream()
1✔
1299
                                                .filter(TableMetadata.Column::isKey)
1✔
1300
                                                .sorted(Comparator.comparingInt(TableMetadata.Column::getKeySeq))
1✔
1301
                                                .map(c -> MappingUtils.getMappingColumnMap(metadata.getSchema(), entityType, SqlKind.NONE)
1✔
1302
                                                                .get(c.getCamelColumnName()))
1✔
1303
                                                .collect(Collectors.toList());
1✔
1304

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

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

1315
                                        for (var start = 0; start < entitySize; start = start + IN_CLAUSE_MAX_PARAM_SIZE) {
1✔
1316
                                                var end = Math.min(start + IN_CLAUSE_MAX_PARAM_SIZE, entitySize);
1✔
1317
                                                var subList = keyList.subList(start, end);
1✔
1318

1319
                                                query(entityType).in(keyColumn.getCamelName(), subList).stream()
1✔
1320
                                                                .map(e -> {
1✔
1321
                                                                        var updatedEntity = updatedEntityMap.get(keyColumn.getValue(e)).get(0);
1✔
1322
                                                                        vColumn.setValue(updatedEntity, vColumn.getValue(e));
1✔
1323
                                                                        return updatedEntity;
1✔
1324
                                                                }).count();
1✔
1325
                                        }
1326
                                } else if (keyColumns.size() > 1) {
1✔
1327
                                        // 複合キーの場合はIN句で一括取得できないため1件ずつ取得して@Versionのついたフィールドを更新する
1328
                                        updatedEntities.stream()
1✔
1329
                                                        .map(updatedEntity -> {
1✔
1330
                                                                var keyValues = keyColumns.stream().map(k -> k.getValue(updatedEntity)).toArray();
×
1331
                                                                find(entityType, keyValues).ifPresent(e -> {
×
1332
                                                                        vColumn.setValue(updatedEntity, vColumn.getValue(e));
×
1333
                                                                });
×
1334
                                                                return updatedEntity;
×
1335
                                                        }).count();
1✔
1336
                                }
1337
                        }
1338
                        if (versionColumn.isPresent() && getDialect().supportsEntityBulkUpdateOptimisticLock()
1✔
1339
                                        && updateCount != entityCount) {
1340
                                // バージョンカラムの指定があり、更新件数と更新対象Entityの件数が不一致の場合は楽観ロックエラーとする
1341
                                throw new OptimisticLockException(String.format(
1✔
1342
                                                "An error occurred due to optimistic locking.%nExecuted SQL [%n%s]%nBatch Entity Count: %d, Update Count: %d.",
1343
                                                context.getExecutableSql(), entityCount, updateCount));
1✔
1344
                        }
1345
                        return updateCount;
1✔
1346
                } catch (SQLException ex) {
×
1347
                        throw new EntitySqlRuntimeException(SqlKind.BATCH_UPDATE, ex);
×
1348
                }
1349
        }
1350

1351
        private <E> int[] doBatchUpdate(final EntityHandler<Object> handler,
1352
                        final ExecutionContext context,
1353
                        final List<E> entityList,
1354
                        final Class<E> entityType) throws SQLException {
1355
                var counts = handler.doBatchUpdate(this, context);
1✔
1356

1357
                // EntityBatchUpdate実行後イベント発行
1358
                var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
1359
                if (eventListenerHolder.hasAfterEntityBatchUpdateListener()) {
1✔
1360
                        var eventObj = new AfterEntityBatchUpdateEvent(context, entityList, entityType, counts);
1✔
1361
                        for (var listener : eventListenerHolder.getAfterEntityBatchUpdateListeners()) {
1✔
1362
                                listener.accept(eventObj);
1✔
1363
                        }
1✔
1364
                        counts = eventObj.getCounts();
1✔
1365
                }
1366

1367
                return counts;
1✔
1368
        }
1369

1370
        /**
1371
         * {@inheritDoc}
1372
         *
1373
         * @see jp.co.future.uroborosql.SqlAgent#query(jp.co.future.uroborosql.context.ExecutionContext)
1374
         */
1375
        @Override
1376
        public ResultSet query(final ExecutionContext executionContext) throws SQLException {
1377
                if (SqlKind.NONE.equals(executionContext.getSqlKind())) {
1✔
1378
                        executionContext.setSqlKind(SqlKind.SELECT);
1✔
1379
                }
1380

1381
                // コンテキスト変換
1382
                transformContext(executionContext);
1✔
1383

1384
                var stmt = getPreparedStatement(executionContext);
1✔
1385

1386
                // INパラメータ設定
1387
                executionContext.bindParams(stmt);
1✔
1388

1389
                debugWith(LOG)
1✔
1390
                                .setMessage("Execute query sql. sqlName: {}")
1✔
1391
                                .addArgument(executionContext.getSqlName())
1✔
1392
                                .log();
1✔
1393
                var startTime = PERFORMANCE_LOG.isDebugEnabled() ? Instant.now(getSqlConfig().getClock()) : null;
1✔
1394

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

1401
                        // デフォルトリトライ待機時間を取得し、個別指定(ExecutionContextの値)があれば上書き
1402
                        var retryWaitTime = executionContext.getRetryWaitTime() > 0
1✔
1403
                                        ? executionContext.getRetryWaitTime()
1✔
1404
                                        : getRetryWaitTime();
1✔
1405
                        var loopCount = 0;
1✔
1406
                        var dialect = getDialect();
1✔
1407
                        ResultSet rs = null;
1✔
1408
                        try {
1409
                                do {
1410
                                        try {
1411
                                                if (maxRetryCount > 0 && dialect.isRollbackToSavepointBeforeRetry()) {
1✔
1412
                                                        setSavepoint(RETRY_SAVEPOINT_NAME);
1✔
1413
                                                }
1414
                                                rs = stmt.executeQuery();
1✔
1415
                                                // Query実行後イベント発行
1416
                                                if (getSqlConfig().getEventListenerHolder().hasAfterSqlQueryListener()) {
1✔
1417
                                                        var eventObj = new AfterSqlQueryEvent(executionContext, rs, stmt.getConnection(), stmt);
1✔
1418
                                                        for (var listener : getSqlConfig().getEventListenerHolder().getAfterSqlQueryListeners()) {
1✔
1419
                                                                listener.accept(eventObj);
1✔
1420
                                                        }
1✔
1421
                                                        rs = eventObj.getResultSet();
1✔
1422
                                                }
1423
                                                stmt.closeOnCompletion();
1✔
1424
                                                return new InnerResultSet(rs, stmt);
1✔
1425
                                        } catch (SQLException ex) {
1✔
1426
                                                if (maxRetryCount > 0 && dialect.isRollbackToSavepointBeforeRetry()) {
1✔
1427
                                                        rollback(RETRY_SAVEPOINT_NAME);
1✔
1428
                                                }
1429
                                                var errorCode = String.valueOf(ex.getErrorCode());
1✔
1430
                                                var sqlState = ex.getSQLState();
1✔
1431
                                                var pessimisticLockingErrorCodes = dialect.getPessimisticLockingErrorCodes();
1✔
1432
                                                if (maxRetryCount > loopCount
1✔
1433
                                                                && (getSqlRetryCodes().contains(errorCode) || getSqlRetryCodes().contains(sqlState))) {
1✔
1434
                                                        debugWith(LOG)
1✔
1435
                                                                        .setMessage("Caught the error code to be retried.({} times). Retry after {} ms.")
1✔
1436
                                                                        .addArgument(loopCount + 1)
1✔
1437
                                                                        .addArgument(() -> String.format("%,3d", retryWaitTime))
1✔
1438
                                                                        .log();
1✔
1439
                                                        if (retryWaitTime > 0) {
1✔
1440
                                                                try {
1441
                                                                        Thread.sleep(retryWaitTime);
1✔
1442
                                                                } catch (InterruptedException ie) {
×
1443
                                                                        // do nothing
1444
                                                                }
1✔
1445
                                                        }
1446
                                                } else {
1447
                                                        if (pessimisticLockingErrorCodes.contains(errorCode)
1✔
1448
                                                                        || pessimisticLockingErrorCodes.contains(sqlState)) {
1✔
1449
                                                                throw new PessimisticLockException(executionContext, ex);
1✔
1450
                                                        } else {
1451
                                                                throw ex;
1✔
1452
                                                        }
1453
                                                }
1454
                                        } finally {
1455
                                                if (maxRetryCount > 0 && dialect.isRollbackToSavepointBeforeRetry()) {
1✔
1456
                                                        releaseSavepoint(RETRY_SAVEPOINT_NAME);
1✔
1457
                                                }
1458
                                                executionContext.contextAttrs().put(CTX_ATTR_KEY_RETRY_COUNT, loopCount);
1✔
1459
                                        }
1460
                                } while (maxRetryCount > loopCount++);
1✔
1461
                        } catch (SQLException | RuntimeException ex) {
1✔
1462
                                if (rs != null && !rs.isClosed()) {
1✔
1463
                                        rs.close();
1✔
1464
                                }
1465
                                throw ex;
1✔
1466
                        }
×
1467
                        return null;
×
1468
                } catch (SQLException ex) {
1✔
1469
                        handleException(executionContext, ex);
×
1470
                        return null;
×
1471
                } finally {
1472
                        // 後処理
1473
                        debugWith(PERFORMANCE_LOG)
1✔
1474
                                        .setMessage("SQL execution time [{}({})] : [{}]")
1✔
1475
                                        .addArgument(() -> generateSqlName(executionContext))
1✔
1476
                                        .addArgument(executionContext.getSqlKind())
1✔
1477
                                        .addArgument(() -> formatElapsedTime(startTime, Instant.now(getSqlConfig().getClock())))
1✔
1478
                                        .log();
1✔
1479
                }
1480
        }
1481

1482
        /**
1483
         * {@inheritDoc}
1484
         *
1485
         * @see jp.co.future.uroborosql.SqlAgent#query(jp.co.future.uroborosql.context.ExecutionContext,
1486
         *      jp.co.future.uroborosql.converter.ResultSetConverter)
1487
         */
1488
        @Override
1489
        public <T> Stream<T> query(final ExecutionContext executionContext, final ResultSetConverter<T> converter)
1490
                        throws SQLException {
1491
                var rs = query(executionContext);
1✔
1492
                return StreamSupport.stream(new ResultSetSpliterator<>(rs, converter), false).onClose(() -> {
1✔
1493
                        try {
1494
                                if (rs != null && !rs.isClosed()) {
1✔
1495
                                        rs.close();
1✔
1496
                                }
1497
                        } catch (SQLException ex) {
×
1498
                                // do nothing
1499
                        }
1✔
1500
                });
1✔
1501
        }
1502

1503
        /**
1504
         * {@inheritDoc}
1505
         *
1506
         * @see jp.co.future.uroborosql.SqlAgent#query(jp.co.future.uroborosql.context.ExecutionContext,
1507
         *      jp.co.future.uroborosql.utils.CaseFormat)
1508
         */
1509
        @Override
1510
        public List<Map<String, Object>> query(final ExecutionContext executionContext, final CaseFormat caseFormat)
1511
                        throws SQLException {
1512
                try (var stream = query(executionContext, new MapResultSetConverter(getSqlConfig(), caseFormat))) {
1✔
1513
                        return stream.collect(Collectors.toList());
1✔
1514
                }
1515
        }
1516

1517
        /**
1518
         * @see jp.co.future.uroborosql.SqlAgent#update(jp.co.future.uroborosql.context.ExecutionContext)
1519
         */
1520
        @Override
1521
        public int update(final ExecutionContext executionContext) throws SQLException {
1522
                if (SqlKind.NONE.equals(executionContext.getSqlKind())) {
1✔
1523
                        executionContext.setSqlKind(SqlKind.UPDATE);
1✔
1524
                }
1525

1526
                // コンテキスト変換
1527
                transformContext(executionContext);
1✔
1528

1529
                // 更新移譲処理の指定がある場合は移譲処理を実行し結果を返却
1530
                if (executionContext.getUpdateDelegate() != null) {
1✔
1531
                        debugWith(LOG)
1✔
1532
                                        .log("Performs update delegate of update process.");
1✔
1533
                        return executionContext.getUpdateDelegate().apply(executionContext);
1✔
1534
                }
1535

1536
                Instant startTime = null;
1✔
1537

1538
                try (var stmt = getPreparedStatement(executionContext)) {
1✔
1539

1540
                        // INパラメータ設定
1541
                        executionContext.bindParams(stmt);
1✔
1542

1543
                        debugWith(LOG)
1✔
1544
                                        .setMessage("Execute update sql. sqlName: {}")
1✔
1545
                                        .addArgument(executionContext.getSqlName())
1✔
1546
                                        .log();
1✔
1547

1548
                        if (PERFORMANCE_LOG.isDebugEnabled()) {
1✔
1549
                                startTime = Instant.now(getSqlConfig().getClock());
×
1550
                        }
1551

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

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

1642
        /**
1643
         * @see jp.co.future.uroborosql.SqlAgent#batch(jp.co.future.uroborosql.context.ExecutionContext)
1644
         */
1645
        @Override
1646
        public int[] batch(final ExecutionContext executionContext) throws SQLException {
1647
                // バッチ処理の場合大量のログが出力されるため、パラメータログの出力を抑止する
1648
                suppressParameterLogging();
1✔
1649

1650
                if (SqlKind.NONE.equals(executionContext.getSqlKind())) {
1✔
1651
                        executionContext.setSqlKind(SqlKind.BATCH_INSERT);
1✔
1652
                }
1653

1654
                // コンテキスト変換
1655
                transformContext(executionContext);
1✔
1656

1657
                // 更新移譲処理の指定がある場合は移譲処理を実行し結果を返却
1658
                if (executionContext.getUpdateDelegate() != null) {
1✔
1659
                        releaseParameterLogging();
1✔
1660
                        debugWith(LOG)
1✔
1661
                                        .log("Performs update delegate of batch process.");
1✔
1662
                        return new int[] { executionContext.getUpdateDelegate().apply(executionContext) };
1✔
1663
                }
1664

1665
                Instant startTime = null;
1✔
1666

1667
                try (var stmt = getPreparedStatement(executionContext)) {
1✔
1668

1669
                        // INパラメータ設定
1670
                        executionContext.bindBatchParams(stmt);
1✔
1671

1672
                        debugWith(LOG)
1✔
1673
                                        .setMessage("Execute batch sql. sqlName: {}")
1✔
1674
                                        .addArgument(executionContext.getSqlName())
1✔
1675
                                        .log();
1✔
1676
                        if (PERFORMANCE_LOG.isDebugEnabled()) {
1✔
1677
                                startTime = Instant.now(getSqlConfig().getClock());
×
1678
                        }
1679

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

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

1770
        /**
1771
         * {@inheritDoc}
1772
         *
1773
         * @see jp.co.future.uroborosql.SqlAgent#procedure(jp.co.future.uroborosql.context.ExecutionContext)
1774
         */
1775
        @Override
1776
        public Map<String, Object> procedure(final ExecutionContext executionContext) throws SQLException {
1777
                // procedureやfunctionの場合、SQL文法エラーになるためバインドパラメータコメントを出力しない
1778
                executionContext.contextAttrs().put(CTX_ATTR_KEY_OUTPUT_BIND_COMMENT, false);
1✔
1779

1780
                if (SqlKind.NONE.equals(executionContext.getSqlKind())) {
1✔
1781
                        executionContext.setSqlKind(SqlKind.PROCEDURE);
1✔
1782
                }
1783

1784
                // コンテキスト変換
1785
                transformContext(executionContext);
1✔
1786

1787
                Instant startTime = null;
1✔
1788

1789
                try (var callableStatement = getCallableStatement(executionContext)) {
1✔
1790

1791
                        // パラメータ設定
1792
                        executionContext.bindParams(callableStatement);
1✔
1793

1794
                        debugWith(LOG)
1✔
1795
                                        .setMessage("Execute stored procedure. sqlName: {}")
1✔
1796
                                        .addArgument(executionContext.getSqlName())
1✔
1797
                                        .log();
1✔
1798
                        if (PERFORMANCE_LOG.isDebugEnabled()) {
1✔
1799
                                startTime = Instant.now(getSqlConfig().getClock());
×
1800
                        }
1801

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

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

1812
                        var loopCount = 0;
1✔
1813
                        do {
1814
                                try {
1815
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
1816
                                                setSavepoint(RETRY_SAVEPOINT_NAME);
1✔
1817
                                        }
1818
                                        var result = callableStatement.execute();
1✔
1819
                                        // Procedure実行後イベント発行
1820
                                        if (getSqlConfig().getEventListenerHolder().hasAfterProcedureListener()) {
1✔
1821
                                                var eventObj = new AfterProcedureEvent(executionContext, result,
1✔
1822
                                                                callableStatement.getConnection(),
1✔
1823
                                                                callableStatement);
1824
                                                for (var listener : getSqlConfig().getEventListenerHolder().getAfterProcedureListeners()) {
1✔
1825
                                                        listener.accept(eventObj);
1✔
1826
                                                }
1✔
1827
                                                result = eventObj.isResult();
1✔
1828
                                        }
1829
                                        break;
1830
                                } catch (SQLException ex) {
1✔
1831
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
1832
                                                rollback(RETRY_SAVEPOINT_NAME);
1✔
1833
                                        }
1834
                                        if (maxRetryCount > loopCount) {
1✔
1835
                                                var errorCode = String.valueOf(ex.getErrorCode());
1✔
1836
                                                var sqlState = ex.getSQLState();
1✔
1837
                                                if (getSqlRetryCodes().contains(errorCode) || getSqlRetryCodes().contains(sqlState)) {
1✔
1838
                                                        debugWith(LOG)
1✔
1839
                                                                        .setMessage("Caught the error code to be retried.({} times). Retry after {} ms.")
1✔
1840
                                                                        .addArgument(loopCount + 1)
1✔
1841
                                                                        .addArgument(() -> String.format("%,3d", retryWaitTime))
1✔
1842
                                                                        .log();
1✔
1843
                                                        if (retryWaitTime > 0) {
1✔
1844
                                                                try {
1845
                                                                        Thread.sleep(retryWaitTime);
1✔
1846
                                                                } catch (InterruptedException ie) {
×
1847
                                                                        // do nothing
1848
                                                                }
1✔
1849
                                                        }
1850
                                                } else {
1851
                                                        throw ex;
1✔
1852
                                                }
1853
                                        } else {
1✔
1854
                                                throw ex;
1✔
1855
                                        }
1856
                                } finally {
1857
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
1858
                                                releaseSavepoint(RETRY_SAVEPOINT_NAME);
1✔
1859
                                        }
1860
                                        executionContext.contextAttrs().put(CTX_ATTR_KEY_RETRY_COUNT, loopCount);
1✔
1861
                                }
1862
                        } while (maxRetryCount > loopCount++);
1✔
1863
                        // 結果取得
1864
                        return executionContext.getOutParams(callableStatement);
1✔
1865
                } catch (SQLException ex) {
1✔
1866
                        handleException(executionContext, ex);
×
1867
                } finally {
1868
                        // 後処理
1869
                        var curStartTime = startTime;
1✔
1870
                        debugWith(PERFORMANCE_LOG)
1✔
1871
                                        .setMessage("Stored procedure execution time [{}({})] : [{}]")
1✔
1872
                                        .addArgument(() -> generateSqlName(executionContext))
1✔
1873
                                        .addArgument(executionContext.getSqlKind())
1✔
1874
                                        .addArgument(() -> formatElapsedTime(curStartTime, Instant.now(getSqlConfig().getClock())))
1✔
1875
                                        .log();
1✔
1876
                }
1877
                return null;
×
1878
        }
1879

1880
        /**
1881
         * ExecutionContextの設定内容を元にSQLを構築する
1882
         *
1883
         * @param executionContext ExecutionContext
1884
         */
1885
        private void transformContext(final ExecutionContext executionContext) {
1886
                var originalSql = executionContext.getSql();
1✔
1887
                var sqlName = executionContext.getSqlName();
1✔
1888
                if (ObjectUtils.isEmpty(originalSql) && getSqlResourceManager() != null) {
1✔
1889
                        originalSql = getSqlResourceManager().getSql(sqlName);
1✔
1890
                }
1891

1892
                // SQL-IDの付与
1893
                if (originalSql.contains(keySqlId)) {
1✔
1894
                        var sqlId = executionContext.getSqlId();
1✔
1895
                        if (ObjectUtils.isEmpty(sqlId)) {
1✔
1896
                                sqlId = sqlName;
1✔
1897
                        }
1898
                        if (ObjectUtils.isEmpty(sqlId)) {
1✔
1899
                                sqlId = String.valueOf(originalSql.hashCode());
×
1900
                        }
1901

1902
                        originalSql = originalSql.replace(keySqlId, sqlId);
1✔
1903
                }
1904

1905
                if (SQL_LOG.isDebugEnabled() && sqlName != null) {
1✔
1906
                        if (executionContext.getSqlKind().isEntityType()) {
×
1907
                                debugWith(SQL_LOG)
×
1908
                                                .setMessage("EntityClass : {}")
×
1909
                                                .addArgument(sqlName)
×
1910
                                                .log();
×
1911
                        } else if (getSqlResourceManager().existSql(sqlName)) {
×
1912
                                debugWith(SQL_LOG)
×
1913
                                                .setMessage("SQLUrl : {}")
×
1914
                                                .addArgument(() -> getSqlResourceManager().getSqlUrl(sqlName))
×
1915
                                                .log();
×
1916
                        }
1917
                }
1918

1919
                // SQL変換前イベント発行
1920
                if (getSqlConfig().getEventListenerHolder().hasBeforeTransformSqlListener()) {
1✔
1921
                        var eventObj = new BeforeTransformSqlEvent(executionContext, originalSql);
1✔
1922
                        getSqlConfig().getEventListenerHolder().getBeforeTransformSqlListeners()
1✔
1923
                                        .forEach(listener -> listener.accept(eventObj));
1✔
1924
                        originalSql = eventObj.getSql();
1✔
1925
                }
1926
                executionContext.setSql(originalSql);
1✔
1927

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

1931
                // SQLパース前イベントの呼出
1932
                if (executionContext.batchCount() == 0
1✔
1933
                                && getSqlConfig().getEventListenerHolder().hasBeforeParseSqlListener()) {
1✔
1934
                        var eventObj = new BeforeParseSqlEvent(executionContext);
1✔
1935
                        getSqlConfig().getEventListenerHolder().getBeforeParseSqlListeners()
1✔
1936
                                        .forEach(listener -> listener.accept(eventObj));
1✔
1937
                }
1938

1939
                traceWith(SQL_LOG)
1✔
1940
                                .setMessage("Template SQL[{}{}{}]")
1✔
1941
                                .addArgument(System.lineSeparator())
1✔
1942
                                .addArgument(originalSql)
1✔
1943
                                .addArgument(System.lineSeparator())
1✔
1944
                                .log();
1✔
1945

1946
                if (ObjectUtils.isEmpty(executionContext.getExecutableSql())) {
1✔
1947
                        // SQLパーサーによるパース処理
1948
                        var outputBindComment = (boolean) executionContext.contextAttrs().getOrDefault(
1✔
1949
                                        CTX_ATTR_KEY_OUTPUT_BIND_COMMENT, true);
1✔
1950
                        var sqlParser = new SqlParserImpl(originalSql, sqlConfig.getExpressionParser(),
1✔
1951
                                        getDialect().isRemoveTerminator(), outputBindComment);
1✔
1952
                        var contextTransformer = sqlParser.parse();
1✔
1953
                        contextTransformer.transform(executionContext);
1✔
1954

1955
                        if (COVERAGE_HANDLER_REF.get() != null) {
1✔
1956
                                // SQLカバレッジ用のログを出力する
1957
                                var coverageData = new CoverageData(sqlName, originalSql,
1✔
1958
                                                contextTransformer.getPassedRoute());
1✔
1959
                                traceWith(COVERAGE_LOG)
1✔
1960
                                                .setMessage("coverage data: {}")
1✔
1961
                                                .addArgument(coverageData)
1✔
1962
                                                .log();
1✔
1963

1964
                                COVERAGE_HANDLER_REF.get().accept(coverageData);
1✔
1965
                        }
1966
                }
1967

1968
                debugWith(SQL_LOG)
1✔
1969
                                .setMessage("Executed SQL[{}{}{}]")
1✔
1970
                                .addArgument(System.lineSeparator())
1✔
1971
                                .addArgument(executionContext.getExecutableSql())
1✔
1972
                                .addArgument(System.lineSeparator())
1✔
1973
                                .log();
1✔
1974
        }
1✔
1975

1976
        /** 時間計測用のログに出力するSQL名を生成する.
1977
         *
1978
         * @param executionContext ExecutionContext
1979
         * @return SQL名. SQL名が取得できない場合はSQL_ID、または空文字を返却する
1980
         */
1981
        private String generateSqlName(final ExecutionContext executionContext) {
1982
                if (executionContext.getSqlName() != null) {
×
1983
                        return executionContext.getSqlName();
×
1984
                } else {
1985
                        return Objects.toString(executionContext.getSqlId(), "");
×
1986
                }
1987
        }
1988

1989
        /**
1990
         * 例外発生時ハンドラー
1991
         *
1992
         * @param executionContext ExecutionContext
1993
         * @param ex SQL例外
1994
         * @throws SQLException SQL例外
1995
         */
1996
        private void handleException(final ExecutionContext executionContext, final SQLException ex) throws SQLException {
1997
                var cause = ex;
1✔
1998

1999
                while (cause.getNextException() != null) {
1✔
2000
                        cause = cause.getNextException();
1✔
2001
                }
2002

2003
                if (outputExceptionLog) {
1✔
2004
                        errorWith(LOG)
1✔
2005
                                        .setMessage(() -> {
1✔
2006
                                                var builder = new StringBuilder();
1✔
2007
                                                builder.append(System.lineSeparator()).append("Exception occurred in SQL execution.")
1✔
2008
                                                                .append(System.lineSeparator());
1✔
2009
                                                builder.append("Executed SQL[").append(executionContext.getExecutableSql()).append("]")
1✔
2010
                                                                .append(System.lineSeparator());
1✔
2011
                                                if (executionContext instanceof ExecutionContextImpl) {
1✔
2012
                                                        var bindParameters = ((ExecutionContextImpl) executionContext).getBindParameters();
1✔
2013
                                                        for (var i = 0; i < bindParameters.length; i++) {
1✔
2014
                                                                var parameter = bindParameters[i];
1✔
2015
                                                                builder.append("Bind Parameter.[INDEX[").append(i + 1).append("], ")
1✔
2016
                                                                                .append(parameter.toString())
1✔
2017
                                                                                .append("]").append(System.lineSeparator());
1✔
2018
                                                        }
2019
                                                }
2020
                                                return builder.toString();
1✔
2021
                                        })
2022
                                        .setCause(cause)
1✔
2023
                                        .log();
1✔
2024
                }
2025

2026
                throw cause;
1✔
2027
        }
2028

2029
        /**
2030
         *
2031
         * {@inheritDoc}
2032
         *
2033
         * @see jp.co.future.uroborosql.SqlAgent#find(java.lang.Class, java.lang.Object[])
2034
         */
2035
        @SuppressWarnings("unchecked")
2036
        @Override
2037
        public <E> Optional<E> find(final Class<? extends E> entityType, final Object... keys) {
2038
                @SuppressWarnings("rawtypes")
2039
                EntityHandler handler = this.getEntityHandler();
1✔
2040
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2041
                        throw new IllegalArgumentException("Entity type not supported");
×
2042
                }
2043

2044
                try {
2045
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2046
                        var keyNames = metadata.getColumns().stream()
1✔
2047
                                        .filter(TableMetadata.Column::isKey)
1✔
2048
                                        .sorted(Comparator.comparingInt(TableMetadata.Column::getKeySeq))
1✔
2049
                                        .map(TableMetadata.Column::getColumnName)
1✔
2050
                                        .map(CaseFormat.CAMEL_CASE::convert)
1✔
2051
                                        .toArray(String[]::new);
1✔
2052

2053
                        if (keyNames.length != keys.length) {
1✔
2054
                                throw new IllegalArgumentException("Number of keys does not match");
×
2055
                        }
2056
                        var params = new HashMap<String, Object>();
1✔
2057
                        for (var i = 0; i < keys.length; i++) {
1✔
2058
                                params.put(keyNames[i], keys[i]);
1✔
2059
                        }
2060

2061
                        var context = handler.createSelectContext(this, metadata, entityType, true);
1✔
2062
                        context.setSqlKind(SqlKind.ENTITY_SELECT).paramMap(params);
1✔
2063

2064
                        // EntityQuery実行前イベント発行
2065
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
2066
                        if (eventListenerHolder.hasBeforeEntityQueryListener()) {
1✔
2067
                                var eventObj = new BeforeEntityQueryEvent(context, null, entityType);
×
2068
                                for (var listener : eventListenerHolder.getBeforeEntityQueryListeners()) {
×
2069
                                        listener.accept(eventObj);
×
2070
                                }
×
2071
                        }
2072

2073
                        var results = handler.doSelect(this, context, entityType);
1✔
2074

2075
                        // EntityQuery実行後イベント発行
2076
                        if (eventListenerHolder.hasAfterEntityQueryListener()) {
1✔
2077
                                var eventObj = new AfterEntityQueryEvent(context(), null, entityType, results);
×
2078
                                for (var listener : eventListenerHolder.getAfterEntityQueryListeners()) {
×
2079
                                        listener.accept(eventObj);
×
2080
                                }
×
2081
                                results = eventObj.getResults();
×
2082
                        }
2083

2084
                        try (var stream = results) {
1✔
2085
                                return stream.findFirst();
1✔
2086
                        }
2087
                } catch (SQLException ex) {
×
2088
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_SELECT, ex);
×
2089
                }
2090
        }
2091

2092
        /**
2093
         * {@inheritDoc}
2094
         *
2095
         * @see jp.co.future.uroborosql.SqlAgent#insert(java.lang.Object)
2096
         */
2097
        @SuppressWarnings("unchecked")
2098
        @Override
2099
        public <E> int insert(final E entity) {
2100
                if (entity instanceof Stream) {
1✔
2101
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
2102
                }
2103

2104
                @SuppressWarnings("rawtypes")
2105
                EntityHandler handler = this.getEntityHandler();
1✔
2106
                if (!handler.getEntityType().isInstance(entity)) {
1✔
2107
                        throw new IllegalArgumentException("Entity type not supported");
×
2108
                }
2109

2110
                try {
2111
                        var entityType = entity.getClass();
1✔
2112
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2113
                        var context = handler.createInsertContext(this, metadata, entityType);
1✔
2114
                        context.setSqlKind(SqlKind.ENTITY_INSERT);
1✔
2115

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

2120
                        // EntityInsert実行前イベント発行
2121
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
2122
                        if (eventListenerHolder.hasBeforeEntityInsertListener()) {
1✔
2123
                                var eventObj = new BeforeEntityInsertEvent(context, entity, entityType);
1✔
2124
                                for (var listener : eventListenerHolder.getBeforeEntityInsertListeners()) {
1✔
2125
                                        listener.accept(eventObj);
1✔
2126
                                }
1✔
2127
                        }
2128
                        handler.setInsertParams(context, entity);
1✔
2129

2130
                        var count = handler.doInsert(this, context, entity);
1✔
2131

2132
                        // EntityInsert実行後イベント発行
2133
                        if (eventListenerHolder.hasAfterEntityInsertListener()) {
1✔
2134
                                var eventObj = new AfterEntityInsertEvent(context, entity, entityType, count);
1✔
2135
                                for (var listener : eventListenerHolder.getAfterEntityInsertListeners()) {
1✔
2136
                                        listener.accept(eventObj);
1✔
2137
                                }
1✔
2138
                                count = eventObj.getCount();
1✔
2139
                        }
2140

2141
                        if (!autoGeneratedColumns.isEmpty()) {
1✔
2142
                                var ids = context.getGeneratedKeyValues();
1✔
2143
                                var idx = 0;
1✔
2144
                                for (var col : autoGeneratedColumns) {
1✔
2145
                                        setEntityIdValue(entity, ids[idx++], col);
1✔
2146
                                }
1✔
2147
                        }
2148
                        return count;
1✔
2149
                } catch (SQLException ex) {
1✔
2150
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_INSERT, ex);
1✔
2151
                }
2152
        }
2153

2154
        /**
2155
         * {@inheritDoc}
2156
         *
2157
         * @see jp.co.future.uroborosql.SqlAgent#insertAndReturn(java.lang.Object)
2158
         */
2159
        @Override
2160
        public <E> E insertAndReturn(final E entity) {
2161
                insert(entity);
1✔
2162
                return entity;
1✔
2163
        }
2164

2165
        /**
2166
         * {@inheritDoc}
2167
         *
2168
         * @see jp.co.future.uroborosql.SqlAgent#update(java.lang.Object)
2169
         */
2170
        @SuppressWarnings("unchecked")
2171
        @Override
2172
        public <E> int update(final E entity) {
2173
                if (entity instanceof Stream) {
1✔
2174
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
2175
                }
2176

2177
                @SuppressWarnings("rawtypes")
2178
                EntityHandler handler = this.getEntityHandler();
1✔
2179
                if (!handler.getEntityType().isInstance(entity)) {
1✔
2180
                        throw new IllegalArgumentException("Entity type not supported");
×
2181
                }
2182

2183
                try {
2184
                        var entityType = entity.getClass();
1✔
2185
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2186
                        var context = handler.createUpdateContext(this, metadata, entityType, true);
1✔
2187
                        context.setSqlKind(SqlKind.ENTITY_UPDATE);
1✔
2188

2189
                        // EntityUpdate実行前イベント発行
2190
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
2191
                        if (eventListenerHolder.hasBeforeEntityUpdateListener()) {
1✔
2192
                                var eventObj = new BeforeEntityUpdateEvent(context, entity, entityType);
1✔
2193
                                for (var listener : eventListenerHolder.getBeforeEntityUpdateListeners()) {
1✔
2194
                                        listener.accept(eventObj);
1✔
2195
                                }
1✔
2196
                        }
2197
                        handler.setUpdateParams(context, entity);
1✔
2198

2199
                        var count = handler.doUpdate(this, context, entity);
1✔
2200

2201
                        // EntityUpdate実行後イベント発行
2202
                        if (eventListenerHolder.hasAfterEntityUpdateListener()) {
1✔
2203
                                var eventObj = new AfterEntityUpdateEvent(context, entity, entityType, count);
1✔
2204
                                for (var listener : eventListenerHolder.getAfterEntityUpdateListeners()) {
1✔
2205
                                        listener.accept(eventObj);
1✔
2206
                                }
1✔
2207
                                count = eventObj.getCount();
1✔
2208
                        }
2209

2210
                        var updateCount = count;
1✔
2211
                        MappingUtils.getVersionMappingColumn(metadata.getSchema(), entityType).ifPresent(versionColumn -> {
1✔
2212
                                if (updateCount == 0) {
1✔
2213
                                        throw new OptimisticLockException(context);
1✔
2214
                                } else {
2215
                                        var columnMap = MappingUtils.getMappingColumnMap(metadata.getSchema(), entityType, SqlKind.NONE);
1✔
2216
                                        var keys = metadata.getColumns().stream()
1✔
2217
                                                        .filter(TableMetadata.Column::isKey)
1✔
2218
                                                        .sorted(Comparator.comparingInt(TableMetadata.Column::getKeySeq))
1✔
2219
                                                        .map(c -> columnMap.get(c.getCamelColumnName()).getValue(entity))
1✔
2220
                                                        .toArray();
1✔
2221

2222
                                        find(entityType, keys).ifPresent(e -> versionColumn.setValue(entity, versionColumn.getValue(e)));
1✔
2223
                                }
2224
                        });
1✔
2225
                        return updateCount;
1✔
2226
                } catch (SQLException ex) {
×
2227
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_UPDATE, ex);
×
2228
                }
2229
        }
2230

2231
        /**
2232
         * {@inheritDoc}
2233
         *
2234
         * @see jp.co.future.uroborosql.SqlAgent#updateAndReturn(java.lang.Object)
2235
         */
2236
        @Override
2237
        public <E> E updateAndReturn(final E entity) {
2238
                update(entity);
1✔
2239
                return entity;
1✔
2240
        }
2241

2242
        /**
2243
         * {@inheritDoc}
2244
         *
2245
         * @see jp.co.future.uroborosql.SqlAgent#update(java.lang.Class)
2246
         */
2247
        @Override
2248
        public <E> SqlEntityUpdate<E> update(final Class<? extends E> entityType) {
2249
                var handler = this.getEntityHandler();
1✔
2250
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2251
                        throw new IllegalArgumentException("Entity type not supported");
×
2252
                }
2253

2254
                try {
2255
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2256

2257
                        var context = handler.createUpdateContext(this, metadata, entityType, false);
1✔
2258
                        context.setSqlKind(SqlKind.ENTITY_UPDATE);
1✔
2259

2260
                        return new SqlEntityUpdateImpl<>(this, handler, metadata, context, entityType);
1✔
2261
                } catch (SQLException ex) {
×
2262
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_UPDATE, ex);
×
2263
                }
2264
        }
2265

2266
        /**
2267
         * {@inheritDoc}
2268
         *
2269
         * @see jp.co.future.uroborosql.SqlAgent#merge(java.lang.Object)
2270
         */
2271
        @Override
2272
        public <E> int merge(final E entity) {
2273
                mergeAndReturn(entity);
1✔
2274
                return 1;
1✔
2275
        }
2276

2277
        /**
2278
         * {@inheritDoc}
2279
         *
2280
         * @see jp.co.future.uroborosql.SqlAgent#mergeAndReturn(java.lang.Object)
2281
         */
2282
        @Override
2283
        public <E> E mergeAndReturn(final E entity) {
2284
                return doMergeAndReturn(entity, false);
1✔
2285
        }
2286

2287
        /**
2288
         * {@inheritDoc}
2289
         *
2290
         * @see jp.co.future.uroborosql.SqlAgent#mergeWithLocking(java.lang.Object)
2291
         */
2292
        @Override
2293
        public <E> int mergeWithLocking(final E entity) {
2294
                mergeWithLockingAndReturn(entity);
1✔
2295
                return 1;
1✔
2296
        }
2297

2298
        /**
2299
         * {@inheritDoc}
2300
         *
2301
         * @see jp.co.future.uroborosql.SqlAgent#mergeWithLockingAndReturn(java.lang.Object)
2302
         */
2303
        @Override
2304
        public <E> E mergeWithLockingAndReturn(final E entity) {
2305
                return doMergeAndReturn(entity, true);
1✔
2306
        }
2307

2308
        /**
2309
         * エンティティのMERGE(INSERTまたはUPDATE)を実行し、MERGEしたエンティティを返却する.<br>
2310
         * locking引数が<code>true</code>の場合、マージの最初に発行するEntityの検索で悲観ロック(forUpdateNoWait)を行う
2311
         *
2312
         * @param <E> エンティティ型
2313
         * @param entity エンティティ
2314
         * @param locking エンティティの悲観ロックを行うかどうか
2315
         * @return MERGEしたエンティティ
2316
         *
2317
         */
2318
        @SuppressWarnings("unchecked")
2319
        private <E> E doMergeAndReturn(final E entity, final boolean locking) {
2320
                if (entity instanceof Stream) {
1✔
2321
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
2322
                }
2323

2324
                var handler = this.getEntityHandler();
1✔
2325
                if (!handler.getEntityType().isInstance(entity)) {
1✔
2326
                        throw new IllegalArgumentException("Entity type not supported.");
×
2327
                }
2328

2329
                var type = entity.getClass();
1✔
2330
                try {
2331
                        var metadata = handler.getMetadata(this.transactionManager, type);
1✔
2332
                        var keyColumns = metadata.getKeyColumns();
1✔
2333

2334
                        if (keyColumns.isEmpty()) {
1✔
2335
                                throw new IllegalArgumentException("Entity has no keys.");
×
2336
                        }
2337

2338
                        var mappingColumns = MappingUtils.getMappingColumnMap(metadata.getSchema(), type, SqlKind.UPDATE);
1✔
2339
                        var query = (SqlEntityQuery<E>) query(type);
1✔
2340
                        for (var column : keyColumns) {
1✔
2341
                                var camelName = column.getCamelColumnName();
1✔
2342
                                query.equal(camelName, mappingColumns.get(camelName).getValue(entity));
1✔
2343
                        }
1✔
2344
                        if (locking) {
1✔
2345
                                query.forUpdateNoWait();
1✔
2346
                        }
2347
                        return query.first()
1✔
2348
                                        .map(findEntity -> {
1✔
2349
                                                for (var mappingColumn : mappingColumns.values()) {
1✔
2350
                                                        if (!mappingColumn.isId() && !mappingColumn.isVersion()) {
1✔
2351
                                                                var value = mappingColumn.getValue(entity);
1✔
2352
                                                                if (value != null) {
1✔
2353
                                                                        mappingColumn.setValue(findEntity, value);
1✔
2354
                                                                }
2355
                                                        }
2356
                                                }
1✔
2357
                                                return updateAndReturn(findEntity);
1✔
2358
                                        }).orElseGet(() -> insertAndReturn(entity));
1✔
2359
                } catch (SQLException ex) {
×
2360
                        throw new EntitySqlRuntimeException(SqlKind.MERGE, ex);
×
2361
                }
2362
        }
2363

2364
        /**
2365
         * {@inheritDoc}
2366
         *
2367
         * @see jp.co.future.uroborosql.SqlAgent#delete(java.lang.Object)
2368
         */
2369
        @Override
2370
        public <E> int delete(final E entity) {
2371
                if (entity instanceof Stream) {
1✔
2372
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
2373
                }
2374

2375
                var handler = this.getEntityHandler();
1✔
2376
                if (!handler.getEntityType().isInstance(entity)) {
1✔
2377
                        throw new IllegalArgumentException("Entity type not supported");
×
2378
                }
2379

2380
                try {
2381
                        var entityType = entity.getClass();
1✔
2382
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2383
                        var context = handler.createDeleteContext(this, metadata, entityType, true);
1✔
2384
                        context.setSqlKind(SqlKind.ENTITY_DELETE);
1✔
2385

2386
                        // EntityDelete実行前イベント発行
2387
                        var eventListenerHolder = getSqlConfig().getEventListenerHolder();
1✔
2388
                        if (eventListenerHolder.hasBeforeEntityDeleteListener()) {
1✔
2389
                                var eventObj = new BeforeEntityDeleteEvent(context, entity, entityType);
1✔
2390
                                for (var listener : eventListenerHolder.getBeforeEntityDeleteListeners()) {
1✔
2391
                                        listener.accept(eventObj);
1✔
2392
                                }
1✔
2393
                        }
2394
                        handler.setDeleteParams(context, entity);
1✔
2395

2396
                        var count = handler.doDelete(this, context, entity);
1✔
2397

2398
                        // EntityDelete実行後イベント発行
2399
                        if (eventListenerHolder.hasAfterEntityDeleteListener()) {
1✔
2400
                                var eventObj = new AfterEntityDeleteEvent(context, entity, entityType, count);
1✔
2401
                                for (var listener : eventListenerHolder.getAfterEntityDeleteListeners()) {
1✔
2402
                                        listener.accept(eventObj);
1✔
2403
                                }
1✔
2404
                                count = eventObj.getCount();
1✔
2405
                        }
2406

2407
                        return count;
1✔
2408
                } catch (SQLException ex) {
×
2409
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_DELETE, ex);
×
2410
                }
2411
        }
2412

2413
        /**
2414
         * {@inheritDoc}
2415
         *
2416
         * @see jp.co.future.uroborosql.SqlAgent#deleteAndReturn(java.lang.Object)
2417
         */
2418
        @Override
2419
        public <E> E deleteAndReturn(final E entity) {
2420
                delete(entity);
1✔
2421
                return entity;
1✔
2422
        }
2423

2424
        /**
2425
         * {@inheritDoc}
2426
         *
2427
         * @see jp.co.future.uroborosql.SqlAgent#delete(java.lang.Class, java.lang.Object[])
2428
         */
2429
        @Override
2430
        public <E> int delete(final Class<? extends E> entityType, final Object... keys) {
2431
                var handler = this.getEntityHandler();
1✔
2432
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2433
                        throw new IllegalArgumentException("Entity type not supported");
×
2434
                }
2435

2436
                try {
2437
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2438
                        TableMetadata.Column keyColumn = null;
1✔
2439
                        var keyColumns = metadata.getKeyColumns();
1✔
2440
                        if (keyColumns.size() == 1) {
1✔
2441
                                keyColumn = keyColumns.get(0);
1✔
2442
                        } else if (keyColumns.isEmpty()) {
1✔
2443
                                keyColumn = metadata.getColumns().get(0);
1✔
2444
                        } else {
2445
                                throw new IllegalArgumentException("Entity has multiple keys");
1✔
2446
                        }
2447
                        return delete(entityType).in(keyColumn.getCamelColumnName(), keys).count();
1✔
2448
                } catch (SQLException ex) {
×
2449
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_DELETE, ex);
×
2450
                }
2451
        }
2452

2453
        /**
2454
         * {@inheritDoc}
2455
         *
2456
         * @see jp.co.future.uroborosql.SqlAgent#delete(java.lang.Class)
2457
         */
2458
        @Override
2459
        public <E> SqlEntityDelete<E> delete(final Class<? extends E> entityType) {
2460
                var handler = this.getEntityHandler();
1✔
2461
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2462
                        throw new IllegalArgumentException("Entity type not supported");
×
2463
                }
2464

2465
                try {
2466
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2467
                        var context = handler.createDeleteContext(this, metadata, entityType, false);
1✔
2468
                        context.setSqlKind(SqlKind.ENTITY_DELETE);
1✔
2469
                        return new SqlEntityDeleteImpl<>(this, handler, metadata, context, entityType);
1✔
2470
                } catch (SQLException ex) {
×
2471
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_DELETE, ex);
×
2472
                }
2473
        }
2474

2475
        /**
2476
         * {@inheritDoc}
2477
         *
2478
         * @see jp.co.future.uroborosql.SqlAgent#truncate(java.lang.Class)
2479
         */
2480
        @Override
2481
        public <E> SqlAgent truncate(final Class<? extends E> entityType) {
2482
                var handler = this.getEntityHandler();
1✔
2483
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
2484
                        throw new IllegalArgumentException("Entity type not supported");
×
2485
                }
2486
                try {
2487
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
2488
                        var context = this.context().setSql("truncate table " + metadata.getTableIdentifier());
1✔
2489
                        context.setSqlKind(SqlKind.TRUNCATE);
1✔
2490
                        update(context);
1✔
2491
                        return this;
1✔
2492
                } catch (SQLException ex) {
1✔
2493
                        throw new EntitySqlRuntimeException(SqlKind.TRUNCATE, ex);
1✔
2494
                }
2495
        }
2496

2497
        /**
2498
         * 自動採番カラムを取得する. 合わせて、{@link ExecutionContext#setGeneratedKeyColumns(String[])} に自動採番カラムを設定する.
2499
         * <pre>
2500
         * 自動採番カラムの条件
2501
         * 1. {@link MappingColumn#isId()} == true 、もしくは{@link TableMetadata.Column#isAutoincrement()} == true であること
2502
         * 2. 1.に加え、エンティティの対象フィールド型がprimitive型、もしくはフィールドの値が<code>null</code>であること
2503
         * </pre>
2504
         *
2505
         * @param <E> entity
2506
         * @param context ExecutionContext
2507
         * @param mappingColumns EntityのMappingColumn配列
2508
         * @param metadata TableMetadata
2509
         * @param entity Entity
2510
         * @return 自動採番カラム配列
2511
         */
2512
        private <E> List<MappingColumn> getAutoGeneratedColumns(final ExecutionContext context,
2513
                        final MappingColumn[] mappingColumns,
2514
                        final TableMetadata metadata,
2515
                        final E entity) {
2516
                var autoGeneratedColumns = new ArrayList<MappingColumn>();
1✔
2517
                var autoGeneratedColumnNames = new ArrayList<String>();
1✔
2518

2519
                if (mappingColumns != null && mappingColumns.length > 0) {
1✔
2520
                        for (var column : metadata.getColumns()) {
1✔
2521
                                if (column.isAutoincrement()) {
1✔
2522
                                        for (var mappingColumn : mappingColumns) {
1✔
2523
                                                if (mappingColumn.getCamelName().equals(column.getCamelColumnName())) {
1✔
2524
                                                        // primitive型か、エンティティの対象フィールドがnullとなっている場合自動生成カラムとする
2525
                                                        if (mappingColumn.getJavaType().getRawType().isPrimitive()
1✔
2526
                                                                        || mappingColumn.getValue(entity) == null) {
1✔
2527
                                                                autoGeneratedColumns.add(mappingColumn);
1✔
2528
                                                                autoGeneratedColumnNames.add(column.getColumnName());
1✔
2529
                                                        }
2530
                                                        break;
2531
                                                }
2532
                                        }
2533
                                }
2534
                        }
1✔
2535
                        for (var mappingColumn : mappingColumns) {
1✔
2536
                                if (!mappingColumn.isId() || autoGeneratedColumns.contains(mappingColumn)) {
1✔
2537
                                        continue;
1✔
2538
                                }
2539
                                for (var column : metadata.getColumns()) {
1✔
2540
                                        if (mappingColumn.getCamelName().equals(column.getCamelColumnName())) {
1✔
2541
                                                // primitive型か、エンティティの対象フィールドがnullとなっている場合自動生成カラムとする
2542
                                                if (mappingColumn.getJavaType().getRawType().isPrimitive()
1✔
2543
                                                                || mappingColumn.getValue(entity) == null) {
1✔
2544
                                                        autoGeneratedColumns.add(mappingColumn);
1✔
2545
                                                        autoGeneratedColumnNames.add(column.getColumnName());
1✔
2546
                                                }
2547
                                                break;
2548
                                        }
2549
                                }
1✔
2550
                        }
2551
                }
2552

2553
                if (!autoGeneratedColumnNames.isEmpty()) {
1✔
2554
                        context.setGeneratedKeyColumns(
1✔
2555
                                        autoGeneratedColumnNames.toArray(new String[autoGeneratedColumnNames.size()]));
1✔
2556
                }
2557
                return autoGeneratedColumns;
1✔
2558
        }
2559

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

2627
        /**
2628
         *
2629
         * {@inheritDoc}
2630
         *
2631
         * @see jp.co.future.uroborosql.SqlAgent#getSqlConfig()
2632
         */
2633
        @Override
2634
        public SqlConfig getSqlConfig() {
2635
                return this.sqlConfig;
1✔
2636
        }
2637

2638
        /**
2639
         *
2640
         * {@inheritDoc}
2641
         *
2642
         * @see jp.co.future.uroborosql.SqlAgent#context()
2643
         */
2644
        @Override
2645
        public ExecutionContext context() {
2646
                return sqlConfig.context();
1✔
2647
        }
2648

2649
        /**
2650
         * {@inheritDoc}
2651
         *
2652
         * @see jp.co.future.uroborosql.SqlAgent#getFetchSize()
2653
         */
2654
        @Override
2655
        public int getFetchSize() {
2656
                return fetchSize;
1✔
2657
        }
2658

2659
        /**
2660
         * {@inheritDoc}
2661
         *
2662
         * @see jp.co.future.uroborosql.SqlAgent#setFetchSize(int)
2663
         */
2664
        @Override
2665
        public SqlAgent setFetchSize(final int fetchSize) {
2666
                this.fetchSize = fetchSize;
1✔
2667
                return this;
1✔
2668
        }
2669

2670
        /**
2671
         * {@inheritDoc}
2672
         *
2673
         * @see jp.co.future.uroborosql.SqlAgent#getQueryTimeout()
2674
         */
2675
        @Override
2676
        public int getQueryTimeout() {
2677
                return queryTimeout;
1✔
2678
        }
2679

2680
        /**
2681
         * {@inheritDoc}
2682
         *
2683
         * @see jp.co.future.uroborosql.SqlAgent#setQueryTimeout(int)
2684
         */
2685
        @Override
2686
        public SqlAgent setQueryTimeout(final int queryTimeout) {
2687
                this.queryTimeout = queryTimeout;
1✔
2688
                return this;
1✔
2689
        }
2690

2691
        /**
2692
         * SQL実行をリトライするSQLエラーコードのリスト を取得します
2693
         *
2694
         * @return SQL実行をリトライするSQLエラーコードのリスト
2695
         */
2696
        public List<String> getSqlRetryCodes() {
2697
                return sqlRetryCodes;
1✔
2698
        }
2699

2700
        /**
2701
         * SQL実行をリトライするSQLエラーコードのリスト を設定します
2702
         *
2703
         * @param sqlRetryCodes SQL実行をリトライするSQLエラーコードのリスト
2704
         * @return SqlAgent
2705
         */
2706
        public SqlAgent setSqlRetryCodes(final List<String> sqlRetryCodes) {
2707
                this.sqlRetryCodes = sqlRetryCodes;
1✔
2708
                return this;
1✔
2709
        }
2710

2711
        /**
2712
         * 最大リトライ回数 を取得します
2713
         *
2714
         * @return 最大リトライ回数
2715
         */
2716
        public int getMaxRetryCount() {
2717
                return maxRetryCount;
1✔
2718
        }
2719

2720
        /**
2721
         * 最大リトライ回数 を設定します
2722
         *
2723
         * @param maxRetryCount 最大リトライ回数
2724
         * @return SqlAgent
2725
         */
2726
        public SqlAgent setMaxRetryCount(final int maxRetryCount) {
2727
                this.maxRetryCount = maxRetryCount;
1✔
2728
                return this;
1✔
2729
        }
2730

2731
        /**
2732
         * リトライタイムアウト時間(ms) を取得します
2733
         *
2734
         * @return リトライタイムアウト時間(ms)
2735
         */
2736
        public int getRetryWaitTime() {
2737
                return retryWaitTime;
1✔
2738
        }
2739

2740
        /**
2741
         * リトライタイムアウト時間(ms) を設定します
2742
         *
2743
         * @param retryWaitTime リトライタイムアウト時間(ms)
2744
         * @return SqlAgent
2745
         */
2746
        public SqlAgent setRetryWaitTime(final int retryWaitTime) {
2747
                this.retryWaitTime = retryWaitTime;
1✔
2748
                return this;
1✔
2749
        }
2750

2751
        /**
2752
         *
2753
         * {@inheritDoc}
2754
         *
2755
         * @see jp.co.future.uroborosql.SqlAgent#getMapKeyCaseFormat()
2756
         */
2757
        @Override
2758
        public CaseFormat getMapKeyCaseFormat() {
2759
                return mapKeyCaseFormat;
1✔
2760
        }
2761

2762
        /**
2763
         *
2764
         * {@inheritDoc}
2765
         *
2766
         * @see jp.co.future.uroborosql.SqlAgent#setMapKeyCaseFormat(jp.co.future.uroborosql.utils.CaseFormat)
2767
         */
2768
        @Override
2769
        public SqlAgent setMapKeyCaseFormat(final CaseFormat mapKeyCaseFormat) {
2770
                this.mapKeyCaseFormat = mapKeyCaseFormat;
1✔
2771
                return this;
1✔
2772
        }
2773

2774
        /**
2775
         *
2776
         * {@inheritDoc}
2777
         *
2778
         * @see jp.co.future.uroborosql.SqlAgent#getInsertsType()
2779
         */
2780
        @Override
2781
        public InsertsType getInsertsType() {
2782
                return this.insertsType;
1✔
2783
        }
2784

2785
        /**
2786
         *
2787
         * {@inheritDoc}
2788
         *
2789
         * @see jp.co.future.uroborosql.SqlAgent#setInsertsType(jp.co.future.uroborosql.enums.InsertsType)
2790
         */
2791
        @Override
2792
        public SqlAgent setInsertsType(final InsertsType defaultInsertsType) {
2793
                this.insertsType = defaultInsertsType;
1✔
2794
                return this;
1✔
2795
        }
2796

2797
        /**
2798
         * SQLリソース管理クラスを取得します。
2799
         *
2800
         * @return SQLリソース管理クラス
2801
         */
2802
        private SqlResourceManager getSqlResourceManager() {
2803
                return sqlConfig.getSqlResourceManager();
1✔
2804
        }
2805

2806
        /**
2807
         * ORM処理クラス を取得します。
2808
         *
2809
         * @return ORM処理クラス
2810
         */
2811
        @SuppressWarnings("unchecked")
2812
        private <E> EntityHandler<E> getEntityHandler() {
2813
                return (EntityHandler<E>) sqlConfig.getEntityHandler();
1✔
2814
        }
2815

2816
        /**
2817
         * Dialect を取得します。
2818
         *
2819
         * @return Dialect
2820
         */
2821
        private Dialect getDialect() {
2822
                return sqlConfig.getDialect();
1✔
2823
        }
2824

2825
        /**
2826
         * ステートメント初期化。
2827
         *
2828
         * @param executionContext ExecutionContext
2829
         * @return PreparedStatement
2830
         * @throws SQLException SQL例外
2831
         */
2832
        private PreparedStatement getPreparedStatement(final ExecutionContext executionContext) throws SQLException {
2833
                var stmt = ((LocalTransactionManager) transactionManager).getPreparedStatement(executionContext);
1✔
2834
                // プロパティ設定
2835
                applyProperties(stmt);
1✔
2836
                return stmt;
1✔
2837
        }
2838

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

2853
        /**
2854
         * フェッチサイズとクエリタイムアウトをPreparedStatementに設定する
2855
         *
2856
         * @param preparedStatement PreparedStatement
2857
         * @throws SQLException SQL例外
2858
         */
2859
        private void applyProperties(final PreparedStatement preparedStatement) throws SQLException {
2860
                // フェッチサイズ指定
2861
                if (getFetchSize() >= 0 && !(preparedStatement instanceof CallableStatement)) {
1✔
2862
                        preparedStatement.setFetchSize(getFetchSize());
1✔
2863
                }
2864

2865
                // クエリタイムアウト指定
2866
                if (getQueryTimeout() >= 0) {
1✔
2867
                        preparedStatement.setQueryTimeout(getQueryTimeout());
1✔
2868
                }
2869
        }
1✔
2870

2871
        /**
2872
         * 経過時間を計算し、HH:mm:ss.SSSSSSにフォーマットする.
2873
         *
2874
         * @param start 開始時間
2875
         * @param end 終了時間
2876
         * @return フォーマットした経過時間
2877
         */
2878
        private static String formatElapsedTime(final Instant start, final Instant end) {
2879
                return ELAPSED_TIME_FORMAT.format(LocalTime.MIDNIGHT.plus(Duration.between(start != null ? start : end, end)));
×
2880
        }
2881

2882
        /**
2883
         * ResultSetをStreamで扱うためのSpliterator
2884
         *
2885
         * @author H.Sugimoto
2886
         *
2887
         * @param <T> ResultSetの1行を変換した型
2888
         */
2889
        private static final class ResultSetSpliterator<T> extends Spliterators.AbstractSpliterator<T>
2890
                        implements ServiceLoggingSupport {
2891
                private final ResultSetConverter<T> converter;
2892
                private final ResultSet rs;
2893
                private boolean finished = false;
1✔
2894

2895
                private ResultSetSpliterator(final ResultSet rs, final ResultSetConverter<T> converter) {
2896
                        super(Long.MAX_VALUE, Spliterator.ORDERED);
1✔
2897
                        this.rs = rs;
1✔
2898
                        this.converter = converter;
1✔
2899
                }
1✔
2900

2901
                @Override
2902
                public boolean tryAdvance(final Consumer<? super T> action) {
2903
                        try {
2904
                                if (finished || !rs.next()) {
1✔
2905
                                        if (!rs.isClosed()) {
1✔
2906
                                                rs.close();
1✔
2907
                                        }
2908
                                        finished = true;
1✔
2909
                                        return false;
1✔
2910
                                }
2911
                                action.accept(converter.createRecord(rs));
1✔
2912
                                return true;
1✔
2913
                        } catch (RuntimeException | Error ex) {
1✔
2914
                                try {
2915
                                        if (rs != null && !rs.isClosed()) {
1✔
2916
                                                rs.close();
1✔
2917
                                        }
2918
                                } catch (SQLException ex2) {
×
2919
                                        errorWith(LOG)
×
2920
                                                        .setMessage(ex2.getMessage())
×
2921
                                                        .setCause(ex2)
×
2922
                                                        .log();
×
2923
                                }
1✔
2924
                                throw ex;
1✔
2925
                        } catch (SQLException ex) {
×
2926
                                try {
2927
                                        if (rs != null && !rs.isClosed()) {
×
2928
                                                rs.close();
×
2929
                                        }
2930
                                } catch (SQLException ex2) {
×
2931
                                        errorWith(LOG)
×
2932
                                                        .setMessage(ex2.getMessage())
×
2933
                                                        .setCause(ex2)
×
2934
                                                        .log();
×
2935
                                }
×
2936
                                throw new UroborosqlSQLException(ex);
×
2937
                        }
2938
                }
2939
        }
2940

2941
        /**
2942
         * ResultSetのラッパークラス。ResultSetのクローズに合わせてStatementもクローズする。
2943
         *
2944
         * @author H.Sugimoto
2945
         * @version 0.5.0
2946
         */
2947
        private static class InnerResultSet extends AbstractResultSetWrapper {
2948
                /** 同期してクローズするStatement */
2949
                private final Statement stmt;
2950

2951
                /**
2952
                 * コンストラクタ
2953
                 *
2954
                 * @param wrapped 元となるResultSet
2955
                 * @param stmt Statement
2956
                 */
2957
                InnerResultSet(final ResultSet wrapped, final Statement stmt) {
2958
                        super(wrapped);
1✔
2959
                        this.stmt = stmt;
1✔
2960
                }
1✔
2961

2962
                /**
2963
                 * {@inheritDoc}
2964
                 *
2965
                 * @see jp.co.future.uroborosql.AbstractResultSetWrapper#close()
2966
                 */
2967
                @Override
2968
                public void close() throws SQLException {
2969
                        try {
2970
                                super.close();
1✔
2971
                        } finally {
2972
                                try {
2973
                                        if (stmt != null && !stmt.isClosed()) {
1✔
2974
                                                stmt.close();
1✔
2975
                                        }
2976
                                } catch (SQLException ex) {
×
2977
                                        // do nothing
2978
                                }
1✔
2979
                        }
2980
                }
1✔
2981
        }
2982
}
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