@@ -71,14 +71,15 @@ class QueryImpl implements Query {
7171 private final boolean managed ;
7272 private final int defaultFetchSize ;
7373 private final boolean streamOnlyFetchSize ;
74+ private final boolean streamingRequiresTransaction ;
7475 private final Function <Throwable , PersistenceException > exceptionTransformer ;
7576
7677 QueryImpl (@ Nonnull RefFactory refFactory ,
7778 @ Nonnull Function <Boolean , PreparedStatement > statement ,
7879 @ Nullable BindVarsHandle bindVarsHandle ,
7980 boolean versionAware ,
8081 @ Nonnull Function <Throwable , PersistenceException > exceptionTransformer ) {
81- this (refFactory , statement , bindVarsHandle , null , versionAware , false , false , 0 , false , exceptionTransformer );
82+ this (refFactory , statement , bindVarsHandle , null , versionAware , false , false , 0 , false , false , exceptionTransformer );
8283 }
8384
8485 QueryImpl (@ Nonnull RefFactory refFactory ,
@@ -87,7 +88,7 @@ class QueryImpl implements Query {
8788 boolean versionAware ,
8889 boolean unsafe ,
8990 @ Nonnull Function <Throwable , PersistenceException > exceptionTransformer ) {
90- this (refFactory , statement , bindVarsHandle , null , versionAware , false , unsafe , 0 , false , exceptionTransformer );
91+ this (refFactory , statement , bindVarsHandle , null , versionAware , false , unsafe , 0 , false , false , exceptionTransformer );
9192 }
9293
9394 QueryImpl (@ Nonnull RefFactory refFactory ,
@@ -97,7 +98,7 @@ class QueryImpl implements Query {
9798 boolean versionAware ,
9899 boolean unsafe ,
99100 @ Nonnull Function <Throwable , PersistenceException > exceptionTransformer ) {
100- this (refFactory , statement , bindVarsHandle , affectedType , versionAware , false , unsafe , 0 , false , exceptionTransformer );
101+ this (refFactory , statement , bindVarsHandle , affectedType , versionAware , false , unsafe , 0 , false , false , exceptionTransformer );
101102 }
102103
103104 QueryImpl (@ Nonnull RefFactory refFactory ,
@@ -108,7 +109,7 @@ class QueryImpl implements Query {
108109 boolean managed ,
109110 boolean unsafe ,
110111 @ Nonnull Function <Throwable , PersistenceException > exceptionTransformer ) {
111- this (refFactory , statement , bindVarsHandle , affectedType , versionAware , managed , unsafe , 0 , false , exceptionTransformer );
112+ this (refFactory , statement , bindVarsHandle , affectedType , versionAware , managed , unsafe , 0 , false , false , exceptionTransformer );
112113 }
113114
114115 QueryImpl (@ Nonnull RefFactory refFactory ,
@@ -120,6 +121,7 @@ class QueryImpl implements Query {
120121 boolean unsafe ,
121122 int defaultFetchSize ,
122123 boolean streamOnlyFetchSize ,
124+ boolean streamingRequiresTransaction ,
123125 @ Nonnull Function <Throwable , PersistenceException > exceptionTransformer ) {
124126 this .refFactory = refFactory ;
125127 this .statement = statement ;
@@ -130,6 +132,7 @@ class QueryImpl implements Query {
130132 this .unsafe = unsafe ;
131133 this .defaultFetchSize = defaultFetchSize ;
132134 this .streamOnlyFetchSize = streamOnlyFetchSize ;
135+ this .streamingRequiresTransaction = streamingRequiresTransaction ;
133136 this .exceptionTransformer = exceptionTransformer ;
134137 }
135138
@@ -147,7 +150,7 @@ class QueryImpl implements Query {
147150 */
148151 @ Override
149152 public PreparedQuery prepare () {
150- return MonitoredResource .wrap (new PreparedQueryImpl (refFactory , statement .apply (unsafe ), bindVarsHandle , affectedType , versionAware , managed , defaultFetchSize , streamOnlyFetchSize , exceptionTransformer ));
153+ return MonitoredResource .wrap (new PreparedQueryImpl (refFactory , statement .apply (unsafe ), bindVarsHandle , affectedType , versionAware , managed , defaultFetchSize , streamOnlyFetchSize , streamingRequiresTransaction , exceptionTransformer ));
151154 }
152155
153156 /**
@@ -158,7 +161,7 @@ public PreparedQuery prepare() {
158161 */
159162 @ Override
160163 public Query managed () {
161- return new QueryImpl (refFactory , statement , bindVarsHandle , affectedType , versionAware , true , unsafe , defaultFetchSize , streamOnlyFetchSize , exceptionTransformer );
164+ return new QueryImpl (refFactory , statement , bindVarsHandle , affectedType , versionAware , true , unsafe , defaultFetchSize , streamOnlyFetchSize , streamingRequiresTransaction , exceptionTransformer );
162165 }
163166
164167 /**
@@ -169,11 +172,11 @@ public Query managed() {
169172 */
170173 @ Override
171174 public Query unsafe () {
172- return new QueryImpl (refFactory , statement , bindVarsHandle , affectedType , versionAware , managed , true , defaultFetchSize , streamOnlyFetchSize , exceptionTransformer );
175+ return new QueryImpl (refFactory , statement , bindVarsHandle , affectedType , versionAware , managed , true , defaultFetchSize , streamOnlyFetchSize , streamingRequiresTransaction , exceptionTransformer );
173176 }
174177
175178 private QueryImpl withoutFetchSize () {
176- return new QueryImpl (refFactory , statement , bindVarsHandle , affectedType , versionAware , managed , unsafe , 0 , false , exceptionTransformer );
179+ return new QueryImpl (refFactory , statement , bindVarsHandle , affectedType , versionAware , managed , unsafe , 0 , false , false , exceptionTransformer );
177180 }
178181
179182 private PreparedStatement getStatement () {
@@ -186,6 +189,38 @@ private void applyFetchSize(@Nonnull PreparedStatement statement) throws SQLExce
186189 }
187190 }
188191
192+ /**
193+ * Configures the connection for cursor-based streaming when the dialect requires an active transaction.
194+ *
195+ * <p>If the connection is in auto-commit mode and the dialect indicates that streaming requires a transaction,
196+ * auto-commit is disabled to enable cursor-based result batching. The returned {@code Runnable} restores the
197+ * connection to its original state when the stream is closed.</p>
198+ *
199+ * @param statement the prepared statement whose connection to configure.
200+ * @return a cleanup action that restores auto-commit, or {@code null} if no configuration was needed.
201+ */
202+ private @ Nullable Runnable configureStreamingTransaction (@ Nonnull PreparedStatement statement ) {
203+ if (streamingRequiresTransaction && defaultFetchSize != 0 ) {
204+ try {
205+ var connection = statement .getConnection ();
206+ if (connection .getAutoCommit ()) {
207+ connection .setAutoCommit (false );
208+ return () -> {
209+ try {
210+ connection .commit ();
211+ connection .setAutoCommit (true );
212+ } catch (SQLException e ) {
213+ throw new PersistenceException (e );
214+ }
215+ };
216+ }
217+ } catch (SQLException ignore ) {
218+ // Unable to determine or change auto-commit state; proceed without cursor-based streaming.
219+ }
220+ }
221+ return null ;
222+ }
223+
189224 protected boolean closeStatement () {
190225 return true ;
191226 }
@@ -216,6 +251,7 @@ public Stream<Object[]> getResultStream() {
216251 boolean close = true ;
217252 try {
218253 applyFetchSize (statement );
254+ Runnable streamingCleanup = configureStreamingTransaction (statement );
219255 ResultSet resultSet = statement .executeQuery ();
220256 try {
221257 int columnCount = resultSet .getMetaData ().getColumnCount ();
@@ -229,7 +265,7 @@ public Stream<Object[]> getResultStream() {
229265 }
230266 })
231267 .takeWhile (Objects ::nonNull )
232- .onClose (() -> close (resultSet , statement )));
268+ .onClose (() -> close (resultSet , statement , streamingCleanup )));
233269 } finally {
234270 if (close ) {
235271 resultSet .close ();
@@ -271,6 +307,7 @@ public <T> Stream<T> getResultStream(@Nonnull Class<T> type) {
271307 try {
272308 try {
273309 applyFetchSize (statement );
310+ Runnable streamingCleanup = configureStreamingTransaction (statement );
274311 ResultSet resultSet = statement .executeQuery ();
275312 int columnCount = resultSet .getMetaData ().getColumnCount ();
276313 var mapper = getObjectMapper (columnCount , type , refFactory )
@@ -285,7 +322,7 @@ public <T> Stream<T> getResultStream(@Nonnull Class<T> type) {
285322 }
286323 })
287324 .takeWhile (Objects ::nonNull )
288- .onClose (() -> close (resultSet , statement )));
325+ .onClose (() -> close (resultSet , statement , streamingCleanup )));
289326 } finally {
290327 if (close && closeStatement ()) {
291328 statement .close ();
@@ -377,9 +414,20 @@ public long getResultCount() {
377414 }
378415
379416 protected void close (@ Nonnull ResultSet resultSet , @ Nonnull PreparedStatement statement ) {
417+ close (resultSet , statement , null );
418+ }
419+
420+ protected void close (@ Nonnull ResultSet resultSet , @ Nonnull PreparedStatement statement ,
421+ @ Nullable Runnable streamingCleanup ) {
380422 try {
381423 try {
382- resultSet .close ();
424+ try {
425+ resultSet .close ();
426+ } finally {
427+ if (streamingCleanup != null ) {
428+ streamingCleanup .run ();
429+ }
430+ }
383431 } finally {
384432 if (closeStatement ()) {
385433 statement .close ();
0 commit comments