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

future-architect / uroborosql / #931

18 Nov 2025 08:56AM UTC coverage: 90.793% (+0.006%) from 90.787%
#931

Pull #376

Copilot
Add test for supportsBatchGeneratedKeys() in MsSqlDialect

Co-authored-by: HidekiSugimoto189 <21981922+HidekiSugimoto189@users.noreply.github.com>
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%)

18 existing lines in 1 file now uncovered.

9220 of 10155 relevant lines covered (90.79%)

0.91 hits per line

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

87.32
/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.event.AfterCommitEvent;
24
import jp.co.future.uroborosql.event.AfterCreateCallableStatementEvent;
25
import jp.co.future.uroborosql.event.AfterCreatePreparedStatementEvent;
26
import jp.co.future.uroborosql.event.AfterRollbackEvent;
27
import jp.co.future.uroborosql.event.BeforeCommitEvent;
28
import jp.co.future.uroborosql.event.BeforeRollbackEvent;
29
import jp.co.future.uroborosql.exception.UroborosqlSQLException;
30
import jp.co.future.uroborosql.exception.UroborosqlTransactionException;
31

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

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

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

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

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

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

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

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

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

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

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

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

120
                PreparedStatement stmt = null;
1✔
121
                switch (executionContext.getSqlKind()) {
1✔
122
                case INSERT:
123
                case ENTITY_INSERT:
124
                case BULK_INSERT:
125
                case BATCH_INSERT:
126
                case ENTITY_BULK_INSERT:
127
                case ENTITY_BATCH_INSERT:
128
                        if (updatable) {
1✔
129
                                // バッチ処理の場合、Dialectがバッチでの自動生成キー取得をサポートしているか確認
130
                                var isBatchOperation = executionContext.getSqlKind().name().contains("BATCH");
1✔
131
                                var supportsBatchKeys = sqlConfig.getDialect().supportsBatchGeneratedKeys();
1✔
132
                                
133
                                if (executionContext.hasGeneratedKeyColumns() && (!isBatchOperation || supportsBatchKeys)) {
1✔
134
                                        stmt = conn.prepareStatement(executionContext.getExecutableSql(),
1✔
135
                                                        executionContext.getGeneratedKeyColumns());
1✔
136
                                } else {
137
                                        stmt = conn.prepareStatement(executionContext.getExecutableSql());
1✔
138
                                }
139
                        } else {
1✔
140
                                throw new UroborosqlTransactionException("Transaction not started.");
1✔
141
                        }
142
                        break;
143
                case SELECT:
144
                case ENTITY_SELECT:
145
                        stmt = conn.prepareStatement(executionContext.getExecutableSql(),
1✔
146
                                        executionContext.getResultSetType(),
1✔
147
                                        executionContext.getResultSetConcurrency());
1✔
148
                        break;
1✔
149
                default:
150
                        if (updatable) {
1✔
151
                                stmt = conn.prepareStatement(executionContext.getExecutableSql());
1✔
152
                        } else {
153
                                throw new UroborosqlTransactionException("Transaction not started.");
1✔
154
                        }
155
                        break;
156
                }
157

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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