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

future-architect / uroborosql / #715

pending completion
#715

push

web-flow
merge v0.x changes to master (#310)

* v0.x to master merge

* fix nanoseconds diff (java8 to java11)

* eclipse cleanup and  optimize import

* eclipse format

* optimize Imports

* remove unused annotation

* library version up

* migrate v0.x to master
- update java version 8 to 11
- update junit4 to junit5
- library version update

* fix test failed

* remove unused annotation

* fixed bug

* use var

* fix java8 coding style

1154 of 1154 new or added lines in 77 files covered. (100.0%)

7721 of 8533 relevant lines covered (90.48%)

0.9 hits per line

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

87.81
/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.ResultSet;
12
import java.sql.SQLException;
13
import java.sql.Statement;
14
import java.time.Duration;
15
import java.time.Instant;
16
import java.time.LocalTime;
17
import java.time.format.DateTimeFormatter;
18
import java.util.ArrayList;
19
import java.util.Arrays;
20
import java.util.Comparator;
21
import java.util.HashMap;
22
import java.util.List;
23
import java.util.Map;
24
import java.util.Objects;
25
import java.util.Optional;
26
import java.util.Spliterator;
27
import java.util.Spliterators;
28
import java.util.function.Consumer;
29
import java.util.stream.Collectors;
30
import java.util.stream.Stream;
31
import java.util.stream.StreamSupport;
32

33
import org.slf4j.Logger;
34
import org.slf4j.LoggerFactory;
35
import org.slf4j.MDC;
36

37
import jp.co.future.uroborosql.config.SqlConfig;
38
import jp.co.future.uroborosql.connection.ConnectionContext;
39
import jp.co.future.uroborosql.context.ExecutionContext;
40
import jp.co.future.uroborosql.context.ExecutionContextImpl;
41
import jp.co.future.uroborosql.converter.MapResultSetConverter;
42
import jp.co.future.uroborosql.converter.ResultSetConverter;
43
import jp.co.future.uroborosql.enums.SqlKind;
44
import jp.co.future.uroborosql.exception.EntitySqlRuntimeException;
45
import jp.co.future.uroborosql.exception.OptimisticLockException;
46
import jp.co.future.uroborosql.exception.PessimisticLockException;
47
import jp.co.future.uroborosql.exception.UroborosqlRuntimeException;
48
import jp.co.future.uroborosql.exception.UroborosqlSQLException;
49
import jp.co.future.uroborosql.fluent.SqlEntityDelete;
50
import jp.co.future.uroborosql.fluent.SqlEntityQuery;
51
import jp.co.future.uroborosql.fluent.SqlEntityUpdate;
52
import jp.co.future.uroborosql.mapping.EntityHandler;
53
import jp.co.future.uroborosql.mapping.MappingColumn;
54
import jp.co.future.uroborosql.mapping.MappingUtils;
55
import jp.co.future.uroborosql.mapping.TableMetadata;
56
import jp.co.future.uroborosql.utils.CaseFormat;
57

58
/**
59
 * SQL実行用クラス。
60
 *
61
 * @author H.Sugimoto
62
 */
63
public class SqlAgentImpl extends AbstractAgent {
64
        /** ロガー */
65
        private static final Logger LOG = LoggerFactory.getLogger("jp.co.future.uroborosql.log");
1✔
66

67
        /** SQLロガー */
68
        private static final Logger SQL_LOG = LoggerFactory.getLogger("jp.co.future.uroborosql.sql");
1✔
69

70
        /** パフォーマンスロガー */
71
        private static final Logger PERFORMANCE_LOG = LoggerFactory.getLogger("jp.co.future.uroborosql.performance");
1✔
72

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

76
        /** 例外発生時のログ出力を行うかどうか デフォルトは<code>true</code> */
77
        protected boolean outputExceptionLog = true;
1✔
78

79
        /** IN句に渡すパラメータのMAXサイズ */
80
        protected static final int IN_CLAUSE_MAX_PARAM_SIZE = 1000;
81

82
        /**
83
         * コンストラクタ。
84
         *
85
         * @param sqlConfig SQL設定管理クラス
86
         * @param settings 設定情報
87
         * @param connectionContext DB接続情報
88
         */
89
        protected SqlAgentImpl(final SqlConfig sqlConfig, final Map<String, String> settings,
90
                        final ConnectionContext connectionContext) {
91
                super(sqlConfig, settings, connectionContext);
1✔
92
                if (settings.containsKey(SqlAgentProviderImpl.PROPS_KEY_OUTPUT_EXCEPTION_LOG)) {
1✔
93
                        outputExceptionLog = Boolean
1✔
94
                                        .parseBoolean(settings.get(SqlAgentProviderImpl.PROPS_KEY_OUTPUT_EXCEPTION_LOG));
1✔
95
                }
96
        }
1✔
97

98
        /**
99
         * {@inheritDoc}
100
         *
101
         * @see jp.co.future.uroborosql.SqlAgent#query(jp.co.future.uroborosql.context.ExecutionContext)
102
         */
103
        @Override
104
        public ResultSet query(final ExecutionContext executionContext) throws SQLException {
105
                // パラメータログを出力する
106
                MDC.put(SUPPRESS_PARAMETER_LOG_OUTPUT, Boolean.FALSE.toString());
1✔
107

108
                if (SqlKind.NONE.equals(executionContext.getSqlKind())) {
1✔
109
                        executionContext.setSqlKind(SqlKind.SELECT);
1✔
110
                }
111

112
                // コンテキスト変換
113
                transformContext(executionContext, true);
1✔
114

115
                var stmt = getPreparedStatement(executionContext);
1✔
116

117
                // INパラメータ設定
118
                executionContext.bindParams(stmt);
1✔
119

120
                Instant startTime = null;
1✔
121
                if (LOG.isDebugEnabled()) {
1✔
122
                        LOG.debug("Execute search SQL.");
×
123
                }
124
                if (PERFORMANCE_LOG.isInfoEnabled()) {
1✔
125
                        startTime = Instant.now(getSqlConfig().getClock());
1✔
126
                }
127

128
                // 前処理
129
                beforeQuery(executionContext);
1✔
130

131
                try {
132
                        // デフォルト最大リトライ回数を取得し、個別指定(ExecutionContextの値)があれば上書き
133
                        var maxRetryCount = getMaxRetryCount();
1✔
134
                        if (executionContext.getMaxRetryCount() > 0) {
1✔
135
                                maxRetryCount = executionContext.getMaxRetryCount();
1✔
136
                        }
137

138
                        // デフォルトリトライ待機時間を取得し、個別指定(ExecutionContextの値)があれば上書き
139
                        var retryWaitTime = getRetryWaitTime();
1✔
140
                        if (executionContext.getRetryWaitTime() > 0) {
1✔
141
                                retryWaitTime = executionContext.getRetryWaitTime();
1✔
142
                        }
143
                        var loopCount = 0;
1✔
144
                        var dialect = getDialect();
1✔
145
                        ResultSet rs = null;
1✔
146
                        try {
147
                                do {
148
                                        try {
149
                                                if (maxRetryCount > 0 && dialect.isRollbackToSavepointBeforeRetry()) {
1✔
150
                                                        setSavepoint(RETRY_SAVEPOINT_NAME);
1✔
151
                                                }
152
                                                rs = new InnerResultSet(
1✔
153
                                                                getSqlFilterManager().doQuery(executionContext, stmt, stmt.executeQuery()),
1✔
154
                                                                stmt);
155
                                                stmt.closeOnCompletion();
1✔
156
                                                return rs;
1✔
157
                                        } catch (SQLException ex) {
1✔
158
                                                if (maxRetryCount > 0 && dialect.isRollbackToSavepointBeforeRetry()) {
1✔
159
                                                        rollback(RETRY_SAVEPOINT_NAME);
1✔
160
                                                }
161
                                                var errorCode = String.valueOf(ex.getErrorCode());
1✔
162
                                                var sqlState = ex.getSQLState();
1✔
163
                                                var pessimisticLockingErrorCodes = dialect.getPessimisticLockingErrorCodes();
1✔
164
                                                if (maxRetryCount > loopCount
1✔
165
                                                                && (getSqlRetryCodes().contains(errorCode) || getSqlRetryCodes().contains(sqlState))) {
1✔
166
                                                        if (LOG.isDebugEnabled()) {
1✔
167
                                                                LOG.debug(String.format(
×
168
                                                                                "Caught the error code to be retried.(%d times). Retry after %,3d ms.",
169
                                                                                loopCount + 1, retryWaitTime));
×
170
                                                        }
171
                                                        if (retryWaitTime > 0) {
1✔
172
                                                                try {
173
                                                                        Thread.sleep(retryWaitTime);
1✔
174
                                                                } catch (InterruptedException ie) {
×
175
                                                                        // do nothing
176
                                                                }
1✔
177
                                                        }
178
                                                } else {
179
                                                        if (pessimisticLockingErrorCodes.contains(errorCode)
1✔
180
                                                                        || pessimisticLockingErrorCodes.contains(sqlState)) {
1✔
181
                                                                throw new PessimisticLockException(executionContext, ex);
1✔
182
                                                        } else {
183
                                                                throw ex;
1✔
184
                                                        }
185
                                                }
186
                                        } finally {
187
                                                if (maxRetryCount > 0 && dialect.isRollbackToSavepointBeforeRetry()) {
1✔
188
                                                        releaseSavepoint(RETRY_SAVEPOINT_NAME);
1✔
189
                                                }
190
                                                executionContext.contextAttrs().put(CTX_ATTR_KEY_RETRY_COUNT, loopCount);
1✔
191
                                        }
192
                                } while (maxRetryCount > loopCount++);
1✔
193
                        } catch (SQLException | RuntimeException e) {
1✔
194
                                if (rs != null && !rs.isClosed()) {
1✔
195
                                        rs.close();
×
196
                                }
197
                                throw e;
1✔
198
                        }
×
199
                        return null;
×
200
                } catch (SQLException ex) {
1✔
201
                        handleException(executionContext, ex);
×
202
                        return null;
×
203
                } finally {
204
                        // 後処理
205
                        afterQuery(executionContext);
1✔
206
                        if (PERFORMANCE_LOG.isInfoEnabled() && startTime != null) {
1✔
207
                                PERFORMANCE_LOG.info("SQL execution time [{}({})] : [{}]",
1✔
208
                                                generateSqlName(executionContext),
1✔
209
                                                executionContext.getSqlKind(),
1✔
210
                                                formatElapsedTime(startTime, Instant.now(getSqlConfig().getClock())));
1✔
211
                        }
212
                        MDC.remove(SUPPRESS_PARAMETER_LOG_OUTPUT);
1✔
213
                }
214
        }
215

216
        /**
217
         * SQL検索前処理 拡張ポイント。子クラスでオーバーライドする
218
         *
219
         * @param executionContext ExecutionContext
220
         */
221
        protected void beforeQuery(final ExecutionContext executionContext) {
222
        }
1✔
223

224
        /**
225
         * SQL検索後処理 拡張ポイント。子クラスでオーバーライドする
226
         *
227
         * @param executionContext ExecutionContext
228
         */
229
        protected void afterQuery(final ExecutionContext executionContext) {
230
        }
1✔
231

232
        /**
233
         * {@inheritDoc}
234
         *
235
         * @see jp.co.future.uroborosql.SqlAgent#query(jp.co.future.uroborosql.context.ExecutionContext,
236
         *      jp.co.future.uroborosql.converter.ResultSetConverter)
237
         */
238
        @Override
239
        public <T> Stream<T> query(final ExecutionContext executionContext, final ResultSetConverter<T> converter)
240
                        throws SQLException {
241
                var rs = query(executionContext);
1✔
242
                return StreamSupport.stream(new ResultSetSpliterator<>(rs, converter), false).onClose(() -> {
1✔
243
                        try {
244
                                if (rs != null && !rs.isClosed()) {
1✔
245
                                        rs.close();
1✔
246
                                }
247
                        } catch (SQLException ex) {
×
248
                                // do nothing
249
                        }
1✔
250
                });
1✔
251
        }
252

253
        /**
254
         * {@inheritDoc}
255
         *
256
         * @see jp.co.future.uroborosql.SqlAgent#query(jp.co.future.uroborosql.context.ExecutionContext,
257
         *      jp.co.future.uroborosql.utils.CaseFormat)
258
         */
259
        @Override
260
        public List<Map<String, Object>> query(final ExecutionContext executionContext, final CaseFormat caseFormat)
261
                        throws SQLException {
262
                try (var stream = query(executionContext,
1✔
263
                                new MapResultSetConverter(getSqlConfig(), caseFormat))) {
1✔
264
                        return stream.collect(Collectors.toList());
1✔
265
                }
266
        }
267

268
        /**
269
         * @see jp.co.future.uroborosql.SqlAgent#update(jp.co.future.uroborosql.context.ExecutionContext)
270
         */
271
        @Override
272
        public int update(final ExecutionContext executionContext) throws SQLException {
273
                // パラメータログを出力する
274
                MDC.put(SUPPRESS_PARAMETER_LOG_OUTPUT, Boolean.FALSE.toString());
1✔
275

276
                if (SqlKind.NONE.equals(executionContext.getSqlKind())) {
1✔
277
                        executionContext.setSqlKind(SqlKind.UPDATE);
1✔
278
                }
279

280
                // コンテキスト変換
281
                transformContext(executionContext, false);
1✔
282

283
                // 更新移譲処理の指定がある場合は移譲処理を実行し結果を返却
284
                if (executionContext.getUpdateDelegate() != null) {
1✔
285
                        MDC.remove(SUPPRESS_PARAMETER_LOG_OUTPUT);
1✔
286
                        LOG.debug("Performs update delegate of update process");
1✔
287
                        return executionContext.getUpdateDelegate().apply(executionContext);
1✔
288
                }
289

290
                Instant startTime = null;
1✔
291

292
                try (var stmt = getPreparedStatement(executionContext)) {
1✔
293

294
                        // INパラメータ設定
295
                        executionContext.bindParams(stmt);
1✔
296

297
                        if (LOG.isDebugEnabled()) {
1✔
298
                                LOG.debug("Execute update SQL.");
×
299
                        }
300
                        if (PERFORMANCE_LOG.isInfoEnabled()) {
1✔
301
                                startTime = Instant.now(getSqlConfig().getClock());
1✔
302
                        }
303

304
                        // 前処理
305
                        beforeUpdate(executionContext);
1✔
306

307
                        // デフォルト最大リトライ回数を取得し、個別指定(ExecutionContextの値)があれば上書き
308
                        var maxRetryCount = getMaxRetryCount();
1✔
309
                        if (executionContext.getMaxRetryCount() > 0) {
1✔
310
                                maxRetryCount = executionContext.getMaxRetryCount();
1✔
311
                        }
312

313
                        // デフォルトリトライ待機時間を取得し、個別指定(ExecutionContextの値)があれば上書き
314
                        var retryWaitTime = getRetryWaitTime();
1✔
315
                        if (executionContext.getRetryWaitTime() > 0) {
1✔
316
                                retryWaitTime = executionContext.getRetryWaitTime();
1✔
317
                        }
318
                        var loopCount = 0;
1✔
319
                        do {
320
                                try {
321
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
322
                                                setSavepoint(RETRY_SAVEPOINT_NAME);
1✔
323
                                        }
324
                                        var count = getSqlFilterManager().doUpdate(executionContext, stmt, stmt.executeUpdate());
1✔
325
                                        if ((SqlKind.INSERT.equals(executionContext.getSqlKind()) ||
1✔
326
                                                        SqlKind.BULK_INSERT.equals(executionContext.getSqlKind()))
1✔
327
                                                        && executionContext.hasGeneratedKeyColumns()) {
1✔
328
                                                try (var rs = stmt.getGeneratedKeys()) {
1✔
329
                                                        var generatedKeyValues = new ArrayList<>();
1✔
330
                                                        while (rs.next()) {
1✔
331
                                                                for (var i = 1; i <= executionContext.getGeneratedKeyColumns().length; i++) {
1✔
332
                                                                        generatedKeyValues.add(rs.getObject(i));
1✔
333
                                                                }
334
                                                        }
335
                                                        executionContext.setGeneratedKeyValues(
1✔
336
                                                                        generatedKeyValues.toArray(new Object[generatedKeyValues.size()]));
1✔
337
                                                }
338
                                        }
339
                                        return count;
1✔
340
                                } catch (SQLException ex) {
1✔
341
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
342
                                                rollback(RETRY_SAVEPOINT_NAME);
1✔
343
                                        }
344
                                        if (maxRetryCount > loopCount) {
1✔
345
                                                var errorCode = String.valueOf(ex.getErrorCode());
1✔
346
                                                var sqlState = ex.getSQLState();
1✔
347
                                                if (getSqlRetryCodes().contains(errorCode) || getSqlRetryCodes().contains(sqlState)) {
1✔
348
                                                        if (LOG.isDebugEnabled()) {
1✔
349
                                                                LOG.debug(String.format(
×
350
                                                                                "Caught the error code to be retried.(%d times). Retry after %,3d ms.",
351
                                                                                loopCount + 1, retryWaitTime));
×
352
                                                        }
353
                                                        if (retryWaitTime > 0) {
1✔
354
                                                                try {
355
                                                                        Thread.sleep(retryWaitTime);
1✔
356
                                                                } catch (InterruptedException ie) {
×
357
                                                                        // do nothing
358
                                                                }
1✔
359
                                                        }
360
                                                } else {
361
                                                        throw ex;
1✔
362
                                                }
363
                                        } else {
1✔
364
                                                throw ex;
1✔
365
                                        }
366
                                } finally {
367
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
368
                                                releaseSavepoint(RETRY_SAVEPOINT_NAME);
1✔
369
                                        }
370
                                        executionContext.contextAttrs().put(CTX_ATTR_KEY_RETRY_COUNT, loopCount);
1✔
371
                                }
372
                        } while (maxRetryCount > loopCount++);
1✔
373
                        return 0;
×
374
                } catch (SQLException ex) {
1✔
375
                        handleException(executionContext, ex);
×
376
                        return 0;
×
377
                } finally {
378
                        afterUpdate(executionContext);
1✔
379
                        if (PERFORMANCE_LOG.isInfoEnabled() && startTime != null) {
1✔
380
                                PERFORMANCE_LOG.info("SQL execution time [{}({})] : [{}]",
1✔
381
                                                generateSqlName(executionContext),
1✔
382
                                                executionContext.getSqlKind(),
1✔
383
                                                formatElapsedTime(startTime, Instant.now(getSqlConfig().getClock())));
1✔
384
                        }
385
                        MDC.remove(SUPPRESS_PARAMETER_LOG_OUTPUT);
1✔
386
                }
