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

future-architect / uroborosql / #833

03 May 2025 07:19AM UTC coverage: 89.875% (-0.04%) from 89.914%
#833

Pull #350

HidekiSugimoto189
add dialect test.
Pull Request #350: Add multi sqlfile bulk update method.

13 of 14 new or added lines in 3 files covered. (92.86%)

5 existing lines in 1 file now uncovered.

8699 of 9679 relevant lines covered (89.87%)

0.9 hits per line

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

89.04
/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.util.List)
531
         */
532
        @Override
533
        public SqlUpdate updateChained(final List<String> sqlNames) {
534
                if (sqlNames == null || sqlNames.isEmpty()) {
1✔
535
                        throw new IllegalArgumentException("sqlNames is required.");
1✔
536
                }
537
                if (sqlNames.size() == 1) {
1✔
538
                        return update(sqlNames.get(0));
1✔
539
                }
540
                if (!getDialect().supportsUpdateChained()) {
1✔
NEW
541
                        throw new UroborosqlRuntimeException(getDialect().getDatabaseName() + " does not support updateChained.");
×
542
                }
543
                var sqls = sqlNames.stream()
1✔
544
                                .map(sqlName -> getSqlResourceManager().getSql(sqlName))
1✔
545
                                .collect(Collectors.joining(";" + System.lineSeparator()));
1✔
546

547
                var sqlName = sqlNames.stream()
1✔
548
                                .collect(Collectors.joining(","));
1✔
549

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

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

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

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

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

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

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

625
        /**
626
         * {@inheritDoc}
627
         *
628
         * @see jp.co.future.uroborosql.SqlAgent#query(java.lang.Class)
629
         */
630
        @Override
631
        public <E> SqlEntityQuery<E> query(final Class<? extends E> entityType) {
632
                var handler = this.getEntityHandler();
1✔
633
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
634
                        throw new IllegalArgumentException("Entity type not supported");
×
635
                }
636
                try {
637
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
638
                        var context = handler.createSelectContext(this, metadata, entityType, false);
1✔
639
                        context.setSqlKind(SqlKind.ENTITY_SELECT);
1✔
640
                        return new SqlEntityQueryImpl<>(this, handler, metadata, context, entityType);
1✔
641
                } catch (SQLException ex) {
×
642
                        throw new EntitySqlRuntimeException(SqlKind.ENTITY_SELECT, ex);
×
643
                }
644
        }
645

646
        /**
647
         * {@inheritDoc}
648
         *
649
         * @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)
650
         */
651
        @Override
652
        public <E> int inserts(final Class<E> entityType,
653
                        final Stream<E> entities,
654
                        final InsertsCondition<? super E> condition,
655
                        final InsertsType insertsType) {
656
                if (insertsType == InsertsType.BULK && getDialect().supportsBulkInsert()) {
1✔
657
                        return bulkInsert(entityType, entities, condition, null);
1✔
658
                } else {
659
                        return batchInsert(entityType, entities, condition, null);
1✔
660
                }
661
        }
662

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1089
                return counts;
1✔
1090
        }
1091

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

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

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

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

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

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

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

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

1168
                                frameCount++;
1✔
1169

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

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

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

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

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

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

1224
                return count;
1✔
1225
        }
1226

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

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

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

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

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

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

1282
                                handler.setUpdateParams(context, entity);
1✔
1283
                                context.addBatch();
1✔
1284

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

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

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

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

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

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

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

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

1365
                return counts;
1✔
1366
        }
1367

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

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

1382
                var stmt = getPreparedStatement(executionContext);
1✔
1383

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

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

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

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

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

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

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

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

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

1534
                Instant startTime = null;
1✔
1535

1536
                try (var stmt = getPreparedStatement(executionContext)) {
1✔
1537

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

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

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

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

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

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

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

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

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

1663
                Instant startTime = null;
1✔
1664

1665
                try (var stmt = getPreparedStatement(executionContext)) {
1✔
1666

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

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

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

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

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

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

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

1785
                Instant startTime = null;
1✔
1786

1787
                try (var callableStatement = getCallableStatement(executionContext)) {
1✔
1788

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

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

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

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

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

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

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

1900
                        originalSql = originalSql.replace(keySqlId, sqlId);
1✔
1901
                }
1902

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

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

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

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

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

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

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

1962
                                COVERAGE_HANDLER_REF.get().accept(coverageData);
1✔
1963
                        }
1964
                }
1965

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

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

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

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

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

2024
                throw cause;
1✔
2025
        }
2026

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

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

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

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

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

2071
                        var results = handler.doSelect(this, context, entityType);
1✔
2072

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

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

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

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

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

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

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

2128
                        var count = handler.doInsert(this, context, entity);
1✔
2129

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

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

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

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

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

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

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

2197
                        var count = handler.doUpdate(this, context, entity);
1✔
2198

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2394
                        var count = handler.doDelete(this, context, entity);
1✔
2395

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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