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

future-architect / uroborosql / #932

18 Nov 2025 05:04PM UTC coverage: 90.803% (+0.02%) from 90.787%
#932

Pull #376

HidekiSugimoto189
testの微調整
Pull Request #376: Fix SQL Server batch insert failure with IDENTITY columns

11 of 11 new or added lines in 4 files covered. (100.0%)

16 existing lines in 1 file now uncovered.

9222 of 10156 relevant lines covered (90.8%)

0.91 hits per line

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

87.41
/src/main/java/jp/co/future/uroborosql/tx/LocalTransactionContext.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.tx;
8

9
import java.sql.CallableStatement;
10
import java.sql.Connection;
11
import java.sql.PreparedStatement;
12
import java.sql.SQLException;
13
import java.sql.Savepoint;
14
import java.util.ArrayList;
15
import java.util.Collections;
16
import java.util.List;
17
import java.util.concurrent.ConcurrentHashMap;
18
import java.util.concurrent.ConcurrentMap;
19

20
import jp.co.future.uroborosql.config.SqlConfig;
21
import jp.co.future.uroborosql.connection.ConnectionContext;
22
import jp.co.future.uroborosql.context.ExecutionContext;
23
import jp.co.future.uroborosql.enums.SqlKind;
24
import jp.co.future.uroborosql.event.AfterCommitEvent;
25
import jp.co.future.uroborosql.event.AfterCreateCallableStatementEvent;
26
import jp.co.future.uroborosql.event.AfterCreatePreparedStatementEvent;
27
import jp.co.future.uroborosql.event.AfterRollbackEvent;
28
import jp.co.future.uroborosql.event.BeforeCommitEvent;
29
import jp.co.future.uroborosql.event.BeforeRollbackEvent;
30
import jp.co.future.uroborosql.exception.UroborosqlSQLException;
31
import jp.co.future.uroborosql.exception.UroborosqlTransactionException;
32

33
/**
34
 * ローカルトランザクションContext
35
 *
36
 * @author ota
37
 */