387
        }
388

389
        /**
390
         * SQL更新前処理 拡張ポイント。子クラスでオーバーライドする
391
         *
392
         * @param executionContext ExecutionContext
393
         */
394
        protected void beforeUpdate(final ExecutionContext executionContext) {
395
        }
1✔
396

397
        /**
398
         * SQL更新後処理 拡張ポイント。子クラスでオーバーライドする
399
         *
400
         * @param executionContext ExecutionContext
401
         */
402
        protected void afterUpdate(final ExecutionContext executionContext) {
403
        }
1✔
404

405
        /**
406
         * @see jp.co.future.uroborosql.SqlAgent#batch(jp.co.future.uroborosql.context.ExecutionContext)
407
         */
408
        @Override
409
        public int[] batch(final ExecutionContext executionContext) throws SQLException {
410
                // バッチ処理の場合大量のログが出力されるため、パラメータログの出力を抑止する
411
                MDC.put(SUPPRESS_PARAMETER_LOG_OUTPUT, Boolean.TRUE.toString());
1✔
412

413
                if (SqlKind.NONE.equals(executionContext.getSqlKind())) {
1✔
414
                        executionContext.setSqlKind(SqlKind.BATCH_INSERT);
1✔
415
                }
416

417
                // コンテキスト変換
418
                transformContext(executionContext, false);
1✔
419

420
                // 更新移譲処理の指定がある場合は移譲処理を実行し結果を返却
421
                if (executionContext.getUpdateDelegate() != null) {
1✔
422
                        MDC.remove(SUPPRESS_PARAMETER_LOG_OUTPUT);
1✔
423
                        LOG.debug("Performs update delegate of batch process");
1✔
424
                        return new int[] { executionContext.getUpdateDelegate().apply(executionContext) };
1✔
425
                }
426

427
                Instant startTime = null;
1✔
428

429
                try (var stmt = getPreparedStatement(executionContext)) {
1✔
430

431
                        // INパラメータ設定
432
                        executionContext.bindBatchParams(stmt);
1✔
433

434
                        if (LOG.isDebugEnabled()) {
1✔
435
                                LOG.debug("Execute batch process.");
×
436
                        }
437
                        if (PERFORMANCE_LOG.isInfoEnabled()) {
1✔
438
                                startTime = Instant.now(getSqlConfig().getClock());
1✔
439
                        }
440

441
                        // 前処理
442
                        beforeBatch(executionContext);
1✔
443

444
                        // デフォルト最大リトライ回数を取得し、個別指定(ExecutionContextの値)があれば上書き
445
                        var maxRetryCount = getMaxRetryCount();
1✔
446
                        if (executionContext.getMaxRetryCount() > 0) {
1✔
447
                                maxRetryCount = executionContext.getMaxRetryCount();
1✔
448
                        }
449

450
                        // デフォルトリトライ待機時間を取得し、個別指定(ExecutionContextの値)があれば上書き
451
                        var retryWaitTime = getRetryWaitTime();
1✔
452
                        if (executionContext.getRetryWaitTime() > 0) {
1✔
453
                                retryWaitTime = executionContext.getRetryWaitTime();
×
454
                        }
455
                        var loopCount = 0;
1✔
456
                        do {
457
                                try {
458
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
459
                                                setSavepoint(RETRY_SAVEPOINT_NAME);
×
460
                                        }
461
                                        var counts = getSqlFilterManager().doBatch(executionContext, stmt, stmt.executeBatch());
1✔
462
                                        if (SqlKind.BATCH_INSERT.equals(executionContext.getSqlKind())
1✔
463
                                                        && executionContext.hasGeneratedKeyColumns()) {
1✔
464
                                                try (var rs = stmt.getGeneratedKeys()) {
1✔
465
                                                        var generatedKeyValues = new ArrayList<>();
1✔
466
                                                        while (rs.next()) {
1✔
467
                                                                for (var i = 1; i <= executionContext.getGeneratedKeyColumns().length; i++) {
1✔
468
                                                                        generatedKeyValues.add(rs.getObject(i));
1✔
469
                                                                }
470
                                                        }
471
                                                        executionContext.setGeneratedKeyValues(
1✔
472
                                                                        generatedKeyValues.toArray(new Object[generatedKeyValues.size()]));
1✔
473
                                                }
474
                                        }
475
                                        return counts;
1✔
476
                                } catch (SQLException ex) {
1✔
477
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
478
                                                rollback(RETRY_SAVEPOINT_NAME);
×
479
                                        }
480
                                        if (maxRetryCount > loopCount) {
1✔
481
                                                var errorCode = String.valueOf(ex.getErrorCode());
1✔
482
                                                var sqlState = ex.getSQLState();
1✔
483
                                                if (getSqlRetryCodes().contains(errorCode) || getSqlRetryCodes().contains(sqlState)) {
1✔
484
                                                        if (LOG.isDebugEnabled()) {
1✔
485
                                                                LOG.debug(String.format(
×
486
                                                                                "Caught the error code to be retried.(%d times). Retry after %,3d ms.",
487
                                                                                loopCount + 1, retryWaitTime));
×
488
                                                        }
489
                                                        if (retryWaitTime > 0) {
1✔
490
                                                                try {
491
                                                                        Thread.sleep(retryWaitTime);
×
492
                                                                } catch (InterruptedException ie) {
×
493
                                                                        // do nothing
494
                                                                }
×
495
                                                        }
496
                                                } else {
497
                                                        throw ex;
×
498
                                                }
499
                                        } else {
1✔
500
                                                throw ex;
1✔
501
                                        }
502
                                } finally {
503
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
504
                                                releaseSavepoint(RETRY_SAVEPOINT_NAME);
×
505
                                        }
506
                                        executionContext.clearBatch();
1✔
507
                                        executionContext.contextAttrs().put(CTX_ATTR_KEY_RETRY_COUNT, loopCount);
1✔
508
                                }
509
                        } while (maxRetryCount > loopCount++);
1✔
510
                        return null;
×
511
                } catch (SQLException ex) {
1✔
512
                        handleException(executionContext, ex);
×
513
                        return null;
×
514
                } finally {
515
                        // 後処理
516
                        afterBatch(executionContext);
1✔
517
                        if (PERFORMANCE_LOG.isInfoEnabled() && startTime != null) {
1✔
518
                                PERFORMANCE_LOG.info("SQL execution time [{}({})] : [{}]",
1✔
519
                                                generateSqlName(executionContext),
1✔
520
                                                executionContext.getSqlKind(),
1✔
521
                                                formatElapsedTime(startTime, Instant.now(getSqlConfig().getClock())));
1✔
522
                        }
523
                        MDC.remove(SUPPRESS_PARAMETER_LOG_OUTPUT);
1✔
524
                }
525
        }
526

527
        /**
528
         * SQLバッチ前処理 拡張ポイント。子クラスでオーバーライドする
529
         *
530
         * @param executionContext ExecutionContext
531
         */
532
        protected void beforeBatch(final ExecutionContext executionContext) {
533
        }
1✔
534

535
        /**
536
         * SQLバッチ処理 拡張ポイント。子クラスでオーバーライドする
537
         *
538
         * @param executionContext ExecutionContext
539
         */
540
        protected void afterBatch(final ExecutionContext executionContext) {
541
        }
1✔
542

543
        /**
544
         * {@inheritDoc}
545
         *
546
         * @see jp.co.future.uroborosql.SqlAgent#procedure(jp.co.future.uroborosql.context.ExecutionContext)
547
         */
548
        @Override
549
        public Map<String, Object> procedure(final ExecutionContext executionContext) throws SQLException {
550
                // パラメータログを出力する
551
                MDC.put(SUPPRESS_PARAMETER_LOG_OUTPUT, Boolean.FALSE.toString());
1✔
552

553
                // procedureやfunctionの場合、SQL文法エラーになるためバインドパラメータコメントを出力しない
554
                executionContext.contextAttrs().put(CTX_ATTR_KEY_OUTPUT_BIND_COMMENT, false);
1✔
555

556
                if (SqlKind.NONE.equals(executionContext.getSqlKind())) {
1✔
557
                        executionContext.setSqlKind(SqlKind.PROCEDURE);
1✔
558
                }
559

560
                // コンテキスト変換
561
                transformContext(executionContext, false);
1✔
562

563
                Instant startTime = null;
1✔
564

565
                try (var callableStatement = getCallableStatement(executionContext)) {
1✔
566

567
                        // パラメータ設定
568
                        executionContext.bindParams(callableStatement);
1✔
569

570
                        if (LOG.isDebugEnabled()) {
1✔
571
                                LOG.debug("Execute stored procedure.");
×
572
                        }
573
                        if (PERFORMANCE_LOG.isInfoEnabled()) {
1✔
574
                                startTime = Instant.now(getSqlConfig().getClock());
1✔
575
                        }
576

577
                        beforeProcedure(executionContext);
1✔
578

579
                        // デフォルト最大リトライ回数を取得し、個別指定(ExecutionContextの値)があれば上書き
580
                        var maxRetryCount = getMaxRetryCount();
1✔
581
                        if (executionContext.getMaxRetryCount() > 0) {
1✔
582
                                maxRetryCount = executionContext.getMaxRetryCount();
1✔
583
                        }
584

585
                        // デフォルトリトライ待機時間を取得し、個別指定(ExecutionContextの値)があれば上書き
586
                        var retryWaitTime = getRetryWaitTime();
1✔
587
                        if (executionContext.getRetryWaitTime() > 0) {
1✔
588
                                retryWaitTime = executionContext.getRetryWaitTime();
1✔
589
                        }
590
                        var loopCount = 0;
1✔
591
                        do {
592
                                try {
593
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
594
                                                setSavepoint(RETRY_SAVEPOINT_NAME);
1✔
595
                                        }
596
                                        getSqlFilterManager().doProcedure(executionContext, callableStatement, callableStatement.execute());
1✔
597
                                        break;
598
                                } catch (SQLException ex) {
1✔
599
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
600
                                                rollback(RETRY_SAVEPOINT_NAME);
1✔
601
                                        }
602
                                        if (maxRetryCount > loopCount) {
1✔
603
                                                var errorCode = String.valueOf(ex.getErrorCode());
1✔
604
                                                var sqlState = ex.getSQLState();
1✔
605
                                                if (getSqlRetryCodes().contains(errorCode) || getSqlRetryCodes().contains(sqlState)) {
1✔
606
                                                        if (LOG.isDebugEnabled()) {
1✔
607
                                                                LOG.debug(String.format(
×
608
                                                                                "Caught the error code to be retried.(%d times). Retry after %,3d ms.",
609
                                                                                loopCount + 1, retryWaitTime));
×
610
                                                        }
611
                                                        if (retryWaitTime > 0) {
1✔
612
                                                                try {
613
                                                                        Thread.sleep(retryWaitTime);
1✔
614
                                                                } catch (InterruptedException ie) {
×
615
                                                                        // do nothing
616
                                                                }
1✔
617
                                                        }
618
                                                } else {
619
                                                        throw ex;
1✔
620
                                                }
621
                                        } else {
1✔
622
                                                throw ex;
1✔
623
                                        }
624
                                } finally {
625
                                        if (maxRetryCount > 0 && getDialect().isRollbackToSavepointBeforeRetry()) {
1✔
626
                                                releaseSavepoint(RETRY_SAVEPOINT_NAME);
1✔
627
                                        }
628
                                        executionContext.contextAttrs().put(CTX_ATTR_KEY_RETRY_COUNT, loopCount);
1✔
629
                                }
630
                        } while (maxRetryCount > loopCount++);