38
class LocalTransactionContext implements TransactionContext {
39
        /** セーブポイント名リスト */
40
        private final List<String> savepointNames = new ArrayList<>();
1✔
41

42
        /** セーブポイントキャッシュ */
43
        private final ConcurrentMap<String, Savepoint> savepointMap = new ConcurrentHashMap<>();
1✔
44

45
        /** SQL設定クラス */
46
        private final SqlConfig sqlConfig;
47

48
        /** コネクション */
49
        private Connection connection;
50

51
        /** ロールバックフラグ */
52
        private boolean rollbackOnly = false;
1✔
53

54
        /** トランザクション内での更新を強制するかどうか */
55
        private final boolean updatable;
56

57
        /** DB接続情報. ConnectionSupplierで指定したデフォルトの接続情報を使用する場合は<code>null</code>を指定する */
58
        private final ConnectionContext connectionContext;
59

60
        /**
61
         * コンストラクタ
62
         *
63
         * @param sqlConfig SQL設定クラス
64
         * @param updatable 更新(INSERT/UPDATE/DELETE)SQL発行可能かどうか
65
         * @param connectionContext DB接続情報. ConnectionSupplierで指定したデフォルトの接続情報を使用する場合は<code>null</code>を指定する
66
         */
67
        LocalTransactionContext(final SqlConfig sqlConfig, final boolean updatable,
68
                        final ConnectionContext connectionContext) {
1✔
69
                this.sqlConfig = sqlConfig;
1✔
70
                this.updatable = updatable;
1✔
71
                this.connectionContext = connectionContext;
1✔
72
        }
1✔
73

74
        /**
75
         * {@inheritDoc}
76
         *
77
         * @see jp.co.future.uroborosql.tx.TransactionContext#getSqlConfig()
78
         */
79
        @Override
80
        public SqlConfig getSqlConfig() {
81
                return this.sqlConfig;
×
82
        }
83

84
        /**
85
         * {@inheritDoc}
86
         *
87
         * @see jp.co.future.uroborosql.tx.TransactionContext#getConnection()
88
         */
89
        @Override
90
        public Connection getConnection() throws SQLException {
91
                if (connection == null) {
1✔
92
                        if (connectionContext == null) {
1✔
93
                                connection = this.sqlConfig.getConnectionSupplier().getConnection();
1✔
94
                        } else {
95
                                connection = this.sqlConfig.getConnectionSupplier().getConnection(connectionContext);
1✔
96
                        }
97
                        initSavepoints(connection);
1✔
98
                }
99
                return connection;
1✔
100
        }
101

102
        /**
103
         * {@inheritDoc}
104
         *
105
         * @see jp.co.future.uroborosql.tx.TransactionContext#getConnectionContext()
106
         */
107
        @Override
108
        public ConnectionContext getConnectionContext() {
109
                return this.connectionContext;
×
110
        }
111

112
        /**
113
         * {@inheritDoc}
114
         *
115
         * @see jp.co.future.uroborosql.tx.TransactionContext#getPreparedStatement(jp.co.future.uroborosql.context.ExecutionContext)
116
         */
117
        @Override
118
        public PreparedStatement getPreparedStatement(final ExecutionContext executionContext) throws SQLException {
119
                var conn = getConnection();
1✔
120

121
                PreparedStatement stmt = null;
1✔
122
                switch (executionContext.getSqlKind()) {
1✔
123
                case INSERT:
124
                case ENTITY_INSERT:
125
                case BULK_INSERT:
126
                case BATCH_INSERT:
127
                case ENTITY_BULK_INSERT:
128
                case ENTITY_BATCH_INSERT:
129
                        if (updatable) {
1✔
130
                                // バッチ処理の場合、Dialectがバッチでの自動生成キー取得をサポートしているか確認
131
                                var isBatchOperation = (executionContext.getSqlKind() == SqlKind.BATCH_INSERT
1✔
132
                                                || executionContext.getSqlKind() == SqlKind.ENTITY_BATCH_INSERT);
1✔
133
                                var supportsBatchKeys = sqlConfig.getDialect().supportsBatchGeneratedKeys();
1✔
134

135
                                if ((supportsBatchKeys || !isBatchOperation) && executionContext.hasGeneratedKeyColumns()) {
1✔
136
                                        stmt = conn.prepareStatement(executionContext.getExecutableSql(),
1✔
137
                                                        executionContext.getGeneratedKeyColumns());
1✔
138
                                } else {
139
                                        stmt = conn.prepareStatement(executionContext.getExecutableSql());
1✔
140
                                }
141
                        } else {
1✔
142
                                throw new UroborosqlTransactionException("Transaction not started.");
1✔
143
                        }
144
                        break;
145
                case SELECT:
146
                case ENTITY_SELECT:
147
                        stmt = conn.prepareStatement(executionContext.getExecutableSql(),
1✔
148
                                        executionContext.getResultSetType(),
1✔
149
                                        executionContext.getResultSetConcurrency());
1✔
150
                        break;
1✔
151
                default:
152
                        if (updatable) {
1✔
153
                                stmt = conn.prepareStatement(executionContext.getExecutableSql());
1✔
154
                        } else {
155
                                throw new UroborosqlTransactionException("Transaction not started.");
1✔
156
                        }
157
                        break;
158
                }
159

160
                // PreparedStatement作成後イベント発行
161
                if (this.sqlConfig.getEventListenerHolder().hasAfterCreatePreparedStatementListener()) {
1✔
162
                        var eventObj = new AfterCreatePreparedStatementEvent(executionContext, conn, stmt);
1✔
163
                        for (var listener : this.sqlConfig.getEventListenerHolder().getAfterCreatePreparedStatementListeners()) {
1✔
164
                                listener.accept(eventObj);
1✔
165
                        }
1✔
166
                        stmt = eventObj.getPreparedStatement();
1✔
167
                }
168
                return stmt;
1✔
169
        }
170

171
        /**
172
         * {@inheritDoc}
173
         *
174
         * @see jp.co.future.uroborosql.tx.TransactionContext#getCallableStatement(jp.co.future.uroborosql.context.ExecutionContext)
175
         */
176
        @Override
177
        public CallableStatement getCallableStatement(final ExecutionContext executionContext) throws SQLException {
178
                var conn = getConnection();
1✔
179

180
                if (this.updatable) {
1✔
181
                        var cstmt = conn.prepareCall(executionContext.getExecutableSql(),
1✔
182
                                        executionContext.getResultSetType(),
1✔
183
                                        executionContext.getResultSetConcurrency());
1✔
184

185
                        // CallableStatement作成後イベント発行
186
                        if (this.sqlConfig.getEventListenerHolder().hasAfterCreateCallableStatementListener()) {
1✔
187
                                var eventObj = new AfterCreateCallableStatementEvent(executionContext, conn, cstmt);
1✔
188
                                for (var listener : this.sqlConfig.getEventListenerHolder()
1✔
189
                                                .getAfterCreateCallableStatementListeners()) {
1✔
190
                                        listener.accept(eventObj);
1✔
191
                                }
1✔
192
                                cstmt = eventObj.getCallableStatement();
1✔
193

194
                        }
195
                        return cstmt;
1✔
196
                } else {
197
                        throw new UroborosqlTransactionException("Transaction not started.");
1✔
198
                }
199
        }
200

201
        /**
202
         * {@inheritDoc}
203
         *
204
         * @see jp.co.future.uroborosql.tx.TransactionContext#commit()
205
         */
206
        @Override
207
        public void commit() {
208
                try {
209
                        if (connection != null && !connection.isClosed() && !connection.getAutoCommit()) {
1✔
210
                                // コミット前イベント発行
211
                                if (this.sqlConfig.getEventListenerHolder().hasBeforeCommitListener()) {
1✔
212
                                        var beforeEventObj = new BeforeCommitEvent(this, connection);
1✔
213
                                        this.sqlConfig.getEventListenerHolder().getBeforeCommitListeners()
1✔
214
                                                        .forEach(listener -> listener.accept(beforeEventObj));
1✔
215
                                }
216
                                connection.commit();
1✔
217
                                // コミット後イベント発行
218
                                if (this.sqlConfig.getEventListenerHolder().hasAfterCommitListener()) {
1✔
219
                                        var afterEventObj = new AfterCommitEvent(this, connection);
1✔
220
                                        this.sqlConfig.getEventListenerHolder().getAfterCommitListeners()
1✔
221
                                                        .forEach(listener -> listener.accept(afterEventObj));
1✔
222
                                }
223
                        }
UNCOV
224
                } catch (SQLException ex) {
×
UNCOV
225
                        throw new UroborosqlSQLException(ex);
×
226
                }
1✔
227
                clearState();
1✔
228
        }
1✔
229

230
        /**
231
         * {@inheritDoc}
232
         *
233
         * @see jp.co.future.uroborosql.tx.TransactionContext#rollback()
234
         */
235
        @Override
236
        public void rollback() {
237
                try {
238
                        if (connection != null && !connection.isClosed() && !connection.getAutoCommit()) {
1✔
239
                                // ロールバック前イベント発行
240
                                if (this.sqlConfig.getEventListenerHolder().hasBeforeRollbackListener()) {
1✔
241
                                        var beforeEventObj = new BeforeRollbackEvent(this, connection);
1✔
242
                                        this.sqlConfig.getEventListenerHolder().getBeforeRollbackListeners()
1✔
243
                                                        .forEach(listener -> listener.accept(beforeEventObj));
1✔
244
                                }
245
                                connection.rollback();
1✔
246
                                // ロールバック後イベント発行
247
                                if (this.sqlConfig.getEventListenerHolder().hasAfterRollbackListener()) {
1✔
248
                                        var afterEventObj = new AfterRollbackEvent(this, connection);
1✔
249
                                        this.sqlConfig.getEventListenerHolder().getAfterRollbackListeners()
1✔
250
                                                        .forEach(listener -> listener.accept(afterEventObj));
1✔
251
                                }
252
                        }
UNCOV
253
                } catch (SQLException ex) {
×
UNCOV
254
                        throw new UroborosqlSQLException(ex);
×
255
                }
1✔
256
                clearState();
1✔
257
        }
1✔
258

259
        /**
260
         * {@inheritDoc}
261
         *
262
         * @see jp.co.future.uroborosql.tx.TransactionContext#isRollbackOnly()
263
         */
264
        @Override
265
        public boolean isRollbackOnly() {
266
                return rollbackOnly;
1✔
267
        }
268

269
        /**
270
         * {@inheritDoc}
271
         *
272
         * @see jp.co.future.uroborosql.tx.TransactionContext#setRollbackOnly()
273
         */
274
        @Override
275
        public void setRollbackOnly() {
276
                this.rollbackOnly = true;
1✔
277
        }
1✔
278

279
        /**
280
         * {@inheritDoc}
281
         *
282
         * @see jp.co.future.uroborosql.tx.TransactionContext#rollback(java.lang.String)
283
         */
284
        @Override
285
        public void rollback(final String savepointName) {
286
                if (connection != null) {
1✔
287
                        try {
288
                                connection.rollback(savepointMap.get(savepointName));
1✔
UNCOV
289
                        } catch (SQLException ex) {
×
UNCOV
290
                                throw new UroborosqlSQLException(ex);
×
291
                        }
1✔
292
                }
293
        }
1✔
294

295
        /**
296
         * {@inheritDoc}
297
         *
298
         * @see jp.co.future.uroborosql.tx.TransactionContext#setSavepoint(java.lang.String)
299
         */
300
        @Override
301
        public void setSavepoint(final String savepointName) {
302
                if (savepointNames.contains(savepointName)) {
1✔
UNCOV
303
                        throw new IllegalStateException();
×
304
                }
305
                savepointNames.add(savepointName);
1✔
306

307
                if (connection != null) {
1✔
308
                        try {
309
                                savepointMap.put(savepointName, connection.setSavepoint(savepointName));
1✔
UNCOV
310
                        } catch (SQLException ex) {
×
UNCOV
311
                                throw new UroborosqlSQLException(ex);
×
312
                        }
1✔
313
                }
314
        }
1✔
315

316
        /**
317
         * {@inheritDoc}
318
         *
319
         * @see jp.co.future.uroborosql.tx.TransactionContext#releaseSavepoint(java.lang.String)
320
         */
321
        @Override
322
        public void releaseSavepoint(final String savepointName) {
323
                var savepoint = savepointMap.get(savepointName);
1✔
324

325
                var pos = savepointNames.lastIndexOf(savepointName);
1✔
326
                if (pos > -1) {
1✔
327
                        var subList = savepointNames.subList(pos, savepointNames.size());
1✔
328
                        for (var name : subList) {
1✔
329
                                savepointMap.remove(name);
1✔
330
                        }
1✔
331
                        subList.clear();
1✔
332
                }
333

334
                if (savepoint != null && connection != null) {
1✔
335
                        try {
336
                                connection.releaseSavepoint(savepoint);
1✔
UNCOV
337
                        } catch (SQLException ex) {
×
UNCOV
338
                                throw new UroborosqlSQLException(ex);
×
339
                        }
1✔
340
                }
341
        }
1✔
342

343
        /**
344
         * {@inheritDoc}
345
         *
346
         * @see jp.co.future.uroborosql.tx.TransactionContext#getSavepointNames()
347
         */
348
        @Override
349
        public List<String> getSavepointNames() {
UNCOV
350
                return Collections.unmodifiableList(this.savepointNames);
×
351
        }
352

353
        /**
354
         * {@inheritDoc}
355
         *
356
         * @see jp.co.future.uroborosql.tx.TransactionContext#close()
357
         */
358
        @Override
359
        public void close() {
360
                try {
361
                        if (!isRollbackOnly()) {
1✔
362
                                commit();
1✔
363
                        } else {
364
                                rollback();
1✔
365
                        }
366
                        if (connection != null && !connection.isClosed()) {
1✔
367
                                connection.close();
1✔
368
                        } else {
369
                                warnWith(LOG)
1✔
370
                                                .log("Connection close was skipped because the connection was already closed.");
1✔
371
                        }
UNCOV
372
                } catch (SQLException ex) {
×
UNCOV
373
                        throw new UroborosqlSQLException(ex);
×
374
                }
1✔
375
                connection = null;
1✔
376
        }
1✔
377

378
        /**
379
         * ステータスクリア
380
         */
381
        void clearState() {
382
                savepointNames.clear();
1✔
383
                savepointMap.clear();
1✔
384
                rollbackOnly = false;
1✔
385
        }
1✔
386

387
        /**
388
         * Savepointの設定を遅延して行う
389
         *
390
         * @param connection コネクション
391
         * @throws SQLException SQL例外
392
         */
393
        private void initSavepoints(final Connection connection) {
394
                for (var savepointName : savepointNames) {
1✔
395
                        try {
396
                                savepointMap.put(savepointName, connection.setSavepoint(savepointName));
1✔
UNCOV
397
                        } catch (SQLException ex) {
×
UNCOV
398
                                throw new UroborosqlSQLException(ex);
×
399
                        }
1✔
400
                }
1✔
401
        }
1✔
402
}
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

© 2026 Coveralls, Inc