1✔
631
                        // 結果取得
632
                        return executionContext.getOutParams(callableStatement);
1✔
633
                } catch (SQLException ex) {
1✔
634
                        handleException(executionContext, ex);
×
635
                } finally {
636
                        afterProcedure(executionContext);
1✔
637
                        if (PERFORMANCE_LOG.isInfoEnabled() && startTime != null) {
1✔
638
                                PERFORMANCE_LOG.info("Stored procedure execution time [{}({})] : [{}]",
1✔
639
                                                generateSqlName(executionContext),
1✔
640
                                                executionContext.getSqlKind(),
1✔
641
                                                formatElapsedTime(startTime, Instant.now(getSqlConfig().getClock())));
1✔
642
                        }
643
                        MDC.remove(SUPPRESS_PARAMETER_LOG_OUTPUT);
1✔
644
                }
645
                return null;
×
646
        }
647

648
        /**
649
         * ストプロ実行前処理 拡張ポイント。子クラスでオーバーライドする
650
         *
651
         * @param executionContext ExecutionContext
652
         */
653
        protected void beforeProcedure(final ExecutionContext executionContext) {
654

655
        }
1✔
656

657
        /**
658
         * ストプロ実行後処理 拡張ポイント。子クラスでオーバーライドする
659
         *
660
         * @param executionContext ExecutionContext
661
         */
662
        protected void afterProcedure(final ExecutionContext executionContext) {
663
        }
1✔
664

665
        /** 時間計測用のログに出力するSQL名を生成する.
666
         *
667
         * @param executionContext ExecutionContext
668
         * @return SQL名. SQL名が取得できない場合はSQL_ID、または空文字を返却する
669
         */
670
        protected String generateSqlName(final ExecutionContext executionContext) {
671
                if (executionContext.getSqlName() != null) {
1✔
672
                        return executionContext.getSqlName();
1✔
673
                } else {
674
                        return Objects.toString(executionContext.getSqlId(), "");
1✔
675
                }
676
        }
677

678
        /**
679
         * {@inheritDoc}
680
         *
681
         * @see jp.co.future.uroborosql.AbstractAgent#handleException(jp.co.future.uroborosql.context.ExecutionContext,
682
         *      java.sql.SQLException)
683
         */
684
        @Override
685
        protected void handleException(final ExecutionContext executionContext, final SQLException ex) throws SQLException {
686
                var cause = ex;
1✔
687

688
                while (cause.getNextException() != null) {
1✔
689
                        cause = cause.getNextException();
1✔
690
                }
691

692
                if (LOG.isErrorEnabled() && isOutputExceptionLog()) {
1✔
693
                        var builder = new StringBuilder();
1✔
694
                        builder.append(System.lineSeparator()).append("Exception occurred in SQL execution.")
1✔
695
                                        .append(System.lineSeparator());
1✔
696
                        builder.append("Executed SQL[").append(executionContext.getExecutableSql()).append("]")
1✔
697
                                        .append(System.lineSeparator());
1✔
698
                        if (executionContext instanceof ExecutionContextImpl) {
1✔
699
                                var bindParameters = ((ExecutionContextImpl) executionContext).getBindParameters();
1✔
700
                                for (var i = 0; i < bindParameters.length; i++) {
1✔
701
                                        var parameter = getSqlFilterManager().doParameter(bindParameters[i]);
1✔
702
                                        builder.append("Bind Parameter.[INDEX[").append(i + 1).append("], ").append(parameter.toString())
1✔
703
                                                        .append("]").append(System.lineSeparator());
1✔
704
                                }
705
                        }
706
                        LOG.error(builder.toString(), cause);
1✔
707
                }
708

709
                throw cause;
1✔
710
        }
711

712
        /**
713
         * 例外発生時のログ出力を行うかどうかを取得します。
714
         *
715
         * @return 例外発生時のログ出力を行うかどうか
716
         */
717
        protected boolean isOutputExceptionLog() {
718
                return outputExceptionLog;
1✔
719
        }
720

721
        /**
722
         * 例外発生時のログ出力を行うかどうかを設定します。
723
         *
724
         * @param outputExceptionLog 例外発生時のログ出力を行うかどうか。ログ出力する場合は<code>true</code>
725
         */
726
        protected void setOutputExceptionLog(final boolean outputExceptionLog) {
727
                this.outputExceptionLog = outputExceptionLog;
×
728
        }
×
729

730
        @SuppressWarnings("unchecked")
731
        @Override
732
        public <E> Optional<E> find(final Class<? extends E> entityType, final Object... keys) {
733
                @SuppressWarnings("rawtypes")
734
                EntityHandler handler = this.getEntityHandler();
1✔
735
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
736
                        throw new IllegalArgumentException("Entity type not supported");
×
737
                }
738

739
                try {
740
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
741

742
                        var keyNames = metadata.getColumns().stream()
1✔
743
                                        .filter(TableMetadata.Column::isKey)
1✔
744
                                        .sorted(Comparator.comparingInt(TableMetadata.Column::getKeySeq))
1✔
745
                                        .map(TableMetadata.Column::getColumnName)
1✔
746
                                        .map(CaseFormat.CAMEL_CASE::convert)
1✔
747
                                        .toArray(String[]::new);
1✔
748

749
                        if (keyNames.length != keys.length) {
1✔
750
                                throw new IllegalArgumentException("Number of keys does not match");
×
751
                        }
752
                        var params = new HashMap<String, Object>();
1✔
753
                        for (var i = 0; i < keys.length; i++) {
1✔
754
                                params.put(keyNames[i], keys[i]);
1✔
755
                        }
756

757
                        var context = handler.createSelectContext(this, metadata, entityType, true);
1✔
758
                        context.paramMap(params);
1✔
759

760
                        try (var stream = handler.doSelect(this, context, entityType)) {
1✔
761
                                return stream.findFirst();
1✔
762
                        }
763
                } catch (SQLException e) {
×
764
                        throw new EntitySqlRuntimeException(SqlKind.SELECT, e);
×
765
                }
766
        }
767

768
        /**
769
         * {@inheritDoc}
770
         *
771
         * @see jp.co.future.uroborosql.SqlAgent#insert(java.lang.Object)
772
         */
773
        @SuppressWarnings("unchecked")
774
        @Override
775
        public <E> int insert(final E entity) {
776
                if (entity instanceof Stream) {
1✔
777
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
778
                }
779

780
                @SuppressWarnings("rawtypes")
781
                EntityHandler handler = this.getEntityHandler();
1✔
782
                if (!handler.getEntityType().isInstance(entity)) {
1✔
783
                        throw new IllegalArgumentException("Entity type not supported");
×
784
                }
785

786
                try {
787
                        Class<?> entityType = entity.getClass();
1✔
788
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
789
                        var context = handler.createInsertContext(this, metadata, entityType);
1✔
790
                        context.setSqlKind(SqlKind.INSERT);
1✔
791

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

796
                        handler.setInsertParams(context, entity);
1✔
797
                        var count = handler.doInsert(this, context, entity);
1✔
798

799
                        if (!autoGeneratedColumns.isEmpty()) {
1✔
800
                                var ids = context.getGeneratedKeyValues();
1✔
801
                                var idx = 0;
1✔
802
                                for (var col : autoGeneratedColumns) {
1✔
803
                                        setEntityIdValue(entity, ids[idx++], col);
1✔
804
                                }
1✔
805
                        }
806

807
                        return count;
1✔
808
                } catch (SQLException e) {
1✔
809
                        throw new EntitySqlRuntimeException(SqlKind.INSERT, e);
1✔
810
                }
811
        }
812

813
        /**
814
         * {@inheritDoc}
815
         *
816
         * @see jp.co.future.uroborosql.SqlAgent#insertAndReturn(java.lang.Object)
817
         */
818
        @Override
819
        public <E> E insertAndReturn(final E entity) {
820
                insert(entity);
1✔
821
                return entity;
1✔
822
        }
823

824
        /**
825
         * 自動採番カラムを取得する. 合わせて、{@link ExecutionContext#setGeneratedKeyColumns(String[])} に自動採番カラムを設定する.
826
         * <pre>
827
         * 自動採番カラムの条件
828
         * 1. {@link MappingColumn#isId()} == true 、もしくは{@link TableMetadata.Column#isAutoincrement()} == true であること
829
         * 2. 1.に加え、エンティティの対象フィールド型がprimitive型、もしくはフィールドの値が<code>null</code>であること
830
         * </pre>
831
         *
832
         * @param <E> entity
833
         * @param context ExecutionContext
834
         * @param mappingColumns EntityのMappingColumn配列
835
         * @param metadata TableMetadata
836
         * @param entity Entity
837
         * @return 自動採番カラム配列
838
         */
839
        protected <E> List<MappingColumn> getAutoGeneratedColumns(final ExecutionContext context,
840
                        final MappingColumn[] mappingColumns,
841
                        final TableMetadata metadata, final E entity) {
842
                var autoGeneratedColumns = new ArrayList<MappingColumn>();
1✔
843
                var autoGeneratedColumnNames = new ArrayList<String>();
1✔
844

845
                if (mappingColumns != null && mappingColumns.length > 0) {
1✔
846
                        for (var column : metadata.getColumns()) {
1✔
847
                                if (column.isAutoincrement()) {
1✔
848
                                        for (var mappingColumn : mappingColumns) {
1✔
849
                                                if (mappingColumn.getCamelName().equals(column.getCamelColumnName())) {
1✔
850
                                                        // primitive型か、エンティティの対象フィールドがnullとなっている場合自動生成カラムとする
851
                                                        if (mappingColumn.getJavaType().getRawType().isPrimitive()
1✔
852
                                                                        || mappingColumn.getValue(entity) == null) {
1✔
853
                                                                autoGeneratedColumns.add(mappingColumn);
1✔
854
                                                                autoGeneratedColumnNames.add(column.getColumnName());
1✔
855
                                                        }
856
                                                        break;
857
                                                }
858
                                        }
859
                                }
860
                        }
1✔
861
                        for (var mappingColumn : mappingColumns) {
1✔
862
                                if (!mappingColumn.isId() || autoGeneratedColumns.contains(mappingColumn)) {
1✔
863
                                        continue;
1✔
864
                                }
865
                                for (var column : metadata.getColumns()) {
1✔
866
                                        if (mappingColumn.getCamelName().equals(column.getCamelColumnName())) {
1✔
867
                                                // primitive型か、エンティティの対象フィールドがnullとなっている場合自動生成カラムとする
868
                                                if (mappingColumn.getJavaType().getRawType().isPrimitive()
1✔
869
                                                                || mappingColumn.getValue(entity) == null) {
1✔
870
                                                        autoGeneratedColumns.add(mappingColumn);
1✔
871
                                                        autoGeneratedColumnNames.add(column.getColumnName());
1✔
872
                                                }
873
                                                break;
874
                                        }
875
                                }
1✔
876
                        }
877
                }
878

879
                if (!autoGeneratedColumnNames.isEmpty()) {
1✔
880
                        context.setGeneratedKeyColumns(
1✔
881
                                        autoGeneratedColumnNames.toArray(new String[autoGeneratedColumnNames.size()]));
1✔
882
                }
883
                return autoGeneratedColumns;
1✔
884
        }
885

886
        /**
887
         * entityにID値を設定する
888
         * @param entity Entity
889
         * @param id ID値
890
         * @param column 設定する対象のカラム
891
         */
892
        protected void setEntityIdValue(final Object entity, final Object id, final MappingColumn column) {
893
                Class<?> rawType = column.getJavaType().getRawType();
1✔
894
                try {
895
                        if (int.class.equals(rawType) || Integer.class.equals(rawType)) {
1✔
896
                                if (id instanceof Number) {
1✔
897
                                        column.setValue(entity, ((Number) id).intValue());
1✔
898
                                } else {
899
                                        throw new IllegalArgumentException();
1✔
900
                                }
901
                        } else if (long.class.equals(rawType) || Long.class.equals(rawType)) {
1✔
902
                                if (id instanceof Number) {
1✔
903
                                        column.setValue(entity, ((Number) id).longValue());
1✔
904
                                } else {
905
                                        throw new IllegalArgumentException();
1✔
906
                                }
907
                        } else if (BigInteger.class.equals(rawType)) {
1✔
908
                                if (id instanceof BigInteger) {
1✔
909
                                        column.setValue(entity, id);
×
910
                                } else if (id instanceof BigDecimal) {
1✔
911
                                        column.setValue(entity, ((BigDecimal) id).toBigInteger());
1✔
912
                                } else if (id instanceof Number) {
1✔
913
                                        column.setValue(entity, new BigInteger(((Number) id).toString()));
1✔
914
                                } else {
915
                                        throw new IllegalArgumentException();
1✔
916
                                }
917
                        } else if (BigDecimal.class.equals(rawType)) {
1✔
918
                                if (id instanceof BigDecimal) {
1✔
919
                                        column.setValue(entity, id);
1✔
920
                                } else if (id instanceof BigInteger) {
1✔
921
                                        column.setValue(entity, new BigDecimal((BigInteger) id));
×
922
                                } else if (id instanceof Number) {
1✔
923
                                        column.setValue(entity, new BigDecimal(((Number) id).toString()));
1✔
924
                                } else {
925
                                        throw new IllegalArgumentException();
1✔
926
                                }
927
                        } else if (String.class.equals(rawType)) {
1✔
928
                                if (id instanceof String) {
1✔
929
                                        column.setValue(entity, id);
×
930
                                } else if (id instanceof BigDecimal) {
1✔
931
                                        column.setValue(entity, ((BigDecimal) id).toPlainString());
1✔
932
                                } else if (id instanceof Number) {
1✔
933
                                        column.setValue(entity, ((Number) id).toString());
1✔
934
                                } else {
935
                                        column.setValue(entity, id.toString());
1✔
936
                                }
937
                        } else {
938
                                try {
939
                                        column.setValue(entity, id);
1✔
940
                                } catch (UroborosqlRuntimeException ex) {
1✔
941
                                        throw new UroborosqlRuntimeException(
1✔
942
                                                        "Column is not correct as ID type. column=" + column.getCamelName() + ", type=" + rawType);
1✔
943
                                }
1✔
944
                        }
945
                } catch (IllegalArgumentException ex) {
1✔
946
                        throw new UroborosqlRuntimeException(
1✔
947
                                        "Column type and ID type do not match. column=" + column.getCamelName() + ", type="
1✔
948
                                                        + rawType
949
                                                        + ", id=" + id + ", type=" + id.getClass().getSimpleName());
1✔
950
                }
1✔
951
        }
1✔
952

953
        /**
954
         * {@inheritDoc}
955
         *
956
         * @see jp.co.future.uroborosql.SqlAgent#update(java.lang.Object)
957
         */
958
        @SuppressWarnings("unchecked")
959
        @Override
960
        public <E> int update(final E entity) {
961
                if (entity instanceof Stream) {
1✔
962
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
963
                }
964

965
                @SuppressWarnings("rawtypes")
966
                EntityHandler handler = this.getEntityHandler();
1✔
967
                if (!handler.getEntityType().isInstance(entity)) {
1✔
968
                        throw new IllegalArgumentException("Entity type not supported");
×
969
                }
970

971
                try {
972
                        Class<?> type = entity.getClass();
1✔
973
                        var metadata = handler.getMetadata(this.transactionManager, type);
1✔
974
                        var context = handler.createUpdateContext(this, metadata, type, true);
1✔
975
                        context.setSqlKind(SqlKind.UPDATE);
1✔
976
                        handler.setUpdateParams(context, entity);
1✔
977
                        var count = handler.doUpdate(this, context, entity);
1✔
978

979
                        MappingUtils.getVersionMappingColumn(metadata.getSchema(), type).ifPresent(versionColumn -> {
1✔
980
                                if (count == 0) {
1✔
981
                                        throw new OptimisticLockException(context);
1✔
982
                                } else {
983
                                        var columnMap = MappingUtils.getMappingColumnMap(metadata.getSchema(), type,
1✔
984
                                                        SqlKind.NONE);
985
                                        var keys = metadata.getColumns().stream()
1✔
986
                                                        .filter(TableMetadata.Column::isKey)
1✔
987
                                                        .sorted(Comparator.comparingInt(TableMetadata.Column::getKeySeq))
1✔
988
                                                        .map(c -> columnMap.get(c.getCamelColumnName()).getValue(entity))
1✔
989
                                                        .toArray();
1✔
990

991
                                        find(type, keys).ifPresent(e -> versionColumn.setValue(entity, versionColumn.getValue(e)));
1✔
992
                                }
993
                        });
1✔
994
                        return count;
1✔
995
                } catch (SQLException e) {
×
996
                        throw new EntitySqlRuntimeException(SqlKind.UPDATE, e);
×
997
                }
998
        }
999

1000
        /**
1001
         * {@inheritDoc}
1002
         *
1003
         * @see jp.co.future.uroborosql.SqlAgent#updateAndReturn(java.lang.Object)
1004
         */
1005
        @Override
1006
        public <E> E updateAndReturn(final E entity) {
1007
                update(entity);
1✔
1008
                return entity;
1✔
1009
        }
1010

1011
        /**
1012
         * {@inheritDoc}
1013
         *
1014
         * @see jp.co.future.uroborosql.SqlAgent#delete(java.lang.Class)
1015
         */
1016
        @SuppressWarnings("unchecked")
1017
        @Override
1018
        public <E> SqlEntityUpdate<E> update(final Class<? extends E> entityType) {
1019
                @SuppressWarnings("rawtypes")
1020
                EntityHandler handler = this.getEntityHandler();
1✔
1021
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
1022
                        throw new IllegalArgumentException("Entity type not supported");
×
1023
                }
1024

1025
                try {
1026
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
1027

1028
                        var context = handler.createUpdateContext(this, metadata, entityType, false);
1✔
1029
                        context.setSqlKind(SqlKind.UPDATE);
1✔
1030

1031
                        return new SqlEntityUpdateImpl<>(this, handler, metadata, context);
1✔
1032
                } catch (SQLException e) {
×
1033
                        throw new EntitySqlRuntimeException(SqlKind.DELETE, e);
×
1034
                }
1035
        }
1036

1037
        /**
1038
         * {@inheritDoc}
1039
         *
1040
         * @see jp.co.future.uroborosql.SqlAgent#merge(java.lang.Object)
1041
         */
1042
        @Override
1043
        public <E> int merge(final E entity) {
1044
                mergeAndReturn(entity);
1✔
1045
                return 1;
1✔
1046
        }
1047

1048
        /**
1049
         * {@inheritDoc}
1050
         *
1051
         * @see jp.co.future.uroborosql.SqlAgent#mergeAndReturn(java.lang.Object)
1052
         */
1053
        @Override
1054
        public <E> E mergeAndReturn(final E entity) {
1055
                return mergeAndReturn(entity, false);
1✔
1056
        }
1057

1058
        /**
1059
         * {@inheritDoc}
1060
         *
1061
         * @see jp.co.future.uroborosql.SqlAgent#mergeWithLocking(java.lang.Object)
1062
         */
1063
        @Override
1064
        public <E> int mergeWithLocking(final E entity) {
1065
                mergeWithLockingAndReturn(entity);
1✔
1066
                return 1;
1✔
1067
        }
1068

1069
        /**
1070
         * {@inheritDoc}
1071
         *
1072
         * @see jp.co.future.uroborosql.SqlAgent#mergeWithLockingAndReturn(java.lang.Object)
1073
         */
1074
        @Override
1075
        public <E> E mergeWithLockingAndReturn(final E entity) {
1076
                return mergeAndReturn(entity, true);
1✔
1077
        }
1078

1079
        /**
1080
         * エンティティのMERGE(INSERTまたはUPDATE)を実行し、MERGEしたエンティティを返却する.<br>
1081
         * locking引数が<code>true</code>の場合、マージの最初に発行するEntityの検索で悲観ロック(forUpdateNoWait)を行う
1082
         *
1083
         * @param <E> エンティティ型
1084
         * @param entity エンティティ
1085
         * @param locking エンティティの悲観ロックを行うかどうか
1086
         * @return MERGEしたエンティティ
1087
         *
1088
         */
1089
        @SuppressWarnings("unchecked")
1090
        private <E> E mergeAndReturn(final E entity, final boolean locking) {
1091
                if (entity instanceof Stream) {
1✔
1092
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
1093
                }
1094

1095
                @SuppressWarnings("rawtypes")
1096
                EntityHandler handler = this.getEntityHandler();
1✔
1097
                if (!handler.getEntityType().isInstance(entity)) {
1✔
1098
                        throw new IllegalArgumentException("Entity type not supported.");
×
1099
                }
1100

1101
                Class<?> type = entity.getClass();
1✔
1102

1103
                try {
1104
                        var metadata = handler.getMetadata(this.transactionManager, type);
1✔
1105
                        var keyColumns = metadata.getKeyColumns();
1✔
1106

1107
                        if (keyColumns.isEmpty()) {
1✔
1108
                                throw new IllegalArgumentException("Entity has no keys.");
×
1109
                        }
1110

1111
                        var mappingColumns = MappingUtils.getMappingColumnMap(metadata.getSchema(), type,
1✔
1112
                                        SqlKind.UPDATE);
1113

1114
                        var query = (SqlEntityQuery<E>) query(type);
1✔
1115
                        for (var column : keyColumns) {
1✔
1116
                                var camelName = column.getCamelColumnName();
1✔
1117
                                query.equal(camelName, mappingColumns.get(camelName).getValue(entity));
1✔
1118
                        }
1✔
1119
                        if (locking) {
1✔
1120
                                query.forUpdateNoWait();
1✔
1121
                        }
1122
                        return query.first()
1✔
1123
                                        .map(findEntity -> {
1✔
1124
                                                for (var mappingColumn : mappingColumns.values()) {
1✔
1125
                                                        if (!mappingColumn.isId()) {
1✔
1126
                                                                var value = mappingColumn.getValue(entity);
1✔
1127
                                                                if (value != null) {
1✔
1128
                                                                        mappingColumn.setValue(findEntity, value);
1✔
1129
                                                                }
1130
                                                        }
1131
                                                }
1✔
1132
                                                return updateAndReturn(findEntity);
1✔
1133
                                        }).orElseGet(() -> insertAndReturn(entity));
1✔
1134
                } catch (SQLException e) {
×
1135
                        throw new EntitySqlRuntimeException(SqlKind.MERGE, e);
×
1136
                }
1137
        }
1138

1139
        /**
1140
         * {@inheritDoc}
1141
         *
1142
         * @see jp.co.future.uroborosql.SqlAgent#delete(java.lang.Object)
1143
         */
1144
        @SuppressWarnings("unchecked")
1145
        @Override
1146
        public <E> int delete(final E entity) {
1147
                if (entity instanceof Stream) {
1✔
1148
                        throw new IllegalArgumentException("Stream type not supported.");
1✔
1149
                }
1150

1151
                @SuppressWarnings("rawtypes")
1152
                EntityHandler handler = this.getEntityHandler();
1✔
1153
                if (!handler.getEntityType().isInstance(entity)) {
1✔
1154
                        throw new IllegalArgumentException("Entity type not supported");
×
1155
                }
1156

1157
                try {
1158
                        Class<?> type = entity.getClass();
1✔
1159
                        var metadata = handler.getMetadata(this.transactionManager, type);
1✔
1160
                        var context = handler.createDeleteContext(this, metadata, type, true);
1✔
1161
                        context.setSqlKind(SqlKind.DELETE);
1✔
1162
                        handler.setDeleteParams(context, entity);
1✔
1163
                        return handler.doDelete(this, context, entity);
1✔
1164
                } catch (SQLException e) {
×
1165
                        throw new EntitySqlRuntimeException(SqlKind.DELETE, e);
×
1166
                }
1167
        }
1168

1169
        /**
1170
         * {@inheritDoc}
1171
         *
1172
         * @see jp.co.future.uroborosql.SqlAgent#deleteAndReturn(java.lang.Object)
1173
         */
1174
        @Override
1175
        public <E> E deleteAndReturn(final E entity) {
1176
                delete(entity);
1✔
1177
                return entity;
1✔
1178
        }
1179

1180
        /**
1181
         * {@inheritDoc}
1182
         *
1183
         * @see jp.co.future.uroborosql.SqlAgent#delete(java.lang.Class, java.lang.Object[])
1184
         */
1185
        @SuppressWarnings("unchecked")
1186
        @Override
1187
        public <E> int delete(final Class<? extends E> entityType, final Object... keys) {
1188
                @SuppressWarnings("rawtypes")
1189
                EntityHandler handler = this.getEntityHandler();
1✔
1190
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
1191
                        throw new IllegalArgumentException("Entity type not supported");
×
1192
                }
1193

1194
                try {
1195
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
1196
                        TableMetadata.Column keyColumn = null;
1✔
1197
                        var keyColumns = metadata.getKeyColumns();
1✔
1198
                        if (keyColumns.size() == 1) {
1✔
1199
                                keyColumn = keyColumns.get(0);
1✔
1200
                        } else if (keyColumns.isEmpty()) {
1✔
1201
                                keyColumn = metadata.getColumns().get(0);
1✔
1202
                        } else {
1203
                                throw new IllegalArgumentException("Entity has multiple keys");
1✔
1204
                        }
1205
                        return delete(entityType).in(keyColumn.getCamelColumnName(), keys).count();
1✔
1206
                } catch (SQLException e) {
×
1207
                        throw new EntitySqlRuntimeException(SqlKind.DELETE, e);
×
1208
                }
1209
        }
1210

1211
        /**
1212
         * {@inheritDoc}
1213
         *
1214
         * @see jp.co.future.uroborosql.SqlAgent#delete(java.lang.Class)
1215
         */
1216
        @SuppressWarnings("unchecked")
1217
        @Override
1218
        public <E> SqlEntityDelete<E> delete(final Class<? extends E> entityType) {
1219
                @SuppressWarnings("rawtypes")
1220
                EntityHandler handler = this.getEntityHandler();
1✔
1221
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
1222
                        throw new IllegalArgumentException("Entity type not supported");
×
1223
                }
1224

1225
                try {
1226
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
1227

1228
                        var context = handler.createDeleteContext(this, metadata, entityType, false);
1✔
1229
                        context.setSqlKind(SqlKind.DELETE);
1✔
1230

1231
                        return new SqlEntityDeleteImpl<>(this, handler, metadata, context);
1✔
1232
                } catch (SQLException e) {
×
1233
                        throw new EntitySqlRuntimeException(SqlKind.DELETE, e);
×
1234
                }
1235
        }
1236

1237
        /**
1238
         * {@inheritDoc}
1239
         *
1240
         * @see jp.co.future.uroborosql.SqlAgent#truncate(java.lang.Class)
1241
         */
1242
        @SuppressWarnings("unchecked")
1243
        @Override
1244
        public <E> SqlAgent truncate(final Class<? extends E> entityType) {
1245
                @SuppressWarnings("rawtypes")
1246
                EntityHandler handler = this.getEntityHandler();
1✔
1247
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
1248
                        throw new IllegalArgumentException("Entity type not supported");
×
1249
                }
1250
                try {
1251
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
1252
                        var context = this.contextWith("truncate table " + metadata.getTableIdentifier());
1✔
1253
                        context.setSqlKind(SqlKind.TRUNCATE);
1✔
1254
                        update(context);
1✔
1255
                        return this;
1✔
1256
                } catch (SQLException e) {
1✔
1257
                        throw new EntitySqlRuntimeException(SqlKind.TRUNCATE, e);
1✔
1258
                }
1259
        }
1260

1261
        /**
1262
         * {@inheritDoc}
1263
         *
1264
         * @see jp.co.future.uroborosql.AbstractAgent#batchInsert(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition, java.util.List)
1265
         */
1266
        @SuppressWarnings("unchecked")
1267
        @Override
1268
        protected <E> int batchInsert(final Class<E> entityType, final Stream<E> entities,
1269
                        final InsertsCondition<? super E> condition, final List<E> insertedEntities) {
1270
                @SuppressWarnings("rawtypes")
1271
                EntityHandler handler = this.getEntityHandler();
1✔
1272
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
1273
                        throw new IllegalArgumentException("Entity type not supported");
1✔
1274
                }
1275

1276
                try {
1277
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
1278
                        var context = handler.createBatchInsertContext(this, metadata, entityType);
1✔
1279
                        context.setSqlKind(SqlKind.BATCH_INSERT);
1✔
1280

1281
                        var count = 0;
1✔
1282
                        var entityList = new ArrayList<E>();
1✔
1283
                        var isFirst = true;
1✔
1284
                        List<MappingColumn> autoGeneratedColumns = List.of();
1✔
1285
                        Map<String, Object> nonNullObjectIdFlags = null;
1✔
1286
                        for (var iterator = entities.iterator(); iterator.hasNext();) {
1✔
1287
                                var entity = iterator.next();
1✔
1288

1289
                                if (!entityType.isInstance(entity)) {
1✔
1290
                                        throw new IllegalArgumentException("Entity types do not match");
1✔
1291
                                }
1292

1293
                                if (isFirst) {
1✔
1294
                                        isFirst = false;
1✔
1295
                                        var mappingColumns = MappingUtils.getMappingColumns(metadata.getSchema(), entityType);
1✔
1296
                                        autoGeneratedColumns = getAutoGeneratedColumns(context, mappingColumns, metadata, entity);
1✔
1297

1298
                                        // SQLのID項目IF分岐判定をtrueにするために値が設定されているID項目を保持しておく
1299
                                        var excludeColumns = autoGeneratedColumns;
1✔
1300
                                        nonNullObjectIdFlags = Arrays.stream(mappingColumns)
1✔
1301
                                                        .filter(col -> !excludeColumns.contains(col)
1✔
1302
                                                                        && !col.getJavaType().getRawType().isPrimitive()
1✔
1303
                                                                        && col.getValue(entity) != null)
1✔
1304
                                                        .collect(Collectors.toMap(MappingColumn::getCamelName, col -> true));
1✔
1305
                                }
1306

1307
                                entityList.add(entity);
1✔
1308
                                if (insertedEntities != null) {
1✔
1309
                                        insertedEntities.add(entity);
1✔
1310
                                }
1311

1312
                                handler.setInsertParams(context, entity);
1✔
1313
                                context.addBatch();
1✔
1314
                                // SQLのID項目IF分岐判定をtrueにするためにaddBatch()の後に保持しておいたID項目をcontextにバインドする
1315
                                if (nonNullObjectIdFlags != null && !nonNullObjectIdFlags.isEmpty()) {
1✔
1316
                                        context.paramMap(nonNullObjectIdFlags);
1✔
1317
                                }
1318
                                if (condition.test(context, context.batchCount(), entity)) {
1✔
1319
                                        count += Arrays.stream(doBatchInsert(context, handler, entityList, autoGeneratedColumns)).sum();
1✔
1320
                                        entityList.clear();
1✔
1321
                                }
1322
                        }
1✔
1323
                        return count + (context.batchCount() != 0
1✔
1324
                                        ? Arrays.stream(doBatchInsert(context, handler, entityList, autoGeneratedColumns)).sum()
1✔
1325
                                        : 0);
1✔
1326
                } catch (SQLException e) {
×
1327
                        throw new EntitySqlRuntimeException(SqlKind.BATCH_INSERT, e);
×
1328
                }
1329
        }
1330

1331
        protected <E> int[] doBatchInsert(final ExecutionContext context, final EntityHandler<E> handler,
1332
                        final List<E> entityList, final List<MappingColumn> autoGeneratedColumns) throws SQLException {
1333
                var counts = handler.doBatchInsert(this, context);
1✔
1334
                if (!autoGeneratedColumns.isEmpty()) {
1✔
1335
                        var ids = context.getGeneratedKeyValues();
1✔
1336
                        var idx = 0;
1✔
1337
                        for (E ent : entityList) {
1✔
1338
                                for (var col : autoGeneratedColumns) {
1✔
1339
                                        setEntityIdValue(ent, ids[idx++], col);
1✔
1340
                                }
1✔
1341
                        }
1✔
1342
                }
1343

1344
                return counts;
1✔
1345
        }
1346

1347
        /**
1348
         * {@inheritDoc}
1349
         *
1350
         * @see jp.co.future.uroborosql.AbstractAgent#bulkInsert(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.InsertsCondition, java.util.List)
1351
         */
1352
        @SuppressWarnings("unchecked")
1353
        @Override
1354
        protected <E> int bulkInsert(final Class<E> entityType, final Stream<E> entities,
1355
                        final InsertsCondition<? super E> condition, final List<E> insertedEntities) {
1356
                @SuppressWarnings("rawtypes")
1357
                EntityHandler handler = this.getEntityHandler();
1✔
1358
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
1359
                        throw new IllegalArgumentException("Entity type not supported");
×
1360
                }
1361

1362
                try {
1363
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
1364
                        var context = handler.createBulkInsertContext(this, metadata, entityType);
1✔
1365
                        context.setSqlKind(SqlKind.BULK_INSERT);
1✔
1366

1367
                        var frameCount = 0;
1✔
1368
                        var count = 0;
1✔
1369
                        var isFirst = true;
1✔
1370
                        List<MappingColumn> autoGeneratedColumns = List.of();
1✔
1371
                        Map<String, Object> nonNullObjectIdFlags = null;
1✔
1372
                        var entityList = new ArrayList<E>();
1✔
1373
                        for (var iterator = entities.iterator(); iterator.hasNext();) {
1✔
1374
                                var entity = iterator.next();
1✔
1375

1376
                                if (!entityType.isInstance(entity)) {
1✔
1377
                                        throw new IllegalArgumentException("Entity types do not match");
×
1378
                                }
1379

1380
                                if (isFirst) {
1✔
1381
                                        isFirst = false;
1✔
1382
                                        var mappingColumns = MappingUtils.getMappingColumns(metadata.getSchema(), entityType);
1✔
1383
                                        autoGeneratedColumns = getAutoGeneratedColumns(context, mappingColumns, metadata, entity);
1✔
1384

1385
                                        // SQLのID項目IF分岐判定をtrueにするために値が設定されているID項目を保持しておく
1386
                                        var excludeColumns = autoGeneratedColumns;
1✔
1387
                                        // indexなしのid値がcontextにバインドされないため、値としてkey=trueを退避しておく
1388
                                        nonNullObjectIdFlags = Arrays.stream(mappingColumns)
1✔
1389
                                                        .filter(col -> !excludeColumns.contains(col)
1✔
1390
                                                                        && !col.getJavaType().getRawType().isPrimitive()
1✔
1391
                                                                        && col.getValue(entity) != null)
1✔
1392
                                                        .collect(Collectors.toMap(MappingColumn::getCamelName, col -> true));
1✔
1393
                                }
1394
                                // 退避しておいたid値をこのタイミングで設定する
1395
                                if (nonNullObjectIdFlags != null && !nonNullObjectIdFlags.isEmpty()) {
1✔
1396
                                        context.paramMap(nonNullObjectIdFlags);
1✔
1397
                                }
1398

1399
                                entityList.add(entity);
1✔
1400
                                if (insertedEntities != null) {
1✔
1401
                                        insertedEntities.add(entity);
1✔
1402
                                }
1403

1404
                                handler.setBulkInsertParams(context, entity, frameCount);
1✔
1405
                                frameCount++;
1✔
1406

1407
                                if (condition.test(context, frameCount, entity)) {
1✔
1408
                                        count += doBulkInsert(context, entityType, handler, metadata, autoGeneratedColumns, entityList);
1✔
1409
                                        frameCount = 0;
1✔
1410
                                        entityList.clear();
1✔
1411

1412
                                        // 新しいExecutionContextを作成する前にgeneratedKeyColumnsを退避しておく
1413
                                        var generatedKeyColumns = context.getGeneratedKeyColumns();
1✔
1414
                                        context = handler.createBulkInsertContext(this, metadata, entityType);
1✔
1415
                                        context.setSqlKind(SqlKind.BULK_INSERT);
1✔
1416
                                        // 実行結果から生成されたIDを取得できるようにPreparedStatementにIDカラムを渡す
1417
                                        context.setGeneratedKeyColumns(generatedKeyColumns);
1✔
1418
                                }
1419
                        }
1✔
1420
                        return count + (frameCount > 0
1✔
1421
                                        ? doBulkInsert(context, entityType, handler, metadata, autoGeneratedColumns, entityList)
1✔
1422
                                        : 0);
1✔
1423

1424
                } catch (SQLException e) {
×
1425
                        throw new EntitySqlRuntimeException(SqlKind.BULK_INSERT, e);
×
1426
                }
1427
        }
1428

1429
        protected <E> int doBulkInsert(final ExecutionContext context, final Class<E> entityType,
1430
                        final EntityHandler<E> handler,
1431
                        final TableMetadata metadata, final List<MappingColumn> autoGeneratedColumns, final List<E> entityList)
1432
                        throws SQLException {
1433
                var count = handler.doBulkInsert(this,
1✔
1434
                                handler.setupSqlBulkInsertContext(this, context, metadata, entityType, entityList.size()));
1✔
1435

1436
                if (!autoGeneratedColumns.isEmpty()) {
1✔
1437
                        var ids = context.getGeneratedKeyValues();
1✔
1438
                        var idx = 0;
1✔
1439
                        for (E ent : entityList) {
1✔
1440
                                for (var col : autoGeneratedColumns) {
1✔
1441
                                        setEntityIdValue(ent, ids[idx++], col);
1✔
1442
                                }
1✔
1443
                        }
1✔
1444
                }
1445

1446
                return count;
1✔
1447
        }
1448

1449
        /**
1450
         * {@inheritDoc}
1451
         *
1452
         * @see jp.co.future.uroborosql.AbstractAgent#batchUpdate(java.lang.Class, java.util.stream.Stream, jp.co.future.uroborosql.SqlAgent.UpdatesCondition, java.util.List)
1453
         */
1454
        @SuppressWarnings("unchecked")
1455
        @Override
1456
        protected <E> int batchUpdate(final Class<E> entityType, final Stream<E> entities,
1457
                        final UpdatesCondition<? super E> condition, final List<E> updatedEntities) {
1458
                @SuppressWarnings("rawtypes")
1459
                EntityHandler handler = this.getEntityHandler();
1✔
1460
                if (!handler.getEntityType().isAssignableFrom(entityType)) {
1✔
1461
                        throw new IllegalArgumentException("Entity type not supported");
×
1462
                }
1463

1464
                try {
1465
                        var metadata = handler.getMetadata(this.transactionManager, entityType);
1✔
1466
                        var context = handler.createBatchUpdateContext(this, metadata, entityType);
1✔
1467
                        context.setSqlKind(SqlKind.BATCH_UPDATE);
1✔
1468

1469
                        var versionColumn = MappingUtils.getVersionMappingColumn(metadata.getSchema(),
1✔
1470
                                        entityType);
1471
                        var entityCount = 0;
1✔
1472
                        var updateCount = 0;
1✔
1473
                        var entityList = new ArrayList<E>();
1✔
1474
                        for (var iterator = entities.iterator(); iterator.hasNext();) {
1✔
1475
                                var entity = iterator.next();
1✔
1476

1477
                                if (!entityType.isInstance(entity)) {
1✔
1478
                                        throw new IllegalArgumentException("Entity types do not match");
×
1479
                                }
1480

1481
                                entityList.add(entity);
1✔
1482
                                if (updatedEntities != null) {
1✔
1483
                                        updatedEntities.add(entity);
1✔
1484
                                }
1485
                                entityCount++;
1✔
1486

1487
                                handler.setUpdateParams(context, entity);
1✔
1488
                                context.addBatch();
1✔
1489

1490
                                if (condition.test(context, context.batchCount(), entity)) {
1✔
1491
                                        updateCount += Arrays.stream(handler.doBatchUpdate(this, context)).sum();
1✔
1492
                                        entityList.clear();
1✔
1493
                                }
1494
                        }
1✔
1495
                        updateCount = updateCount + (context.batchCount() != 0
1✔
1496
                                        ? Arrays.stream(handler.doBatchUpdate(this, context)).sum()
1✔
1497
                                        : 0);
1✔
1498

1499
                        if (updatedEntities != null && versionColumn.isPresent()) {
1✔
1500
                                var vColumn = versionColumn.get();
1✔
1501
                                var keyColumns = metadata.getColumns().stream()
1✔
1502
                                                .filter(TableMetadata.Column::isKey)
1✔
1503
                                                .sorted(Comparator.comparingInt(TableMetadata.Column::getKeySeq))
1✔
1504
                                                .map(c -> MappingUtils.getMappingColumnMap(metadata.getSchema(), entityType, SqlKind.NONE)
1✔
1505
                                                                .get(c.getCamelColumnName()))
1✔
1506
                                                .collect(Collectors.toList());
1✔
1507

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

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

1518
                                        for (var start = 0; start < entitySize; start = start + IN_CLAUSE_MAX_PARAM_SIZE) {
1✔
1519
                                                var end = Math.min(start + IN_CLAUSE_MAX_PARAM_SIZE, entitySize);
1✔
1520
                                                var subList = keyList.subList(start, end);
1✔
1521

1522
                                                query(entityType).in(keyColumn.getCamelName(), subList).stream()
1✔
1523
                                                                .map(e -> {
1✔
1524
                                                                        var updatedEntity = updatedEntityMap.get(keyColumn.getValue(e)).get(0);
1✔
1525
                                                                        vColumn.setValue(updatedEntity, vColumn.getValue(e));
1✔
1526
                                                                        return updatedEntity;
1✔
1527
                                                                }).count();
1✔
1528
                                        }
1529
                                } else if (keyColumns.size() > 1) {
1✔
1530
                                        // 複合キーの場合はIN句で一括取得できないため1件ずつ取得して@Versionのついたフィールドを更新する
1531
                                        updatedEntities.stream()
1✔
1532
                                                        .map(updatedEntity -> {
1✔
1533
                                                                var keyValues = keyColumns.stream().map(k -> k.getValue(updatedEntity)).toArray();
×
1534
                                                                find(entityType, keyValues).ifPresent(e -> {
×
1535
                                                                        vColumn.setValue(updatedEntity, vColumn.getValue(e));
×
1536
                                                                });
×
1537
                                                                return updatedEntity;
×
1538
                                                        }).count();
1✔
1539
                                }
1540
                        }
1541
                        if (versionColumn.isPresent() && getDialect().supportsEntityBulkUpdateOptimisticLock()
1✔
1542
                                        && updateCount != entityCount) {
1543
                                // バージョンカラムの指定があり、更新件数と更新対象Entityの件数が不一致の場合は楽観ロックエラーとする
1544
                                throw new OptimisticLockException(String.format(
1✔
1545
                                                "An error occurred due to optimistic locking.%nExecuted SQL [%n%s]%nBatch Entity Count: %d, Update Count: %d.",
1546
                                                context.getExecutableSql(), entityCount, updateCount));
1✔
1547
                        }
1548
                        return updateCount;
1✔
1549
                } catch (SQLException e) {
×
1550
                        throw new EntitySqlRuntimeException(SqlKind.BATCH_UPDATE, e);
×
1551
                }
1552
        }
1553

1554
        /**
1555
         * ResultSetをStreamで扱うためのSpliterator
1556
         *
1557
         * @author H.Sugimoto
1558
         *
1559
         * @param <T> ResultSetの1行を変換した型
1560
         */
1561
        private static final class ResultSetSpliterator<T> extends Spliterators.AbstractSpliterator<T> {
1562
                private final ResultSetConverter<T> converter;
1563
                private final ResultSet rs;
1564
                private boolean finished = false;
1✔
1565

1566
                private ResultSetSpliterator(final ResultSet rs, final ResultSetConverter<T> converter) {
1567
                        super(Long.MAX_VALUE, Spliterator.ORDERED);
1✔
1568
                        this.rs = rs;
1✔
1569
                        this.converter = converter;
1✔
1570
                }
1✔
1571

1572
                @Override
1573
                public boolean tryAdvance(final Consumer<? super T> action) {
1574
                        try {
1575
                                if (finished || !rs.next()) {
1✔
1576
                                        if (!rs.isClosed()) {
1✔
1577
                                                rs.close();
1✔
1578
                                        }
1579
                                        finished = true;
1✔
1580
                                        return false;
1✔
1581
                                }
1582
                                action.accept(converter.createRecord(rs));
1✔
1583
                                return true;
1✔
1584
                        } catch (RuntimeException | Error ex) {
1✔
1585
                                try {
1586
                                        if (rs != null && !rs.isClosed()) {
1✔
1587
                                                rs.close();
1✔
1588
                                        }
1589
                                } catch (SQLException e) {
×
1590
                                        e.printStackTrace();
×
1591
                                }
1✔
1592
                                throw ex;
1✔
1593
                        } catch (SQLException ex) {
×
1594
                                try {
1595
                                        if (rs != null && !rs.isClosed()) {
×
1596
                                                rs.close();
×
1597
                                        }
1598
                                } catch (SQLException e) {
×
1599
                                        e.printStackTrace();
×
1600
                                }
×
1601
                                throw new UroborosqlSQLException(ex);
×
1602
                        }
1603
                }
1604
        }
1605

1606
        /**
1607
         * ResultSetのラッパークラス。ResultSetのクローズに合わせてStatementもクローズする。
1608
         *
1609
         * @author H.Sugimoto
1610
         * @version 0.5.0
1611
         */
1612
        private static class InnerResultSet extends AbstractResultSetWrapper {
1613
                /** 同期してクローズするStatement */
1614
                private final Statement stmt;
1615

1616
                /**
1617
                 * コンストラクタ
1618
                 *
1619
                 * @param wrapped 元となるResultSet
1620
                 * @param stmt Statement
1621
                 */
1622
                InnerResultSet(final ResultSet wrapped, final Statement stmt) {
1623
                        super(wrapped);
1✔
1624
                        this.stmt = stmt;
1✔
1625
                }
1✔
1626

1627
                /**
1628
                 * {@inheritDoc}
1629
                 *
1630
                 * @see jp.co.future.uroborosql.AbstractResultSetWrapper#close()
1631
                 */
1632
                @Override
1633
                public void close() throws SQLException {
1634
                        try {
1635
                                super.close();
1✔
1636
                        } finally {
1637
                                try {
1638
                                        if (stmt != null && !stmt.isClosed()) {
1✔
1639
                                                stmt.close();
1✔
1640
                                        }
1641
                                } catch (SQLException e) {
×
1642
                                        // do nothing
1643
                                }
1✔
1644
                        }
1645
                }
1✔
1646
        }
1647

1648
        /**
1649
         * 経過時間を計算し、HH:mm:ss.SSSSSSにフォーマットする.
1650
         *
1651
         * @param start 開始時間
1652
         * @param end 終了時間
1653
         * @return フォーマットした経過時間
1654
         */
1655
        private static String formatElapsedTime(final Instant start, final Instant end) {
1656
                return ELAPSED_TIME_FORMAT.format(LocalTime.MIDNIGHT.plus(Duration.between(start, end)));
1✔
1657
        }
1658

1659
}
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